|
#!/usr/bin/python3
|
|
|
|
from glob import glob
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import sys
|
|
from time import sleep, time
|
|
|
|
from watchdog.observers import Observer
|
|
from watchdog.observers.api import EventQueue
|
|
from watchdog.events import PatternMatchingEventHandler
|
|
|
|
# ---------------- Configuration ---------------------------------------------
|
|
|
|
# Replace the RSYNC_TO string appropriately (and set up public key
|
|
# authentication for ssh), or set RSYNC_TO to None if you want to use scp
|
|
# instead.
|
|
RSYNC_TO = 'hx1234@staff.uni-due.de:/homes/hx1234/public_html/live'
|
|
|
|
# If you want to use scp to upload files, install the paramiko and scp packages
|
|
# and set the server address and directory on the server here. (You must create
|
|
# the directory on the server beforehand.)
|
|
SERVER = None # e.g., 'staff.uni-due.de'
|
|
UPLOAD_DIRECTORY = None # e.g., '/homes/hx1234/public_html/live'
|
|
SSH_USERNAME = None # e.g., 'hx1234'
|
|
|
|
# If you want to use scp to upload files, set up public key authentication for
|
|
# ssh (preferably - if an SSH agent is running, this should be used
|
|
# automatically by paramiko; if your private key is password protected and no
|
|
# SSH agent is running, you need to provide the password for the key here), or
|
|
# replace None in the next line by your password.
|
|
SSH_PASSWORD = None
|
|
|
|
# -----------------------------------------------------------------------------
|
|
|
|
if not RSYNC_TO:
|
|
# open ssh connection
|
|
from paramiko import SSHClient
|
|
from scp import SCPClient
|
|
|
|
ssh = SSHClient()
|
|
ssh.load_system_host_keys()
|
|
ssh.connect(SERVER, username=SSH_USERNAME, password=SSH_PASSWORD)
|
|
scp = SCPClient(ssh.get_transport())
|
|
|
|
# SOURCE_DIR will be watched for changes to *.xopp files.
|
|
SOURCE_DIR = sys.argv[1]
|
|
|
|
# In OUTPUT_DIR, a directory "png-source" will be created which will hold
|
|
# the png files exported by xournal++, and a directory "png-upload" will be
|
|
# created which holds the files that should be uploaded to the web server.
|
|
OUTPUT_DIR = sys.argv[2]
|
|
|
|
# The title to be displayed on the web page.
|
|
TITLE = sys.argv[3]
|
|
|
|
# List of files we uploaded already
|
|
uploaded_files = []
|
|
|
|
|
|
def md5sum_file(filename, blocksize=65536):
|
|
'''Computes the md5 hash of the given file and returns the first ten digits
|
|
of its hexadecimal presentation.'''
|
|
|
|
hash = hashlib.md5()
|
|
with open(filename, "rb") as f:
|
|
for block in iter(lambda: f.read(blocksize), b""):
|
|
hash.update(block)
|
|
return hash.hexdigest()[:10]
|
|
|
|
|
|
def upload_files():
|
|
slides = []
|
|
|
|
# Go through the new slides and add hash codes. This ensures that a slide
|
|
# gets a new name when changed. This ensures that only changed/new slides
|
|
# are uploaded to the web server and also avoids that the web page is not
|
|
# updated as it should because of caching.
|
|
|
|
for path in sorted(glob(os.path.join(OUTPUT_DIR, 'png-source', '*.png'))):
|
|
MD5SUM = md5sum_file(path)
|
|
fn = os.path.basename(path)
|
|
os.rename(
|
|
path,
|
|
os.path.join(OUTPUT_DIR, 'png-upload', '%s-%s.png' % (fn, MD5SUM, )))
|
|
slides.append((fn, MD5SUM, ))
|
|
|
|
# Compute a timestamp and store it together with the current list of slides.
|
|
# The javascript running on the web page can compare its own timestamp with
|
|
# the one stored in the current version of the timestamp.kson file in order
|
|
# to decide whether the slides need to be updated (which is the case when
|
|
# those two timestamps differ).
|
|
timestamp = hashlib.md5(
|
|
''.join(s[1] for s in slides).encode('utf-8')).hexdigest()
|
|
result = [timestamp, TITLE, ] + ["%s-%s.png" % s for s in slides]
|
|
with open(os.path.join(OUTPUT_DIR, 'png-upload', 'timestamp.json'), 'w') as f:
|
|
f.write(json.dumps(result))
|
|
# print(result)
|
|
|
|
# Upload the timestamp.json file and the new slide files to the web server
|
|
if RSYNC_TO:
|
|
os.system(
|
|
f'command rsync -cr -e ssh {OUTPUT_DIR}/png-upload/ {RSYNC_TO}')
|
|
else:
|
|
scp.put(
|
|
os.path.join(OUTPUT_DIR, 'png-upload', 'timestamp.json'),
|
|
remote_path=UPLOAD_DIRECTORY)
|
|
|
|
for s in result[2:]:
|
|
if s in uploaded_files:
|
|
continue
|
|
|
|
# print('Uploading', s)
|
|
scp.put(
|
|
os.path.join(OUTPUT_DIR, 'png-upload', s),
|
|
remote_path=UPLOAD_DIRECTORY)
|
|
uploaded_files.append(s)
|
|
|
|
|
|
class MyEventHandler(PatternMatchingEventHandler):
|
|
|
|
# (On my system at least, ) when a xournal file is "Saved as" (i.e., an
|
|
# on_created event happens), actually saving that file happens in several
|
|
# steps, so it also triggers an on_modified event shortly thereafter. It is
|
|
# therefore sufficient to listen to on_modified events only.
|
|
# def on_created(self, event):
|
|
# queue.put(event)
|
|
|
|
def on_modified(self, event):
|
|
queue.put(event)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
# Create directories if necessary
|
|
for d in ['png-source', 'png-upload', ]:
|
|
if not os.path.exists(os.path.join(OUTPUT_DIR, d)):
|
|
try:
|
|
os.mkdir(os.path.join(OUTPUT_DIR, d))
|
|
except:
|
|
print('Failed to create directory', os.path.join(OUTPUT_DIR, d))
|
|
sys.exit()
|
|
|
|
event_handler = MyEventHandler(patterns=['*.xopp', '.*.xopp', ])
|
|
queue = EventQueue()
|
|
observer = Observer()
|
|
observer.schedule(event_handler, SOURCE_DIR, recursive=True)
|
|
observer.start()
|
|
print("Watching for changes ...")
|
|
|
|
try:
|
|
while True:
|
|
# wait for 3 seconds between checking for file modification events
|
|
sleep(3)
|
|
|
|
if not queue.empty():
|
|
# (Auto-)saving a file triggers several file modification events
|
|
# within 50 milliseconds or so. Therefore we wait here for
|
|
# another second to ensure that we do not use a version of the
|
|
# file that does not have all changes that were to be saved.
|
|
sleep(1)
|
|
|
|
event = queue.get()
|
|
print('File modification', time(), event)
|
|
|
|
if event.is_directory:
|
|
continue
|
|
|
|
result = os.system('xournalpp --create-img=%s %s'
|
|
% (os.path.join(
|
|
OUTPUT_DIR,
|
|
'png-source',
|
|
'slide.png'),
|
|
event.src_path, ))
|
|
if result == 0:
|
|
upload_files()
|
|
else:
|
|
print("Conversion failed.")
|
|
break
|
|
finally:
|
|
observer.stop()
|
|
observer.join()
|
|
if not RSYNC_TO:
|
|
scp.close()
|