offpunk now respects XDG

This commit is contained in:
Lionel Dricot 2022-01-18 18:04:29 +01:00
parent 9b8c06efce
commit bdd006c896
2 changed files with 45 additions and 46 deletions

View File

@ -55,6 +55,7 @@ experience or some other features. Python libraries requests, bs4 and readabliit
To avoid using unstable or too recent libraries, the rule of thumb is that a library should be packaged in Debian/Ubuntu.
* [Python-xdg](https://www.freedesktop.org/wiki/Software/pyxdg) will place your data, config and cache in place recommended by the XDG specs (usually its .local/share/offpunk, .config/offpunk and .cache/offpunk). Without it, look for ~/.offpunk or ~/.config/offpunk while the cache will be in ~/.cache/offpunk/.
* [Python-requests](http://python-requests.org) is needed to handle http/https requests natively (apt-get install python3-requests). Without it, http links will be opened in an external browser
* [BeautifulSoup4](https://www.crummy.com/software/BeautifulSoup) and [Readability](https://github.com/buriy/python-readability) are both needed to render HTML. Without them, HTML will not be rendered or be sent to an external parser like Lynx. (apt-get install python3-bs4 python3-readability or pip3 install readability-lxml)
* The [ansiwrap library](https://pypi.org/project/ansiwrap/) may result in
@ -88,14 +89,7 @@ commands upon start up. This can be used to make settings controlled with the
your RC file to visit a "homepage" automatically on startup, or to pre-prepare
a `tour` of your favourite Gemini sites or `offline` to go offline by default.
The RC file should be called `offpunkrc`. Offpunk will look for it first in
`~/.offpunk/` and second in `~/.config/offpunk/`. Note that either directory might
already exist even if you haven't created it manually, as Offpunk will, if
necessary, create the directory itself the first time you save a bookmark (the
bookmark file is saved in the same location). Offpunk will create
`~/.config/offpunk` only if `~/.config/` already exists on your system, otherwise
it will create `~/.offpunk/`.
The RC file should be called `offpunkrc` and goes in $XDG_CONFIG_DIR/offpunk (or .config/offpunk or .offpunk if xdg not available)
## Cache design

View File

@ -75,11 +75,32 @@ except ModuleNotFoundError:
_DO_HTML = False
_VERSION = "0.1~dev"
## Config directories
try:
import xdg
_CACHE_PATH = str(xdg.xdg_cache_home().resolve()) + "/offpunk/"
_CONFIG_DIR = str(xdg.xdg_config_home().resolve()) + "/offpunk"
_DATA_DIR = str(xdg.xdg_data_home().resolve()) + "/offpunk"
except ModuleNotFoundError:
_CACHE_PATH = "~/.cache/offpunk/"
## Look for pre-existing config directory, if any
for confdir in ("~/.offpunk/", "~/.config/offpunk/"):
confdir = os.path.expanduser(confdir)
if os.path.exists(confdir):
_CONFIG_DIR = confdir
break
## Otherwise, make one in .config if it exists
else:
_CONFIG_DIR = os.path.expanduser("~/.offpunk/")
_DATA_DIR = _CONFIG_DIR
for f in [_CONFIG_DIR, _CACHE_PATH, _DATA_DIR]:
if not os.path.exists(f):
print("Creating config directory {}".format(f))
os.makedirs(f)
_MAX_REDIRECTS = 5
_MAX_CACHE_SIZE = 10
_MAX_CACHE_AGE_SECS = 180
# TODO :use XDG spec for cache
_CACHE_PATH = "~/.cache/offpunk/"
#_DEFAULT_LESS = "less -EXFRfM -PMurl\ lines\ \%lt-\%lb/\%L\ \%Pb\%$ %s"
_DEFAULT_LESS = "less -EXFRfM %s"
@ -677,21 +698,6 @@ class GeminiClient(cmd.Cmd):
# type sensitivie information.
os.umask(0o077)
# Find config directory
## Look for something pre-existing
for confdir in ("~/.offpunk/", "~/.config/offpunk/"):
confdir = os.path.expanduser(confdir)
if os.path.exists(confdir):
self.config_dir = confdir
break
## Otherwise, make one in .config if it exists
else:
if os.path.exists(os.path.expanduser("~/.config/")):
self.config_dir = os.path.expanduser("~/.config/offpunk/")
else:
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" + "ON" + "\x1b[38;5;255m" + "> " + "\x1b[0m"
self.cert_prompt = "\x1b[38;5;202m" + "ON" + "\x1b[38;5;255m"
@ -715,8 +721,8 @@ class GeminiClient(cmd.Cmd):
self.visited_hosts = set()
self.offline_only = False
self.sync_only = False
self.tourfile = os.path.join(self.config_dir, "tour")
self.syncfile = os.path.join(self.config_dir, "to_fetch")
self.tourfile = os.path.join(_CONFIG_DIR, "tour")
self.syncfile = os.path.join(_CONFIG_DIR, "to_fetch")
self.client_certs = {
"active": None
@ -757,7 +763,7 @@ class GeminiClient(cmd.Cmd):
def _connect_to_tofu_db(self):
db_path = os.path.join(self.config_dir, "tofu.db")
db_path = os.path.join(_CONFIG_DIR, "tofu.db")
self.db_conn = sqlite3.connect(db_path)
self.db_cur = self.db_conn.cursor()
@ -1241,7 +1247,7 @@ you'll be able to transparently follow links to Gopherspace!""")
if _HAS_CRYPTOGRAPHY:
# Load the most frequently seen certificate to see if it has
# expired
certdir = os.path.join(self.config_dir, "cert_cache")
certdir = os.path.join(_CONFIG_DIR, "cert_cache")
with open(os.path.join(certdir, most_frequent_cert+".crt"), "rb") as fp:
previous_cert = fp.read()
previous_cert = x509.load_der_x509_certificate(previous_cert, _BACKEND)
@ -1280,7 +1286,7 @@ you'll be able to transparently follow links to Gopherspace!""")
VALUES (?, ?, ?, ?, ?, ?)""",
(host, address, fingerprint, now, now, 1))
self.db_conn.commit()
certdir = os.path.join(self.config_dir, "cert_cache")
certdir = os.path.join(_CONFIG_DIR, "cert_cache")
if not os.path.exists(certdir):
os.makedirs(certdir)
with open(os.path.join(certdir, fingerprint+".crt"), "wb") as fp:
@ -1383,7 +1389,7 @@ you'll be able to transparently follow links to Gopherspace!""")
Use `openssl` command to generate a new transient client certificate
with 24 hours of validity.
"""
certdir = os.path.join(self.config_dir, "transient_certs")
certdir = os.path.join(_CONFIG_DIR, "transient_certs")
name = str(uuid.uuid4())
self._generate_client_cert(certdir, name, transient=True)
self.active_is_transient = True
@ -1394,7 +1400,7 @@ you'll be able to transparently follow links to Gopherspace!""")
Interactively use `openssl` command to generate a new persistent client
certificate with one year of validity.
"""
certdir = os.path.join(self.config_dir, "client_certs")
certdir = os.path.join(_CONFIG_DIR, "client_certs")
print("What do you want to name this new certificate?")
print("Answering `mycert` will create `{0}/mycert.crt` and `{0}/mycert.key`".format(certdir))
name = input("> ")
@ -1424,7 +1430,7 @@ you'll be able to transparently follow links to Gopherspace!""")
Interactively select a previously generated client certificate and
activate it.
"""
certdir = os.path.join(self.config_dir, "client_certs")
certdir = os.path.join(_CONFIG_DIR, "client_certs")
certs = glob.glob(os.path.join(certdir, "*.crt"))
if len(certs) == 0:
print("There are no previously generated certificates.")
@ -1963,7 +1969,7 @@ Use 'ls -l' to see URLs."""
def do_add(self, line):
"""Add the current URL to the bookmarks menu.
Optionally, specify the new name for the bookmark."""
with open(os.path.join(self.config_dir, "bookmarks.gmi"), "a") as fp:
with open(os.path.join(_CONFIG_DIR, "bookmarks.gmi"), "a") as fp:
fp.write(self.gi.to_map_line(line))
def do_bookmarks(self, line):
@ -1971,7 +1977,7 @@ Optionally, specify the new name for the bookmark."""
'bookmarks' shows all bookmarks.
'bookmarks n' navigates immediately to item n in the bookmark menu.
Bookmarks are stored using the 'add' command."""
bm_file = os.path.join(self.config_dir, "bookmarks.gmi")
bm_file = os.path.join(_CONFIG_DIR, "bookmarks.gmi")
if not os.path.exists(bm_file):
print("You need to 'add' some bookmarks, first!")
return
@ -1989,7 +1995,7 @@ Bookmarks are stored using the 'add' command."""
self.default(line)
def list_add_line(self,line,list):
list_path = os.path.join(self.config_dir, "lists/%s.gmi"%list)
list_path = os.path.join(_DATA_DIR, "lists/%s.gmi"%list)
if not os.path.exists(list_path):
print("List %s does not exist. Create it with ""list create %s"""%(list,list))
return
@ -1999,26 +2005,25 @@ Bookmarks are stored using the 'add' command."""
l_file.close()
def list_rm_line(self,line,list=None):
list_path = os.path.join(self.config_dir, "lists/%s.gmi"%list)
list_path = os.path.join(_DATA_DIR, "lists/%s.gmi"%list)
print("removing %s from %s not yet implemented"%(line,list))
def list_show(self,list):
list_path = os.path.join(self.config_dir, "lists/%s.gmi"%list)
list_path = os.path.join(_DATA_DIR, "lists/%s.gmi"%list)
if not os.path.exists(list_path):
print("List %s does not exist. Create it with ""list create %s"""%(list,list))
else:
gi = GeminiItem("localhost:/" + list_path,list)
# We dont display bookmarks if accessing directly one
# or if in sync_only
self._go_to_gi(gi)
display = not ( args or self.sync_only)
self._handle_gemtext(gi, display = display)
self._go_to_gi(gi,handle=display)
if args:
# Use argument as a numeric index
self.default(line)
def list_create(self,list,title=None):
listdir = os.path.join(self.config_dir,"lists")
listdir = os.path.join(_DATA_DIR,"lists")
os.makedirs(listdir,exist_ok=True)
list_path = os.path.join(listdir, "%s.gmi"%list)
if not os.path.exists(list_path):
@ -2091,7 +2096,7 @@ current gemini browsing session."""
for cert in self.transient_certs_created:
for ext in (".crt", ".key"):
certfile = os.path.join(self.config_dir, "transient_certs", cert+ext)
certfile = os.path.join(_CONFIG_DIR, "transient_certs", cert+ext)
if os.path.exists(certfile):
os.remove(certfile)
print()
@ -2129,7 +2134,7 @@ def main():
gc = GeminiClient(restricted=args.restricted,synconly=args.sync)
# Process config file
rcfile = os.path.join(gc.config_dir, "offpunkrc")
rcfile = os.path.join(_CONFIG_DIR, "offpunkrc")
if os.path.exists(rcfile):
print("Using config %s" % rcfile)
with open(rcfile, "r") as fp:
@ -2172,7 +2177,7 @@ def main():
if args.sync:
# fetch_cache is the core of the sync algorithm.
# It takes as input :
# - a list of GeminiItems to be fetched
# - a GeminiItem to be fetched
# - depth : the degree of recursion to build the cache (0 means no recursion)
# - validity : the age, in seconds, existing caches need to have before
# being refreshed (0 = never refreshed if it already exists)
@ -2257,13 +2262,13 @@ def main():
if tot > 0:
print(" * * * %s to fetch from your offline browsing * * *" %tot)
for l in set(lines_lookup):
#always fetch the cache (we allows only a 3 minutes time
#always fetch the cache (we allows only a 10 minutes time
# to avoid multiple fetch in the same sync run)
#then add to tour
counter += 1
gitem = GeminiItem(l.strip())
if l.startswith("gemini://") or l.startswith("http"):
fetch_cache(gitem,depth=1,validity=180,\
fetch_cache(gitem,depth=1,validity=600,\
savetotour=False,count=[counter,tot])
add_to_tour(gitem)