Blob Blame History Raw
#!/usr/bin/python3

# to forece reindex pass a single argument:
#   force

# to 'hide' files
# NB: file still will be accessible for ALL who know a mangled url
#   place file into '.hide' subdirectory

# to set user thumbs:
#   path.thumb.png      - user thumb for 'path'
#   path/.thumb.png     - user thumb for 'path'
#   path/.thumb..png    - default thumb for unknown extension
#   path/.thumb...png   - default thumb for subdirectories
#   path/.thumb.ext.png - default thumb for 'ext'
#   path/.thumb/.png    - default thumb for unknown extension
#   path/.thumb/..png   - default thumb for subdirectories
#   path/.thumb/ext.png - default thumb for 'ext'


import os
import sys
import time
import base64
import hashlib

import config
import thumb
import template


thumbext = [ 'png', 'svg', 'jpg' ]
skipname = [ 'index.html', 'indexd.html' ]


def join(*args): return '/'.join(a for a in args if a)
def isdir(path): return not os.path.islink(path) and os.path.isdir(path)
def isfile(path): return not os.path.islink(path) and os.path.isfile(path)

def nameext(name):
    name = os.path.basename(name)
    i = name.rfind('.')
    return (name[:i], name[i+1:].lower()) if i >= 0 else (name, '')
def remove(path):
    if os.path.isfile(path) or os.path.islink(path):
        print('remove file:', path)
        os.remove(path)
    if os.path.isdir(path):
        for f in os.listdir(path): remove(join(path, f))
        print('remove dir:', path)
        os.rmdir(path)
def mkdirfor(path):
    os.makedirs(os.path.dirname(path), exist_ok = True)
def touch(path, msg = True):
    if msg: print('create file:', path)
    assert(not path in files)
    mkdirfor(path)
    files.add(path)
    return path
def link(src, dst):
    touch(dst, False)
    if not force and isfile(dst) and os.path.samefile(src, dst): return
    remove(dst)
    print('create link:', dst, '(' + src + ')')
    os.link(src, dst, follow_symlinks = False)
def clean(path, root = True):
    if os.path.islink(path) or (os.path.isfile(path) and not path in files):
        print('remove file:', path)
        os.remove(path)
    elif os.path.isdir(path):
        for f in os.listdir(path): clean(join(path, f), False)
        if not root and not os.listdir(path):
            print('remove dir:', path)
            os.rmdir(path)
def mangle(path):
    return base64.b64encode(hashlib.sha256((config.salt + ':' + path).encode()).digest()).decode()[:20].replace('/', '_').replace('+', '-')


class Node:
    def __init__(self, parent, src, name, hide = False):
        #print('node:', src)
        self.parent = parent
        self.src = src
        self.name = name
        self.title = name if parent else config.title
        self.isdir = isdir(src)
        self.ext = '' if self.isdir else nameext(name)[1]
        self.path = join(parent.path, name) if parent else name
        self.date = os.path.getmtime(src)
        self.mangle = config.mangle + mangle(self.path) + '.' + self.ext
        self.hide = hide or (parent and parent.hide)
        self.url = join(config.url, self.path)
        self.mangleurl = join(config.url, self.mangle)
        self.hideurl = join(config.hideurl, self.path) if self.isdir else (self.mangleurl if self.hide else self.url)
        self.thumburl = None
        self.thumbs = parent.thumbs if parent else {}
        self.subs = {}
        if parent: parent.touchdate(self.date); parent.subs[name] = self

    def touchdate(self, date):
        if self.date >= date: return
        self.date = date
        if self.parent: self.parent.touchdate(date)

    def scan(self):
        if not self.isdir: return

        td = join(self.src, '.thumb')
        if isdir(td):
            for f in os.listdir(td):
                n, e = nameext(f); fn = join(td, f)
                if e in thumbext and isfile(fn): self.thumbs[None if n == '.' else n] = fn

        for f in os.listdir(self.src):
            fn = join(self.src, f)
            if f[:1] != '.' or not isfile(fn): continue
            n, e = nameext(f[1:])
            if not e in thumbext: continue
            n, e = nameext(n)
            if n == 'thumb': self.thumbs[e] = fn
            if n == 'thumb.': self.thumbs[None] = fn

        for f in os.listdir(self.src): self.sub(join(self.src, f), f, False)
        hd = join(self.src, '.hide')
        if isdir(hd):
            for f in os.listdir(hd): self.sub(join(hd, f), f, True)

    def sub(self, src, name, hide):
        n, e = nameext(name)
        if ( self.isdir
          and name
          and name not in self.subs
          and name[0] != '.'
          and not name.lower() in skipname
          and (self.parent or not name.lower().startswith(config.mangle))
          and nameext(n)[1] != 'thumb'
          and (isfile(src) or isdir(src)) ):
            Node(self, src, name, hide).scan()

    def makefile(self):
        if self.isdir: return
        link(self.src, join(config.dst, self.mangle))
        if not self.hide: link(self.src, join(config.dst, self.path))

    def makethumb(self):
        if self.hide and not config.hidedst: return
        ts = None
        td = None
        dst = config.hidedst if self.hide else config.dst
        url = config.hideurl if self.hide else config.url
        if self.isdir:
            dst = join(dst, self.path, '.thumb.')
            url = join(url, self.path, '.thumb.')
        else:
            dst = join(dst, self.path + '.thumb.')
            url = join(url, self.path + '.thumb.')

        for e in reversed(thumbext):
            if isfile(join(self.src, '.thumb.' + e)): ts = join(self.src, '.thumb.' + e)
        for e in reversed(thumbext):
            if isfile(self.src +  '.thumb.' + e): ts = self.src +  '.thumb.' + e

        if not ts and not self.isdir:
            self.path + '.thumb.'
            if not force:
                for e in reversed(thumbext):
                    if isfile(dst + e) and os.path.getmtime(dst + e) > self.date: td = dst + e
            if td: touch(td, False)
            else:
                mkdirfor(dst + thumbext[0])
                td = thumb.make(self.src, dst + thumbext[0]);
                if td: touch(td)

        if not ts and not td:
            if self.isdir: ts = self.thumbs.get(None)
            else: ts = self.thumbs.get(self.ext, self.thumbs.get(''))
        if not td and ts:
            td = dst + nameext(ts)[1]
            link(ts, td)
        if td: self.thumburl = url + nameext(td)[1]

    def makeinfo(self):
        return {
            'name':      self.name,
            'title':     self.title,
            'date':      time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(self.date)),
            'isdir':     self.isdir,
            'hide':      self.hide,
            'url':       self.url,
            'hideurl':   self.hideurl,
            'mangleurl': self.mangleurl,
            'thumburl':  self.thumburl }

    def makepath(self):
        r = []; p = ''; n = self
        while n: r.insert(0, {'name': n.name, 'title': n.title, 'url': p}); p = join(p, '..'); n = n.parent
        return r

    def makeinfoex(self, bydate):
        r = self.makeinfo()
        r['bydate'] = bydate
        r['path'] = self.makepath()
        r['tw'] = config.thumbw
        r['th'] = config.thumbh
        return r

    def makeindex(self, bydate):
        if not self.isdir or self.hide: return
        fn = join(config.dst, self.path, ('indexd.html' if bydate else 'index.html'))
        if not force and isfile(fn) and os.path.getmtime(fn) > self.date: touch(fn, False); return
        r = self.makeinfoex(bydate)
        r['subs'] = []
        for n in sorted(self.subs.values(), key=lambda n: (not n.isdir, (-n.date if bydate else n.name))):
            if not n.hide: r['subs'].append(n.makeinfo())
        with open(touch(fn), 'w') as f: tpl.write(f, r)

    def makehideindex(self, bydate):
        if not self.isdir or not config.hidedst: return
        fn = join(config.hidedst, self.path, ('indexd.html' if bydate else 'index.html'))
        if not force and isfile(fn) and os.path.getmtime(fn) > self.date: touch(fn, False); return
        r = self.makeinfoex(bydate)
        r['subs'] = [ n.makeinfo() for n in sorted(self.subs.values(), key=lambda n: (not n.isdir, (-n.date if bydate else n.name), not n.hide)) ]
        with open(touch(fn), 'w') as f: hidetpl.write(f, r)

    def make(self):
        #print('make:', self.path)
        for n in self.subs.values(): n.make()
        self.makefile()
        self.makethumb()
        self.makeindex(False)
        self.makeindex(True)
        self.makehideindex(False)
        self.makehideindex(True)


#os.umask(0o0027)
force = len(sys.argv) > 1 and sys.argv[1] == 'force'
files = set()

tpl = template.TplLoader.load("tpl/index.tpl")
hidetpl = template.TplLoader.load("tpl/hide.tpl")

n = Node(None, config.src, '')
n.scan()
if time.time() - n.date <= config.guardsecs:
    print('files are being update just now')
    exit(0)
n.make()

clean(config.dst)
if config.hidedst: clean(config.hidedst)

#print('done')