diff options
author | Vivien Kraus <vivien@planete-kraus.eu> | 2021-02-21 01:44:01 +0100 |
---|---|---|
committer | Vivien Kraus <vivien@planete-kraus.eu> | 2021-02-21 12:23:32 +0100 |
commit | c9533cb89bc9d209d48314c7d608090b3d6ab7b5 (patch) | |
tree | f17cbedfc7e15373377a6771442d3503e610ba4a |
First commit
-rw-r--r-- | AUTHORS | 6 | ||||
-rw-r--r-- | COPYING | 3 | ||||
-rw-r--r-- | ChangeLog | 1 | ||||
-rw-r--r-- | Makefile.am | 44 | ||||
-rw-r--r-- | NEWS | 7 | ||||
-rw-r--r-- | README | 7 | ||||
-rwxr-xr-x | bootstrap | 7 | ||||
-rw-r--r-- | configure.ac | 13 | ||||
-rw-r--r-- | doc/fdl.texi | 505 | ||||
-rw-r--r-- | pre-inst-env.in | 15 | ||||
-rw-r--r-- | web-client-with-cache.org | 542 |
11 files changed, 1150 insertions, 0 deletions
@@ -0,0 +1,6 @@ +-*- mode: org; coding: utf-8; -*- + +#+TITLE: Web-Client-With-Cache NEWS – authors +#+STARTUP: content hidestars + +- Vivien Kraus [[mailto:vivien@planete-kraus.eu]]
\ No newline at end of file @@ -0,0 +1,3 @@ +This project's license is GPL 3+. + +You can read the full license at https://www.gnu.org/licenses/gpl.html. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..76c33bf --- /dev/null +++ b/ChangeLog @@ -0,0 +1 @@ +For a complete log, please see the Git commit log at </PATH/TO/LOG>. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..70babd4 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,44 @@ +DISTCHECK_CONFIGURE_FLAGS = CONFIG_SHELL=$(CONFIG_SHELL) SHELL=$(SHELL) +ACLOCAL_AMFLAGS = -I m4 +nodist_noinst_SCRIPTS = pre-inst-env + +EXTRA_DIST = web-client-with-cache.org + +moddir = $(prefix)/share/guile/site/$(GUILE_EFFECTIVE_VERSION) +godir = $(libdir)/guile/$(GUILE_EFFECTIVE_VERSION)/site-ccache + +webmoddir = $(moddir)/web +webgodir = $(godir)/web +clientwebmoddir = $(webmoddir)/client +clientwebgodir = $(webgodir)/client + +dist_clientwebmod_DATA = $(srcdir)/web/client/with-cache.scm +clientwebgo_DATA = web/client/with-cache.go + +install_go_targets = install-clientwebgoDATA +install_mod_targets = install-dist_clientwebmodDATA + +CLEANFILES = $(clientwebgo_DATA) + +SUFFIXES = .scm .go +.scm.go: + $(AM_V_GEN) $(top_builddir)/pre-inst-env $(GUILD) compile $(GUILE_WARNINGS) $(GUILD_OPTIONS) -o "$@-t" "$<" + @mv "$@-t" "$@" + +$(install_go_targets): $(install_mod_targets) + +$(srcdir)/web/client/with-cache.scm: web-client-with-cache.org + $(AM_V_GEN) $(EMACS) --batch \ + --file $(srcdir)/$< \ + --eval "(setq org-confirm-babel-evaluate nil)" \ + --eval "(setq geiser-scheme-implementation 'guile)" \ + -f org-babel-tangle \ + || rm -f web/client/with-cache.scm + +$(srcdir)/doc/web-client-with-cache.texi: web-client-with-cache.org + $(AM_V_GEN) $(EMACS) --batch \ + --file $(srcdir)/$< \ + --eval "(setq org-confirm-babel-evaluate nil)" \ + --eval "(setq geiser-scheme-implementation 'guile)" \ + -f org-texinfo-export-to-texinfo + @mv web-client-with-cache.texi doc/web-client-with-cache.texi @@ -0,0 +1,7 @@ +-*- mode: org; coding: utf-8; -*- + +#+TITLE: Web-Client-With-Cache NEWS – history of user-visible changes +#+STARTUP: content hidestars + +* Publication at 0.0.0 +The code for the main function is written. @@ -0,0 +1,7 @@ +-*- mode: org; coding: utf-8; -*- + +#+TITLE: README for Web-Client-With-Cache + +This project is contained in the Org notebook named +web-client-with-cache.org. You should definitely read that one. + diff --git a/bootstrap b/bootstrap new file mode 100755 index 0000000..11f3413 --- /dev/null +++ b/bootstrap @@ -0,0 +1,7 @@ +#!/bin/sh + +(echo '\input texinfo' ; + echo '@include version.texi') \ + > doc/web-client-with-cache.texi || exit 1 +autoreconf -vif || exit 1 +rm doc/web-client-with-cache.texi || exit 1 diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..3ec536c --- /dev/null +++ b/configure.ac @@ -0,0 +1,13 @@ +dnl -*- Autoconf -*- + +AC_INIT([web-client-with-cache], [0.0.0], [vivien@planete-kraus.eu]) +AC_CONFIG_SRCDIR([web-client-with-cache.org]) +AM_INIT_AUTOMAKE([subdir-objects]) +AM_SILENT_RULES([yes]) +AM_MISSING_PROG([EMACS], [emacs]) +GUILE_PKG([3.0 2.2 2.0]) +GUILE_PROGS +GUILE_SITE_DIR +AC_CONFIG_FILES([Makefile]) +AC_CONFIG_FILES([pre-inst-env], [chmod +x pre-inst-env]) +AC_OUTPUT diff --git a/doc/fdl.texi b/doc/fdl.texi new file mode 100644 index 0000000..eaf3da0 --- /dev/null +++ b/doc/fdl.texi @@ -0,0 +1,505 @@ +@c The GNU Free Documentation License. +@center Version 1.3, 3 November 2008 + +@c This file is intended to be included within another document, +@c hence no sectioning command or @node. + +@display +Copyright @copyright{} 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. +@uref{https://fsf.org/} + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. +@end display + +@enumerate 0 +@item +PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document @dfn{free} in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of ``copyleft'', which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + +@item +APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The ``Document'', below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as ``you''. You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A ``Modified Version'' of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A ``Secondary Section'' is a named appendix or a front-matter section +of the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall +subject (or to related matters) and contains nothing that could fall +directly within that overall subject. (Thus, if the Document is in +part a textbook of mathematics, a Secondary Section may not explain +any mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The ``Invariant Sections'' are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The ``Cover Texts'' are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A ``Transparent'' copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not ``Transparent'' is called ``Opaque''. + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, La@TeX{} input +format, SGML or XML using a publicly available +DTD, and standard-conforming simple HTML, +PostScript or PDF designed for human modification. Examples +of transparent image formats include PNG, XCF and +JPG@. Opaque formats include proprietary formats that can be +read and edited only by proprietary word processors, SGML or +XML for which the DTD and/or processing tools are +not generally available, and the machine-generated HTML, +PostScript or PDF produced by some word processors for +output purposes only. + +The ``Title Page'' means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, ``Title Page'' means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +The ``publisher'' means any person or entity that distributes copies +of the Document to the public. + +A section ``Entitled XYZ'' means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as ``Acknowledgements'', +``Dedications'', ``Endorsements'', or ``History''.) To ``Preserve the Title'' +of such a section when you modify the Document means that it remains a +section ``Entitled XYZ'' according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + +@item +VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + +@item +COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + +@item +MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +@enumerate A +@item +Use in the Title Page (and on the covers, if any) a title distinct +from that of the Document, and from those of previous versions +(which should, if there were any, be listed in the History section +of the Document). You may use the same title as a previous version +if the original publisher of that version gives permission. + +@item +List on the Title Page, as authors, one or more persons or entities +responsible for authorship of the modifications in the Modified +Version, together with at least five of the principal authors of the +Document (all of its principal authors, if it has fewer than five), +unless they release you from this requirement. + +@item +State on the Title page the name of the publisher of the +Modified Version, as the publisher. + +@item +Preserve all the copyright notices of the Document. + +@item +Add an appropriate copyright notice for your modifications +adjacent to the other copyright notices. + +@item +Include, immediately after the copyright notices, a license notice +giving the public permission to use the Modified Version under the +terms of this License, in the form shown in the Addendum below. + +@item +Preserve in that license notice the full lists of Invariant Sections +and required Cover Texts given in the Document's license notice. + +@item +Include an unaltered copy of this License. + +@item +Preserve the section Entitled ``History'', Preserve its Title, and add +to it an item stating at least the title, year, new authors, and +publisher of the Modified Version as given on the Title Page. If +there is no section Entitled ``History'' in the Document, create one +stating the title, year, authors, and publisher of the Document as +given on its Title Page, then add an item describing the Modified +Version as stated in the previous sentence. + +@item +Preserve the network location, if any, given in the Document for +public access to a Transparent copy of the Document, and likewise +the network locations given in the Document for previous versions +it was based on. These may be placed in the ``History'' section. +You may omit a network location for a work that was published at +least four years before the Document itself, or if the original +publisher of the version it refers to gives permission. + +@item +For any section Entitled ``Acknowledgements'' or ``Dedications'', Preserve +the Title of the section, and preserve in the section all the +substance and tone of each of the contributor acknowledgements and/or +dedications given therein. + +@item +Preserve all the Invariant Sections of the Document, +unaltered in their text and in their titles. Section numbers +or the equivalent are not considered part of the section titles. + +@item +Delete any section Entitled ``Endorsements''. Such a section +may not be included in the Modified Version. + +@item +Do not retitle any existing section to be Entitled ``Endorsements'' or +to conflict in title with any Invariant Section. + +@item +Preserve any Warranty Disclaimers. +@end enumerate + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled ``Endorsements'', provided it contains +nothing but endorsements of your Modified Version by various +parties---for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + +@item +COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled ``History'' +in the various original documents, forming one section Entitled +``History''; likewise combine any sections Entitled ``Acknowledgements'', +and any sections Entitled ``Dedications''. You must delete all +sections Entitled ``Endorsements.'' + +@item +COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + +@item +AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an ``aggregate'' if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + +@item +TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled ``Acknowledgements'', +``Dedications'', or ``History'', the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + +@item +TERMINATION + +You may not copy, modify, sublicense, or distribute the Document +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense, or distribute it is void, and +will automatically terminate your rights under this License. + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, receipt of a copy of some or all of the same material does +not give you any rights to use it. + +@item +FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. See +@uref{https://www.gnu.org/licenses/}. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License ``or any later version'' applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. If the Document +specifies that a proxy can decide which future versions of this +License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the +Document. + +@item +RELICENSING + +``Massive Multiauthor Collaboration Site'' (or ``MMC Site'') means any +World Wide Web server that publishes copyrightable works and also +provides prominent facilities for anybody to edit those works. A +public wiki that anybody can edit is an example of such a server. A +``Massive Multiauthor Collaboration'' (or ``MMC'') contained in the +site means any set of copyrightable works thus published on the MMC +site. + +``CC-BY-SA'' means the Creative Commons Attribution-Share Alike 3.0 +license published by Creative Commons Corporation, a not-for-profit +corporation with a principal place of business in San Francisco, +California, as well as future copyleft versions of that license +published by that same organization. + +``Incorporate'' means to publish or republish a Document, in whole or +in part, as part of another Document. + +An MMC is ``eligible for relicensing'' if it is licensed under this +License, and if all works that were first published under this License +somewhere other than this MMC, and subsequently incorporated in whole +or in part into the MMC, (1) had no cover texts or invariant sections, +and (2) were thus incorporated prior to November 1, 2008. + +The operator of an MMC Site may republish an MMC contained in the site +under CC-BY-SA on the same site at any time before August 1, 2009, +provided the MMC is eligible for relicensing. + +@end enumerate + +@page +@heading ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + +@smallexample +@group + Copyright (C) @var{year} @var{your name}. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover + Texts. A copy of the license is included in the section entitled ``GNU + Free Documentation License''. +@end group +@end smallexample + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the ``with@dots{}Texts.''@: line with this: + +@smallexample +@group + with the Invariant Sections being @var{list their titles}, with + the Front-Cover Texts being @var{list}, and with the Back-Cover Texts + being @var{list}. +@end group +@end smallexample + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. + +@c Local Variables: +@c ispell-local-pdict: "ispell-dict" +@c End: diff --git a/pre-inst-env.in b/pre-inst-env.in new file mode 100644 index 0000000..844cf1e --- /dev/null +++ b/pre-inst-env.in @@ -0,0 +1,15 @@ +#!/bin/sh + +abs_top_srcdir="$(cd "@abs_top_srcdir@" > /dev/null; pwd)" +abs_top_builddir="$(cd "@abs_top_builddir@" > /dev/null; pwd)" + +export GUILE_LOAD_COMPILED_PATH="$abs_top_builddir${GUILE_LOAD_COMPILED_PATH:+:}$GUILE_LOAD_COMPILED_PATH" +export GUILE_LOAD_PATH="$abs_top_builddir:$abs_top_srcdir${GUILE_LOAD_PATH:+:}:$GUILE_LOAD_PATH" +export "XDG_DATA_HOME=$abs_top_builddir/xdg-data-home" +export "XDG_CACHE_HOME=$abs_top_builddir/xdg-cache-home" +mkdir -p "$XDG_DATA_HOME" +mkdir -p "$XDG_CACHE_HOME" +export GUILE_LOAD_COMPILED_PATH="$abs_top_builddir:$GUILE_LOAD_COMPILED_PATH" +export GUILE_LOAD_PATH="$abs_top_builddir:$abs_top_srcdir:$GUILE_LOAD_PATH" +export GUILE_AUTO_COMPILE=0 +exec "$@" diff --git a/web-client-with-cache.org b/web-client-with-cache.org new file mode 100644 index 0000000..17d3f4f --- /dev/null +++ b/web-client-with-cache.org @@ -0,0 +1,542 @@ +#+title: Web Client with Cache {{{version}}} +#+subtitle: for version {{{version}}}, {{{updated}}} +#+author: Vivien Kraus +#+email: vivien@planete-kraus.eu +#+options: ':t toc:t author:t email:t +#+language: en + +#+macro: version @@texinfo:@value{VERSION}@@ +#+macro: updated @@texinfo:@value{UPDATED}@@ +#+texinfo_filename: web-client-with-cache.info +#+texinfo_header: @syncodeindex pg cp fn +#+texinfo_dir_category: The Algorithmic Language Scheme +#+texinfo_dir_title: Web Client with Cache: (web-client-with-cache) +#+texinfo_dir_desc: Using the cache +#+texinfo_printed_title: Web Client with Cache + +This manual is for the Guile Web Client with Cache (version +{{{version}}}, {{{updated}}}). + +* Copying + :PROPERTIES: + :COPYING: t + :END: +This manual is for the Guile Web Client with Cache. + +Copyright \copy 2021 Vivien Kraus. + +#+begin_quote +Permission is granted to copy, distribute and/or modify this document +under the terms of the GNU Free Documentation License, Version 1.3 or +any later version published by the Free Software Foundation; with no +Invariant Sections, with no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license is included in the section entitled "GNU +Free Documentation License". + +The program present in this document is free software: you can +redistribute it and/or modify it under the terms of the GNU General +Public License as published by the Free Software Foundation, either +version 3 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see [[https://www.gnu.org/licenses/]]. +#+end_quote + +* Using the cache +#+cindex: using the cache + +The API is defined in the =(web client with-cache)= module. It +consists of one function: + +#+begin_src emacs-lisp :noweb yes :exports results :results drawer + (format " + ,#+begin_export texinfo + @defun with-cache [#:http-get] [#:current-time] [#:box] + <<with-cache-docstring>> + @end defun + ,#+end_export") +#+end_src + +* Covoiturage +The goal here is to wrap an expensive long-running pure function so +that calling it multiple times with the same arguments until it has +not returned reuses the return value. The name is not pretty, if you +find a better one please tell me. + +#+name: covoiturage-docstring +#+caption: Docstring of the covoiturage function +#+begin_src texinfo + Wrap @var{f} so that simultaneous calls use the same return value. By + simultaneous, we mean parallel calls with the same value for the first + argument (compared with @code{equal?}) during the long-running + evaluation of @var{f}. + + @var{f} takes at least one argument, the key. @var{f} may throw or + return. It is executed in a Future (from @code{(ice-9 futures)}, so + side effects should be synchronized somehow. +#+end_src + +Internally, we use an atomic box. The atomic box contains an alist +from key values to promises to futures. Futures are cool, but once +they are constructed they start running immediately. + +However, if the lockless synchronization failed because of a +concurrent update (for instance, inserting a new query), we should not +start f at all before having updated the atomic box. Otherwise, for +http-get, an unwanted connection to the server will still be +attempted. + +To work around that, we use a promise to a future, so the +future starts when the promise is forced -- thus, either by our +thread, after we have synchronized the box ourselves; or by another +thread, because it has already been added to the box. + +Once the function has returned, we notify all the other waiting +threads and remove everything from the box. Function +[[fn-covoiturage-remove]] does the removal. + +#+name: fn-covoiturage-remove +#+caption: Remove a completed entry from a box +#+begin_src scheme :eval no + (define (remove box key) + (let* ((old (atomic-box-ref box)) + (new (filter (lambda (record) + (not (equal? (car record) key))) + old)) + (discarded + (atomic-box-compare-and-swap! box old new))) + (unless (eq? discarded old) + (remove box key)))) +#+end_src + +The application of function /f/ may either return (multiple values), +or throw. We want to forward both cases to all the callers, so we need +to intercept the throws. Function [[fn-covoiturage-wrap-f]] does that: +call f, and cons either ='ok= or ='error= and the return or throw +values. + +#+name: fn-covoiturage-wrap-f +#+caption: Wrap a thunk so that we know whether it threw or returned +#+begin_src scheme :eval no + (define (wrap-f f) + (catch #t + (lambda () + (call-with-values f + (lambda args + (cons 'ok args)))) + (lambda error + (cons 'error error)))) +#+end_src + +Symmetrically, when we want to report the value, we need to apply +either =throw= or =values=. This is the job of [[fn-covoiturage-report]]. + +#+name: fn-covoiturage-report +#+caption: Report the computed values +#+begin_src scheme :eval no + (define (report result) + (case (car result) + ((ok) (apply values (cdr result))) + ((error) (apply throw (cdr result))))) +#+end_src + +There are now two cases when we query a key: either there is an entry +in the cache, in which case we just remove that, or we need to create +a new future. Function [[fn-covoiturage-create]] will create the job (it +will not start until it has been added to the cache). We note that if +the return value should be cached for longer, the caching should +happen before /f/ has finished executing. + +#+name: fn-covoiturage-create +#+caption: Create a new job (don't start it yet) +#+begin_src scheme :eval no + (define (create box key f args) + (delay + (make-future + (lambda () + (let ((ret (wrap-f (lambda () (apply f key args))))) + (remove box key) + ret))))) +#+end_src + +The query function works atomically: it first reads the cache, use it +if it is present, and otherwise add an entry with a job and start the +job. If it can't do that atomically, it retries. That's function +[[fn-covoiturage-do-query]]. + +#+name: fn-covoiturage-do-query +#+caption: Do query the cache +#+begin_src scheme :eval no + (define (do-query box f key args) + (let ((old (atomic-box-ref box))) + (if (assoc-ref old key) + (report (touch (force (assoc-ref old key)))) + (let* ((job (create box key f args)) + (new (acons key job old))) + (let ((discarded + (atomic-box-compare-and-swap! box old new))) + (if (eq? discarded old) + ;; The box is updated, so the future can be + ;; forced and f starts running + (report (touch (force job))) + ;; Concurrent update, retry. + (do-query box f key args))))))) +#+end_src + +Finally, we wrap everything in a public function that will manage the +state (the atomic box). The final function is [[fn-covoiturage]]. + +#+name: fn-covoiturage +#+caption: The final function definition +#+begin_src scheme :eval no :noweb no-export + (define-public (covoiturage f) + "\ + <<covoiturage-docstring>>" + (let ((box (make-atomic-box '()))) + <<fn-covoiturage-remove>> + <<fn-covoiturage-wrap-f>> + <<fn-covoiturage-report>> + <<fn-covoiturage-create>> + <<fn-covoiturage-do-query>> + (lambda (key . args) + (do-query box f key args)))) +#+end_src + +* Web caching +While we still use an atomic box for this cache, it is more complex +than covoiturage. We need to fix HTTP headers, parse the responses, +manipulate time. + +** Filter =#:header= keyword argument + +Let's start with the headers. We need a function to parse keyword +arguments, extract the non-ignored headers and the non-header keyword +arguments. The function [[fn-filter-keyword]] extracts the headers, and +[[fn-filter-headers]] removes the unwanted ones. + +#+name: fn-filter-keyword +#+caption: Filter a keyword from a keyword list +#+begin_src scheme :eval no + (define (filter-keyword kw args) + (define (search args-kept value args) + (if (null? args) + (values value (reverse args-kept)) + (let ((next-keyword (car args)) + (next-value (cadr args)) + (rest (cddr args))) + (if (eq? next-keyword kw) + (search args-kept (or value next-value) rest) + (search + ;; args-kept is in reverse order! + (cons* next-value next-keyword args-kept) + value + rest))))) + (search '() #f args)) +#+end_src + +#+name: fn-filter-headers +#+caption: Remove headers for which we have the responsibility +#+begin_src scheme :eval no + (define (filter-headers headers) + (filter (lambda (header) + (case (car header) + ((if-none-match if-modified-since) #f) + (else #t))) + headers)) +#+end_src + +** Time management + +Now, it's time for time management. We need different auxiliary +functions: +1. Add seconds to a date, which is polymorphic enough to allow SRFI-19 + dates and SRFI-19 times; +2. Compare two dates or times. + +*** Adding seconds to a date +Function [[fn-date-add]] adds a number of seconds to a time or a date, and +returns a date. + +#+name: fn-date-add +#+caption: Add seconds to a date +#+begin_src scheme :eval no + (define (date-add date seconds) + (let* ((duration (make-time time-duration 0 seconds)) + (time (if (time? date) + date + (date->time-utc date))) + (result (add-duration time duration))) + (time-utc->date result))) +#+end_src + +*** Compare two dates/times +This is the job of functon [[fn-date-geqp]]. If the arguments are dates, +they are first compared to times. + +#+name: fn-date-geqp +#+caption: Compare two dates +#+begin_src scheme :eval no + (define (date>=? past future) + (when (date? past) + (set! past (date->time-utc past))) + (when (date? future) + (set! future (date->time-utc future))) + (time>=? past future)) +#+end_src + +** Fix the responses +There are different things we need to do to responses: +1. Add a date to every response that does not have one; +2. Estimate an expiration date for a response, based on either the + Expires header or its Date and Cache-Control information; +3. Merge the headers for two responses. + +*** Ensure that a response has a date +If the server did not set a date, we set it from the client +clock. This is the job of [[fn-response-with-date]]. + +#+name: fn-response-with-date +#+caption: Ensure that the response has a date. Use the client clock if needed. +#+begin_src scheme :eval no + (define (response-with-date response current-time) + ;; current-time is only used if response does not have a date. + (let ((date (response-date response (time-utc->date current-time))) + (other-headers + (filter (lambda (header) + (not (eq? (car header) 'date))) + (response-headers response)))) + (build-response + #:version (response-version response) + #:code (response-code response) + #:reason-phrase (response-reason-phrase response) + #:headers (acons 'date date other-headers) + #:port (response-port response)))) +#+end_src + +*** Estimate the expiration date from the response +The expiration date can be: +- the max-age value of the cache control after the response date, if + the cache control is present and has a max-age; +- the value of the Expires header; +- =#f= if the others failed. + +This is what [[fn-estimate-response-expires]] does. + +#+name: fn-estimate-response-expires +#+caption: Estimate the expiration date of a response +#+begin_src scheme :eval no + (define (estimate-response-expires response) + (let ((cache-control (response-cache-control response '())) + (expires (response-expires response)) + (date (response-date response))) + (let ((max-age (assoc-ref cache-control 'max-age))) + (cond (max-age + (date-add date max-age)) + (expires expires) + (else #f))))) +#+end_src + +*** Merge the headers of two responses +The difficult part is that this will happen every time a response is +refreshed from a 304 Not Modified response. So if we just merge the +alists, the header alist will grow arithmetically. The trick is to +use a hash table to remove duplicate header names. This explains +function [[fn-merge-headers]]. + +#+name: fn-merge-headers +#+caption: Merge the headers for two responses +#+begin_src scheme :eval no + (define (merge-headers most-important less-important) + (let ((h+ (response-headers most-important)) + (h- (response-headers less-important))) + (let* ((alist (append h+ h-)) + (table (alist->hash-table alist))) + (hash-table->alist table)))) +#+end_src + +** Wrap =http-fetch= so as to store the response +Now we can start using the =covoiturage= function. The inner function +will call =http-get=, but between the moment we have the response and +response body and the moment we return them, we add them to a web +cache. + +The function [[fn-with-cache-add-to-box]] atomically adds the response and +response-body to the cache, while ensuring there is a Date header. + +#+name: fn-with-cache-add-to-box +#+caption: Add a response and its body to the cache +#+begin_src scheme :eval no + (define (with-cache-add-to-box box key response body current-time) + (set! response (response-with-date response current-time)) + (let* ((old (atomic-box-ref box)) + (new (acons key (cons response body) + (filter + (lambda (cell) + (not (equal? key (car cell)))) + old))) + (discarded (atomic-box-compare-and-swap! box old new))) + (if (eq? discarded old) + (values response body) + (with-cache-add-to-box box key response body current-time)))) +#+end_src + +The function [[fn-with-cache-smart-http-get]] takes more argument than +regular =http-get=. It takes a base response and base response body, +which are the previously cached versions of the response. Then, it +proceeds with =http-get=. If the result is a 304 Not Modified +response, then it updates the cached response with the headers from +the new response and returns the previous response. Also, it updates +the cache just before returning. + +#+name: fn-with-cache-smart-http-get +#+caption: =http-get=, but compare with the previous cached responses +#+begin_src scheme :eval no + (define (with-cache-smart-http-get base-response base-response-body + box current-time http-get key . args) + (receive (response response-body) + (apply http-get key args) + (set! response (response-with-date response current-time)) + (when (and base-response (eq? (response-code response) 304)) + (set! response + (build-response + #:version (response-version base-response) + #:code (response-code base-response) + #:reason-phrase (response-reason-phrase base-response) + #:headers (merge-headers response base-response) + #:port (response-port response))) + (set! response-body base-response-body)) + (with-cache-add-to-box box key response response-body current-time))) +#+end_src + +We can now wrap =with-cache-smart-http-get= within covoiturage, and +focus on the main function. The main function takes the box, +current-time and smart http-get function within covoiturage, looks up +the response in the cache, and returns it as is if it is still +valid. Otherwise, the smart http-get is called. This is the job of +function [[fn-with-cache-query]]. + +#+name: fn-with-cache-query +#+caption: The main function for the cache +#+begin_src scheme :eval no + (define (with-cache-query box current-time smart-http-get http-get key . args) + (let* ((old (atomic-box-ref box)) + (resp (assoc-ref old key))) + (receive (request-headers other-request-args) + (filter-keyword #:headers args) + (unless request-headers + (set! request-headers '())) + (set! request-headers (filter-headers request-headers)) + (if resp + (let ((response (car resp)) + (response-body (cdr resp))) + (let ((expires (estimate-response-expires response))) + (if (and expires (date>=? expires current-time)) + ;; The response is cached and not expired + (values response response-body) + ;; The response is cached but expired + (let ((etag (response-etag response)) + (last-modified (response-last-modified response))) + (when etag + (set! request-headers + (acons 'if-none-match (list etag) request-headers))) + (when last-modified + (set! request-headers + (acons 'if-modified-since last-modified request-headers))) + (apply smart-http-get + response response-body box current-time http-get key + #:headers request-headers + other-request-args))))) + ;; The response is not cached + (apply smart-http-get + #f #f box current-time http-get key + #:headers request-headers + other-request-args))))) +#+end_src + +** Putting everything together +The final function is [[fn-with-cache]]. It takes its docstring from +[[with-cache-docstring]]. + +#+name: with-cache-docstring +#+caption: Docstring of the http-get with cache +#+begin_src texinfo + Call @var{http-get} with a cache. @var{http-get} is a procedure that + takes an URI, and optionally some headers and other arguments, and + either fails or returns two values: a response and a response body. + + The following headers are set by the caching function and are thus + ignored from the additional arguments: + + @itemize + @item @code{If-None-Match} + @item @code{If-Modified-Since} + @end itemize + + For cache validation without connecting to the server, + @var{current-time} is needed. It should be a SRFI-19 time or date. By + default, use the client system clock. + + The returned function takes the same arguments as @var{http-get} from + @code{(web client)}, and also returns a response and a response body, + however some headers are filtered, and responses with 304 will be + converted to 200 with the help of the cache, if there is some. + + The optional argument @code{#:box} can be used to use a specific + atomic box instead of one created just for the occasion. +#+end_src + +#+name: fn-with-cache +#+caption: HTTP GET with cache +#+begin_src scheme :eval no :noweb no-export :tangle web/client/with-cache.scm :mkdirp t + (define-module (web client with-cache) + #:use-module (web client) + #:use-module (web response) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-19) + #:use-module (srfi srfi-69) + #:use-module (ice-9 atomic) + #:use-module (ice-9 receive) + #:use-module (ice-9 optargs) + #:use-module (ice-9 threads) + #:use-module (ice-9 futures)) + + <<fn-covoiturage>> + + (define*-public (with-cache + #:key + (http-get http-get) + (current-time current-time) + (box #f)) + "\ + <<with-cache-docstring>>" + (let ((box (or box (make-atomic-box '())))) + <<fn-filter-keyword>> + <<fn-filter-headers>> + <<fn-date-add>> + <<fn-date-geqp>> + <<fn-response-with-date>> + <<fn-estimate-response-expires>> + <<fn-merge-headers>> + <<fn-with-cache-add-to-box>> + <<fn-with-cache-smart-http-get>> + (define smart-http-get + (covoiturage with-cache-smart-http-get)) + <<fn-with-cache-query>> + (lambda (uri . args) + (apply with-cache-query box (current-time) smart-http-get http-get uri args)))) +#+end_src + +* GNU Free Documentation License + :PROPERTIES: + :APPENDIX: t + :END: +#+texinfo: @include fdl.texi +* Index + :PROPERTIES: + :INDEX: cp + :END: |