Examples

example.ini

; By default, configuration is read from ~/.gmcapsulerc. Use the -c option
; to specify some other configuration file.

[server]
;host = localhost
;address = 0.0.0.0
;port = 1965
;certs = .certs
;modules =
;threads = 5

[static]
root = .

[titan]
;upload_limit = 10485760

[cgi.booster]
protocol        = titan
host            = localhost
path            = /gemlog/* /plan.gmi
command         = /usr/bin/python3 ../booster/booster.py

;--------------------------------------------------------------------------

;[gitview]
;git             = /usr/bin/git
;cache_path      = /Users/jaakko/Library/Caches/gmgitview

[gitview.lagrange]
title           = Lagrange
brief           = A Beautiful Gemini Client
clone_url       = https://git.skyjake.fi/gemini/lagrange.git
tag_url         = https://git.skyjake.fi/gemini/lagrange/releases/tag/{tag}
path            = /Users/jaakko/src/lagrange
url_root        = lagrange
default_branch  = release

[gitview.gitview]
title           = GmGitView
brief           = Git Repository Viewer for Gemini
clone_url       = https://git.skyjake.fi/gemini/gitview.git
path            = /Users/jaakko/src/gmgitview
url_root        = gmgitview
default_branch  = main

Caching

class GitViewCache(Cache):
    """
    File-based cache that stores content using ``pickle``.

    File paths inside the cache directory are based on a SHA-256 hash
    of the original URL path.

    Args:
        hostname (str): Hostname that this cache belongs to.
        file_root (str): Directory where the cached content is stored.
            A large number of hash-prefix subdirectories are created.
    """
    def __init__(self, hostname, file_root):
        super().__init__()
        self.hostname = hostname
        self.file_root = file_root
        os.makedirs(file_root, exist_ok=True)
        self.ptn_static_path = \
            re.compile('.*/(tags|commits|patch|cdiff|pcdiff)/[0-9a-f]+')

    def storage_path(self, path):
        digest = hashlib.sha256(path.encode('utf-8')).hexdigest()
        return digest[0:2] + '/' + digest[2:]

    def max_age(self, path):
        if self.ptn_static_path.match(path):
            return 60 * 24 * 3600  # two months
        return 3600 # one hour for dynamic content

    def try_load(self, path):
        if not path.startswith(self.hostname + '/'):
            return None, None
        storage_path = pjoin(self.file_root, self.storage_path(path))
        if not os.path.exists(storage_path):
            return None, None
        now_time = time.time()
        storage_time = os.path.getmtime(storage_path)
        if now_time - storage_time > self.max_age(path):
            return None, None
        return pickle.load(open(storage_path, 'rb'))

    def save(self, path, media_type, content):
        if not path.startswith(self.hostname + '/'):
            return False
        storage_path = pjoin(self.file_root, self.storage_path(path))
        os.makedirs(os.path.dirname(storage_path), exist_ok=True)
        pickle.dump((media_type, content), open(storage_path, 'wb'))
        return True