From 700cba7184ec03ce53948326f8fe208446f2a524 Mon Sep 17 00:00:00 2001 From: Lionel Dricot Date: Thu, 30 Dec 2021 16:03:08 +0100 Subject: [PATCH] Introducing Offpunk --- README.md | 80 +++++++++++++++++++++++++------------------ av98.py => offpunk.py | 46 +++++++++++++------------ setup.py | 16 ++++----- 3 files changed, 79 insertions(+), 63 deletions(-) rename av98.py => offpunk.py (98%) diff --git a/README.md b/README.md index 0e11818..5285786 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,49 @@ -# AV-98 +# OFFPUNK + +A command-line, text-based and offline Gemini browser. + +Focused on Gemini first but with some Gopher/web support available or projected, the goal of Offpunk is to be able to synchronise your content once (a day, a week, a month) and then browse it while staying disconnected. + +Offpunk is a fork of the original [AV-98](https://tildegit.org/solderpunk/AV-98) by Solderpunk and was originally only called AV-98-offline as an experimental branch. + +## Lightning introduction + +You use the `go` command to visit a URL, e.g. `go gemini.circumlunar.space`. If xsel is installed, go will automatically fetch the URL from your clipboard. + +Links in Gemini documents are assigned numerical indices. Just type an index to +follow that link. + +If a Gemini document is too long to fit on your screen, use the `less` command +to pipe it to the `less` pager. + +Use `add` to add a capsule to your bookmarks and `bm` to show your bookmarks (which are stored in a file in you .config). + +Use `offline` to only browse cached content and `online` to go back online. + +Use the `help` command to learn about additional commands. + +When launched with the "--sync" option, offpunk will run non-interactively and fetch content from your bookmarks and content tentatively accessed while offline. New content found in your bookmarks will be added to your tour. + +With "--sync", one could specify a "--cache validity" in seconds. This option will not refresh content if a cache exists and is less than the specified amount of seconds old. + +For example, running + +`offpunk.py --sync --cache-validity 43200` + +will refresh your bookmarks if those are at least 12h old. + + + +## TODO + +Known issues in the code: +* WONTFIX: Sync is slow if you have bookmarks with lot of links that change very often. +* FIXME0: Certificates error are not handled in --sync +* FIXME1: consider root file is always index.gmi +* FIXME2: offline web browser use os.system because it’s the only one that understands the ">> file.txt" + +* TODO: Update blackbox to reflect cache hits. -## AV-98-offline -This is a fork of the original [AV-98](https://tildegit.org/solderpunk/AV-98) by Solderpunk. This fork is an experiment by Ploum (=> gemini://rawtext.club/~ploum ) to add offline capabilities to AV-98, including a persistent "tour" option. @@ -18,27 +60,10 @@ New ressources discovered in your bookmarks while --sync will be added to your t At the moment, caching only work for gemini:// ressources. gopher:// is not implemented and http(s):// ressources are sent to an "offline browser" (by default, None, nothing is done). It could be useful to, for example, send the http:// links to a text file in order to visit them while online. -Known issues in the code: -* WONTFIX: Sync is slow if you have bookmarks with lot of links that change very often. -* WONTFIX: Certificates error are not handled in --sync -* FIXME1: consider root file is always index.gmi -* FIXME2: offline web browser use os.system because it’s the only one that understands the ">> file.txt" - -* TODO: Update blackbox to reflect cache hits. - -## Original description - -AV-98 is an experimental client for the -[Gemini protocol](https://gemini.circumlunar.space). It is derived from the -[gopher client VF-1](https://github.com/solderpunk/VF-1) by the same author. -AV-98 is "experimental" in the sense that it may occasionally extend or deviate -from the official Gemini specification for the purposes of, well, -experimentation. Despite this, it is expected to be stable enough for regular -daily use at the same time. ## Dependencies -AV-98 has no "strict dependencies", i.e. it will run and work without anything +Offpunk has no "strict dependencies", i.e. it will run and work without anything else beyond the Python standard library. However, it will "opportunistically import" a few other libraries if they are available to offer an improved experience. @@ -48,6 +73,7 @@ experience. * The [cryptography library](https://pypi.org/project/cryptography/) will provide a better and slightly more secure experience when using the default TOFU certificate validation mode and is highly recommended. +* [Python magic](https://github.com/ahupp/python-magic/) is useful to determine the MIME type of cached object. If not present, the file extension will be used but some capsules provide wrong extension or no extension at all. ## Features @@ -61,18 +87,6 @@ experience. * IPv6 support * Supports any character encoding recognised by Python -## Lightning introduction - -You use the `go` command to visit a URL, e.g. `go gemini.circumlunar.space`. - -Links in Gemini documents are assigned numerical indices. Just type an index to -follow that link. - -If a Gemini document is too long to fit on your screen, use the `less` command -to pipe it to the `less` pager. - -Use the `help` command to learn about additional commands. - ## RC files You can use an RC file to automatically run any sequence of valid AV-98 diff --git a/av98.py b/offpunk.py similarity index 98% rename from av98.py rename to offpunk.py index cbf0101..619a767 100755 --- a/av98.py +++ b/offpunk.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 -# AV-98 Gemini client -# Dervied from VF-1 (https://github.com/solderpunk/VF-1), +# Offpunk Offline Gemini client +# Derived from AV-98 by Solderpunk, +# (C) 2021 Ploum # (C) 2019, 2020 Solderpunk # With contributions from: # - danceka @@ -66,7 +67,7 @@ _MAX_REDIRECTS = 5 _MAX_CACHE_SIZE = 10 _MAX_CACHE_AGE_SECS = 180 # TODO : use XDG spec for cache -_CACHE_PATH = "~/.cache/av98/" +_CACHE_PATH = "~/.cache/offpunk/" # Command abbreviations _ABBREVS = { @@ -309,7 +310,7 @@ class GeminiClient(cmd.Cmd): # Find config directory ## Look for something pre-existing - for confdir in ("~/.av98/", "~/.config/av98/"): + for confdir in ("~/.offpunk/", "~/.config/offpunk/"): confdir = os.path.expanduser(confdir) if os.path.exists(confdir): self.config_dir = confdir @@ -317,15 +318,15 @@ class GeminiClient(cmd.Cmd): ## Otherwise, make one in .config if it exists else: if os.path.exists(os.path.expanduser("~/.config/")): - self.config_dir = os.path.expanduser("~/.config/av98/") + self.config_dir = os.path.expanduser("~/.config/offpunk/") else: - self.config_dir = os.path.expanduser("~/.av98/") + self.config_dir = os.path.expanduser("~/.offpunk/") print("Creating config directory {}".format(self.config_dir)) os.makedirs(self.config_dir) - self.no_cert_prompt = "\x1b[38;5;76m" + "AV-98" + "\x1b[38;5;255m" + "> " + "\x1b[0m" - self.cert_prompt = "\x1b[38;5;202m" + "AV-98" + "\x1b[38;5;255m" - self.offline_prompt = "\x1b[38;5;76m" + "OFF-98" + "\x1b[38;5;255m" + "> " + "\x1b[0m" + self.no_cert_prompt = "\x1b[38;5;76m" + "ON" + "\x1b[38;5;255m" + "> " + "\x1b[0m" + self.cert_prompt = "\x1b[38;5;202m" + "ON" + "\x1b[38;5;255m" + self.offline_prompt = "\x1b[38;5;76m" + "OFF" + "\x1b[38;5;255m" + "> " + "\x1b[0m" self.prompt = self.no_cert_prompt self.gi = None self.history = [] @@ -401,7 +402,7 @@ class GeminiClient(cmd.Cmd): first_seen date, last_seen date, count integer)""") def _go_to_gi(self, gi, update_hist=True, check_cache=True, handle=True): - """This method might be considered "the heart of AV-98". + """This method might be considered "the heart of Offpunk". Everything involved in fetching a gemini resource happens here: sending the request over the network, parsing the response if its a menu, storing the response in a temporary file, choosing @@ -427,7 +428,7 @@ class GeminiClient(cmd.Cmd): return elif gi.scheme == "gopher" and not self.options.get("gopher_proxy", None)\ and not self.sync_only: - print("""AV-98 does not speak Gopher natively. + print("""Offpunk does not speak Gopher natively. However, you can use `set gopher_proxy hostname:port` to tell it about a Gopher-to-Gemini proxy (such as a running Agena instance), in which case you'll be able to transparently follow links to Gopherspace!""") @@ -1384,7 +1385,7 @@ you'll be able to transparently follow links to Gopherspace!""") print("Are you sure you don't want to pass the filename to the handler?") def do_abbrevs(self, *args): - """Print all AV-98 command abbreviations.""" + """Print all Offpunk command abbreviations.""" header = "Command Abbreviations:" self.stdout.write("\n{}\n".format(str(header))) if self.ruler: @@ -1394,19 +1395,20 @@ you'll be able to transparently follow links to Gopherspace!""") self.stdout.write("\n") def do_offline(self, *args): - """Use AV-98 offline by only accessing cached content""" + """Use Offpunk offline by only accessing cached content""" if self.offline_only: print("Offline and undisturbed.") else: self.offline_only = True self.prompt = self.offline_prompt - print("AV-98 is now offline and will only access cached content") + print("Offpunk is now offline and will only access cached content") def do_online(self, *args): + """Use Offpunk online with a direct connection""" if self.offline_only: self.offline_only = False self.prompt = self.no_cert_prompt - print("AV-98 is online and will access the network") + print("Offpunk is online and will access the network") else: print("Already online. Try offline.") @@ -1593,7 +1595,7 @@ Think of it like marks in vi: 'mark a'='ma' and 'go a'=''a'.""" def do_version(self, line): """Display version information.""" - print("AV-98 " + _VERSION) + print("Offpunk " + _VERSION) ### Stuff that modifies the lookup table def do_ls(self, line): @@ -1726,7 +1728,7 @@ Use 'ls -l' to see URLs.""" print("File %s already exists!" % filename) else: # Don't use _get_active_tmpfile() here, because we want to save the - # "source code" of menus, not the rendered view - this way AV-98 + # "source code" of menus, not the rendered view - this way Offpunk # can navigate to it later. shutil.copyfile(self.tmp_filename, filename) print("Saved to %s" % filename) @@ -1818,7 +1820,7 @@ current gemini browsing session.""" ### The end! def do_quit(self, *args): - """Exit AV-98.""" + """Exit Offpunk.""" # Close TOFU DB self.db_conn.commit() self.db_conn.close() @@ -1835,7 +1837,7 @@ current gemini browsing session.""" if os.path.exists(certfile): os.remove(certfile) print() - print("Thank you for flying AV-98!") + print("You can close your screen!") sys.exit() do_exit = do_quit @@ -1862,14 +1864,14 @@ def main(): # Handle --version if args.version: - print("AV-98 " + _VERSION) + print("Offpunk " + _VERSION) sys.exit() # Instantiate client gc = GeminiClient(restricted=args.restricted,synconly=args.sync) # Process config file - rcfile = os.path.join(gc.config_dir, "av98rc") + rcfile = os.path.join(gc.config_dir, "offpunkrc") if os.path.exists(rcfile): print("Using config %s" % rcfile) with open(rcfile, "r") as fp: @@ -1887,7 +1889,7 @@ def main(): # Say hi if not args.sync: - print("Welcome to AV-98!") + print("Welcome to Offpunk!") if args.restricted: print("Restricted mode engaged!") print("Enjoy your patrol through Geminispace...") diff --git a/setup.py b/setup.py index 104f2ec..2e06693 100755 --- a/setup.py +++ b/setup.py @@ -1,12 +1,12 @@ from setuptools import setup setup( - name='AV-98', - version='1.0.4dev', - description="Command line Gemini client.", - author="Solderpunk", - author_email="solderpunk@sdf.org", - url='https://tildegit.org/solderpunk/AV-98/', + name='offpunk', + version='0.1', + description="Offline Command line Gemini client forked from AV-98.", + author="Ploum", + author_email="offpunk@ploum.eu", + url='https://tildegit.org/ploum/AV-98-offline/', classifiers=[ 'License :: OSI Approved :: BSD License', 'Programming Language :: Python :: 3 :: Only', @@ -15,9 +15,9 @@ setup( 'Environment :: Console', 'Development Status :: 4 - Beta', ], - py_modules = ["av98"], + py_modules = ["offpunk"], entry_points={ - "console_scripts": ["av98=av98:main"] + "console_scripts": ["offpunk=offpunk:main"] }, install_requires=[], )