diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..28971c5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +[*] +end_of_line = lf +insert_final_newline = true + +[*.{js,py,html}] +indent_style = space +indent_size = 4 +charset = utf-8 + +[Makefile] +indent_style = tab + diff --git a/ctl b/ctl index 805595c..4c05511 100755 --- a/ctl +++ b/ctl @@ -162,12 +162,14 @@ if [ "$1" == "open" ]; then xdg-open "file://${BASE}/openmedialibrary/static/html/load.html" fi else - $PYTHON "${NAME}/oml/gtkstatus.py" $@ - exit $? + #$PYTHON "${NAME}/oml/gtkstatus.py" $@ + #exit $? + "$0" start & fi else - $PYTHON "$NAME/oml/gtkstatus.py" $@ - exit $? + #$PYTHON "$NAME/oml/gtkstatus.py" $@ + #exit $? + "$0" start & fi fi exit 0 @@ -187,7 +189,7 @@ fi if [ "$1" == "ui" ]; then shift - $PYTHON "$NAME/oml/ui.py" $@ + $PYTHON "$NAME/oml/ui.py" "$@" exit $? fi if [ "$1" == "init" ]; then diff --git a/oml/__main__.py b/oml/__main__.py index f888dd5..f5e2b5e 100644 --- a/oml/__main__.py +++ b/oml/__main__.py @@ -1,6 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import os import sys diff --git a/oml/api.py b/oml/api.py index 32b31bc..bf44566 100644 --- a/oml/api.py +++ b/oml/api.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 from os.path import normpath, dirname, abspath, join diff --git a/oml/changelog.py b/oml/changelog.py index 483a708..c16834c 100644 --- a/oml/changelog.py +++ b/oml/changelog.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import os from datetime import datetime diff --git a/oml/commands.py b/oml/commands.py index 7e0a05d..d147b2e 100644 --- a/oml/commands.py +++ b/oml/commands.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 from os.path import join, exists, dirname diff --git a/oml/downloads.py b/oml/downloads.py index 8374f95..1835ec1 100644 --- a/oml/downloads.py +++ b/oml/downloads.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import os from threading import Thread diff --git a/oml/gtkstatus.py b/oml/gtkstatus.py index 726afc5..4dc3ce5 100644 --- a/oml/gtkstatus.py +++ b/oml/gtkstatus.py @@ -9,39 +9,48 @@ import webbrowser import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GLib -try: - gi.require_version('AppIndicator3', '0.1') - from gi.repository import AppIndicator3 as appindicator -except: - appindicator = None - base = dirname(dirname(dirname(abspath(__file__)))) -icon = os.path.join(base, 'openmedialibrary/static/png/oml.png') +icon = os.path.join(base, 'openmedialibrary/static/svg/oml.svg') title = "Open Media Library" ctl = base + '/ctl' -class OMLIcon: +def check_pid(pid): + if not os.path.exists(pid): + return False + try: + with open(pid, 'r') as fd: + pid = int(fd.read().strip()) + os.kill(pid, 0) + except OSError: + return False + else: + return True + +def preexec(): # Don't forward signals. + os.setpgrp() + + +class OMLStatus: menu = None icon = None - indicator = None def __init__(self, autostart=False): self.autostart = autostart self.create_pid() - if appindicator: - self.indicator = appindicator.Indicator.new("OML", icon, - appindicator.IndicatorCategory.APPLICATION_STATUS) - self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE) - self.menu = self.get_menu() - self.indicator.set_menu(self.menu) - else: - self.icon = Gtk.StatusIcon() - self.icon.set_from_file(icon) - self.icon.set_title(title) - self.icon.connect("activate", self._click) - self.icon.connect("popup-menu", self._click) + self.win = Gtk.Window() + self.win.set_icon_from_file(icon) + self.win.set_title(title) + #self.win.show_all() + self.win.iconify() + ''' + self.icon = Gtk.StatusIcon() + self.icon.set_from_file(icon) + self.icon.set_title(title) + self.icon.connect("activate", self._click) + self.icon.connect("popup-menu", self._click) + ''' subprocess.Popen([ctl, 'start'], close_fds=True, preexec_fn=preexec) if not self.autostart: GLib.timeout_add_seconds(1, self._open, None) @@ -81,7 +90,7 @@ class OMLIcon: @classmethod def is_running(cls): pid = cls.get_pid() - if pid and os.path.exists(pid): + if pid and check_pid(pid): return True else: return False @@ -140,16 +149,14 @@ class OMLIcon: url += '#%s' % port webbrowser.open_new_tab(url) -def preexec(): # Don't forward signals. - os.setpgrp() if __name__ == '__main__': import signal autostart = len(sys.argv) > 1 and sys.argv[1] == '--autostart' - if OMLIcon.is_running(): - OMLIcon.load() + if OMLStatus.is_running(): + OMLStatus.load() else: - oml = OMLIcon(autostart) + oml = OMLStatus(autostart) main_loop = GLib.MainLoop() try: main_loop.run() diff --git a/oml/item/api.py b/oml/item/api.py index c94d7cf..d0c04a5 100644 --- a/oml/item/api.py +++ b/oml/item/api.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import json import hashlib import os diff --git a/oml/item/handlers.py b/oml/item/handlers.py index b13c9f4..19d6460 100644 --- a/oml/item/handlers.py +++ b/oml/item/handlers.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 from datetime import datetime @@ -7,6 +6,7 @@ import mimetypes import os from urllib.request import quote import zipfile +import base64 import ox @@ -27,7 +27,42 @@ import state import logging logger = logging.getLogger(__name__) -class OMLHandler(tornado.web.RequestHandler): + +class OptionalBasicAuthMixin(object): + class SendChallenge(Exception): + pass + + def prepare(self): + if settings.preferences.get('authentication'): + try: + self.authenticate_user() + except self.SendChallenge: + self.send_auth_challenge() + + def send_auth_challenge(self): + realm = "Open Media Library" + hdr = 'Basic realm="%s"' % realm + self.set_status(401) + self.set_header('www-authenticate', hdr) + self.finish() + return False + + def authenticate_user(self): + auth_header = self.request.headers.get('Authorization') + if not auth_header or not auth_header.startswith('Basic '): + raise self.SendChallenge() + + auth_data = auth_header.split(None, 1)[-1] + auth_data = base64.b64decode(auth_data).decode('ascii') + username, password = auth_data.split(':', 1) + + auth = settings.preferences.get('authentication') + if auth.get('username') == username and auth.get('password') == password: + self._current_user = username + else: + raise self.SendChallenge() + +class OMLHandler(OptionalBasicAuthMixin, tornado.web.RequestHandler): def initialize(self): pass @@ -141,7 +176,7 @@ class ReaderHandler(OMLHandler): path = os.path.join(settings.static_path, html) return serve_static(self, path, 'text/html') -class UploadHandler(tornado.web.RequestHandler): +class UploadHandler(OMLHandler): def initialize(self, context=None): self._context = context diff --git a/oml/item/icons.py b/oml/item/icons.py index 7d96d39..37d89fb 100644 --- a/oml/item/icons.py +++ b/oml/item/icons.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import os import sqlite3 @@ -23,23 +22,32 @@ logger = logging.getLogger(__name__) MAX_WORKERS = 4 - class Icons(dict): + def __init__(self, db): self._db = db self.create() + def is_available(self): + folder = os.path.dirname(self._db) + if os.path.exists(folder): + if not os.path.exists(self._db): + self.create() + return os.path.exists(self._db) + def connect(self): conn = sqlite3.connect(self._db, timeout=90) return conn def create(self): - conn = self.connect() - c = conn.cursor() - c.execute('CREATE TABLE IF NOT EXISTS icon (id varchar(64) unique, data blob)') - c.execute('CREATE TABLE IF NOT EXISTS setting (key varchar(256) unique, value text)') - if int(self.get_setting(c, 'version', 0)) < 1: - self.set_setting(c, 'version', 1) + folder = os.path.dirname(self._db) + if os.path.exists(folder): + conn = self.connect() + c = conn.cursor() + c.execute('CREATE TABLE IF NOT EXISTS icon (id varchar(64) unique, data blob)') + c.execute('CREATE TABLE IF NOT EXISTS setting (key varchar(256) unique, value text)') + if int(self.get_setting(c, 'version', 0)) < 1: + self.set_setting(c, 'version', 1) def get_setting(self, c, key, default=None): c.execute('SELECT value FROM setting WHERE key = ?', (key, )) @@ -105,7 +113,7 @@ class Icons(dict): c.close() conn.close() except: - logger.debug('failed to clear icon %s', id) + logger.debug('failed to clear icon %s', prefix) def vacuum(self, ids): conn = self.connect() @@ -140,6 +148,8 @@ def get_icons_db_path(): return icons_db_path def get_icon_sync(id, type_, size): + if not icons.is_available(): + return '' if size: skey = '%s:%s:%s' % (type_, id, size) data = icons[skey] diff --git a/oml/item/models.py b/oml/item/models.py index b2d5c3a..cfc44e3 100644 --- a/oml/item/models.py +++ b/oml/item/models.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 from datetime import datetime import base64 import hashlib diff --git a/oml/item/person.py b/oml/item/person.py index 4e68205..27ad8f7 100644 --- a/oml/item/person.py +++ b/oml/item/person.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import unicodedata diff --git a/oml/item/person_api.py b/oml/item/person_api.py index e6cb8dc..1293f34 100644 --- a/oml/item/person_api.py +++ b/oml/item/person_api.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import unicodedata from oxtornado import actions diff --git a/oml/item/query.py b/oml/item/query.py index b48fe29..9683dee 100644 --- a/oml/item/query.py +++ b/oml/item/query.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 #does not work in sqlite diff --git a/oml/item/scan.py b/oml/item/scan.py index 04a58c9..4869ac9 100644 --- a/oml/item/scan.py +++ b/oml/item/scan.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 from datetime import datetime diff --git a/oml/item/title_api.py b/oml/item/title_api.py index b34a70c..22fb3b5 100644 --- a/oml/item/title_api.py +++ b/oml/item/title_api.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import hashlib import json import unicodedata diff --git a/oml/library.py b/oml/library.py index 510e7d2..c80fc4d 100644 --- a/oml/library.py +++ b/oml/library.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import json import os import time diff --git a/oml/localnodes.py b/oml/localnodes.py index 5e7129b..f3fa7ec 100644 --- a/oml/localnodes.py +++ b/oml/localnodes.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import socket diff --git a/oml/media/__init__.py b/oml/media/__init__.py index 5cebfcf..dd9d32b 100644 --- a/oml/media/__init__.py +++ b/oml/media/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import base64 diff --git a/oml/media/cbr.py b/oml/media/cbr.py index 9f72a71..66e1324 100644 --- a/oml/media/cbr.py +++ b/oml/media/cbr.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import os diff --git a/oml/media/epub.py b/oml/media/epub.py index 81ccb17..cd5a6f0 100644 --- a/oml/media/epub.py +++ b/oml/media/epub.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import os diff --git a/oml/media/opf.py b/oml/media/opf.py index b8a20b8..8070c40 100644 --- a/oml/media/opf.py +++ b/oml/media/opf.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import xml.etree.ElementTree as ET diff --git a/oml/media/pdf.py b/oml/media/pdf.py index c4e9719..bc342d0 100644 --- a/oml/media/pdf.py +++ b/oml/media/pdf.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import sys diff --git a/oml/media/txt.py b/oml/media/txt.py index 193441c..ca76253 100644 --- a/oml/media/txt.py +++ b/oml/media/txt.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import os diff --git a/oml/meta/__init__.py b/oml/meta/__init__.py index 45c3d3e..13209c0 100644 --- a/oml/meta/__init__.py +++ b/oml/meta/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import stdnum.isbn diff --git a/oml/meta/duckduckgo.py b/oml/meta/duckduckgo.py index 105426b..b3c28e6 100644 --- a/oml/meta/duckduckgo.py +++ b/oml/meta/duckduckgo.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import ox.web.duckduckgo diff --git a/oml/meta/google.py b/oml/meta/google.py index 4b628bf..713647d 100644 --- a/oml/meta/google.py +++ b/oml/meta/google.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 from time import time, sleep from urllib.parse import urlencode diff --git a/oml/meta/utils.py b/oml/meta/utils.py index 49f0703..741fce8 100644 --- a/oml/meta/utils.py +++ b/oml/meta/utils.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import re diff --git a/oml/node/cert.py b/oml/node/cert.py index 64cb741..8b7e5d1 100644 --- a/oml/node/cert.py +++ b/oml/node/cert.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import hashlib import os diff --git a/oml/node/nodeapi.py b/oml/node/nodeapi.py index a3f16d5..93c0d49 100644 --- a/oml/node/nodeapi.py +++ b/oml/node/nodeapi.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 from user.models import User @@ -82,9 +81,9 @@ def api_upload(user_id, items): from user.models import List peer = User.get(user_id) if peer: - l = List.get_or_create(':Public') + l = List.get_or_create(':Inbox') if l: - logger.debug('%s added items to public folder: %s', user_id, items) + logger.debug('%s added items to inbox: %s', user_id, items) l.add_items(items) trigger_event('change', {}) return True diff --git a/oml/node/server.py b/oml/node/server.py index 4673441..0e5694e 100644 --- a/oml/node/server.py +++ b/oml/node/server.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 from socketserver import ThreadingMixIn from threading import Thread import base64 diff --git a/oml/nodes.py b/oml/nodes.py index 70fae05..f9b3ff2 100644 --- a/oml/nodes.py +++ b/oml/nodes.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 from queue import Queue @@ -462,7 +461,7 @@ class Node(Thread): return False def upload(self, items): - logger.debug('add items to %s\'s public folder: %s', self.id, items) + logger.debug('add items to %s\'s inbox: %s', self.user_id, items) r = self.request('upload', items) return bool(r) diff --git a/oml/oxtornado.py b/oml/oxtornado.py index 7215e9a..b34cdfe 100644 --- a/oml/oxtornado.py +++ b/oml/oxtornado.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 from contextlib import contextmanager diff --git a/oml/pdict.py b/oml/pdict.py index a648d24..cf1d37b 100644 --- a/oml/pdict.py +++ b/oml/pdict.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import os import json diff --git a/oml/queryparser.py b/oml/queryparser.py index 5ccc68a..033e068 100644 --- a/oml/queryparser.py +++ b/oml/queryparser.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 from datetime import datetime import unicodedata diff --git a/oml/server.py b/oml/server.py index f98ed7b..b01d598 100644 --- a/oml/server.py +++ b/oml/server.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import os import sys diff --git a/oml/settings.py b/oml/settings.py index 631b398..f70b930 100644 --- a/oml/settings.py +++ b/oml/settings.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import json import os diff --git a/oml/setup.py b/oml/setup.py index 85c2bae..b243a00 100644 --- a/oml/setup.py +++ b/oml/setup.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import os diff --git a/oml/ssl_request.py b/oml/ssl_request.py index 538ea75..43b4fb3 100644 --- a/oml/ssl_request.py +++ b/oml/ssl_request.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import ssl import http.client diff --git a/oml/tasks.py b/oml/tasks.py index af02c3c..3f97379 100644 --- a/oml/tasks.py +++ b/oml/tasks.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 from queue import PriorityQueue from threading import Thread import json diff --git a/oml/tor_request.py b/oml/tor_request.py index 7b10063..0fd6455 100644 --- a/oml/tor_request.py +++ b/oml/tor_request.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import ssl import http.client diff --git a/oml/ui.py b/oml/ui.py index a15dfac..5b9dc26 100644 --- a/oml/ui.py +++ b/oml/ui.py @@ -1,5 +1,4 @@ # encoding: utf-8 -# vi:si:et:sw=4:sts=4:ts=4 import sys import os try: @@ -105,7 +104,8 @@ if __name__ == '__main__': if len(sys.argv) >= 3: base = sys.argv[2] base = os.path.expanduser(base) - os.chdir(base) + if os.path.exists(base): + os.chdir(base) if len(sys.argv) >= 2 and sys.argv[1] == 'folder': print(ui.selectFolder({})) else: diff --git a/oml/ui_websocket.py b/oml/ui_websocket.py index 7efdddd..3bd5204 100644 --- a/oml/ui_websocket.py +++ b/oml/ui_websocket.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 import json diff --git a/oml/update.py b/oml/update.py index 8ab472c..83c490a 100644 --- a/oml/update.py +++ b/oml/update.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 from contextlib import closing import base64 diff --git a/oml/user/api.py b/oml/user/api.py index d8fecfa..7f138c8 100644 --- a/oml/user/api.py +++ b/oml/user/api.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 from copy import deepcopy @@ -189,7 +188,7 @@ def getLists(data): 'type': 'libraries', 'user': None, }) - List.get_or_create(':Public') + List.get_or_create(':Inbox') for u in models.User.query.filter((models.User.peered==True)|(models.User.id==settings.USER_ID)): lists += u.lists_json() return { @@ -290,7 +289,7 @@ def removeList(data): } ''' l = models.List.get(data['id']) - if l and l.name != 'Public': + if l and l.name != 'Inbox': l.remove() return {} actions.register(removeList, cache=False) @@ -309,12 +308,10 @@ def addListItems(data): i = Item.get(item_id) i.queue_download() i.update() - elif data['list'] and (data['list'].startswith(':') or - data['list'].endswith(':Public') or - data['list'].enswtih(':')): + elif data['list'] and (data['list'].startswith(':') or data['list'].endswith(':')): l = models.List.get_or_create(data['list']) if l: - if l.name in ('Public', '') and l.user_id != settings.USER_ID: + if l.name in ('Inbox', '') and l.user_id != settings.USER_ID: state.tasks.queue('upload', { 'user': l.user_id, 'items': data['items'] @@ -351,12 +348,11 @@ def sortLists(data): ''' n = 0 logger.debug('sortLists %s', data) - lists = ['Public'] for id in data['ids']: l = models.List.get(id) l.index_ = n n += 1 - if l.type == 'static': + if l.type == 'static' and l.name not in ('', 'Inbox'): lists.append(l.name) state.db.session.add(l) state.db.session.commit() diff --git a/oml/user/models.py b/oml/user/models.py index ad75f0d..f90cf89 100644 --- a/oml/user/models.py +++ b/oml/user/models.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 from datetime import datetime import json import os @@ -218,7 +217,7 @@ class User(db.Model): Changelog.record(self, 'edititem', item.id, item.meta, _commit=False) lists = [] for l in List.query.filter_by(user_id=self.id, type='static').order_by('index_'): - if l.name: + if l.name and l.name != 'Inbox': lists.append(l.name) Changelog.record(self, 'addlist', l.name, _commit=False) items = [i.id for i in l.get_items().options(load_only('id'))] @@ -301,7 +300,7 @@ class List(db.Model): state.db.session.add(l) state.db.session.commit() if user_id == settings.USER_ID: - if l.type == 'static' and name != '': + if l.type == 'static' and name != '' and name != 'Inbox': add_record('addlist', l.name) return l diff --git a/oml/utils.py b/oml/utils.py index e5b9337..5130156 100644 --- a/oml/utils.py +++ b/oml/utils.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 from datetime import datetime diff --git a/oml/websocket.py b/oml/websocket.py index 90821bb..27a3d4e 100644 --- a/oml/websocket.py +++ b/oml/websocket.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# vi:si:et:sw=4:sts=4:ts=4 from tornado.websocket import WebSocketHandler diff --git a/static/js/annotation.js b/static/js/annotation.js index f524b34..8003e73 100644 --- a/static/js/annotation.js +++ b/static/js/annotation.js @@ -1,34 +1,56 @@ 'use strict'; -oml.ui.annotation = function(data, $iframe) { +oml.ui.annotation = function(annotation, $iframe) { var $quote = Ox.Element().addClass('OxSelectable OMLQuote').css({ + backgroundColor: 'white', color: 'black', fontFamily: 'Georgia, Palatino, DejaVu Serif, Book Antiqua, Palatino Linotype, Times New Roman, serif', fontSize: '14px', lineHeight: '21px', padding: '8px' - }).html(Ox.encodeHTMLEntities(data.text).replace(/\n/g, '
')).on({ + }).html(Ox.encodeHTMLEntities(annotation.text).replace(/\n/g, '
')).on({ click: function(event) { that.select() $iframe.postMessage('selectAnnotation', { - id: data.id + id: annotation.id }) } }) - var $note = Ox.Editable({ + var $note = Ox.ArrayEditable({ editing: true, + items: (annotation.comments || []).map(function(comment) { + comment.editable = true + return comment + }), type: 'textarea' }).css({ margin: '2px', minHeight: '12px' + }).bindEvent({ + submit: function(data) { + var comment = Ox.getObjectById(annotation.comments, data.id) + if (comment) { + comment.value = data.value + comment.modified = (new Date).toISOString() + } else { + annotation.comments.push({ + created: data.created || (new Date).toISOString(), + modified: (new Date).toISOString(), + id: data.id, + value: data.value + }) + } + that.triggerEvent('change') + } }); var that = Ox.Element().attr({ - id: 'a-' + data.id + id: 'a-' + annotation.id }).addClass( 'OxSelectable OMLAnnotation' ).css({ borderBottom: '1px solid rgb(208, 208, 208)', }).append($quote).append($note); + that.annotate = function() { var item = { id: 'note', value: '', editable: true diff --git a/static/js/annotationFolder.js b/static/js/annotationFolder.js index 0472bce..b9d7760 100644 --- a/static/js/annotationFolder.js +++ b/static/js/annotationFolder.js @@ -3,8 +3,9 @@ oml.ui.annotationFolder = function() { var ui = oml.user.ui, that = Ox.Element().css({ overflowY: 'auto', + overflowX: 'hidden', }); return that; -}; \ No newline at end of file +}; diff --git a/static/js/folderList.js b/static/js/folderList.js index 44d5a57..323766e 100644 --- a/static/js/folderList.js +++ b/static/js/folderList.js @@ -13,7 +13,7 @@ oml.ui.folderList = function(options) { src: Ox.UI.getImageURL( value == 'libraries' ? 'symbolData' : value == 'library' ? 'symbolUser' - : data.name == 'Public' ? 'symbolPublish' + : data.name == 'Inbox' ? 'symbolPublish' : value == 'static' ? 'symbolClick' : 'symbolFind' ) diff --git a/static/js/folders.js b/static/js/folders.js index d943e62..f4e84a3 100644 --- a/static/js/folders.js +++ b/static/js/folders.js @@ -42,7 +42,7 @@ oml.ui.folders = function() { }).indexOf(list.user) : null; return list.id == '' ? oml.$ui.librariesList : Ox.endsWith(list.id, ':') ? oml.$ui.libraryList[index] - : Ox.endsWith(list.id, ':Public') ? oml.$ui.publicList[index] + : Ox.endsWith(list.id, ':Inbox') ? oml.$ui.inboxList[index] : oml.$ui.folderList[index]; } @@ -66,7 +66,7 @@ oml.ui.folders = function() { !list ? 'libraryList' : 'folderList' ][index] - : list == 'Public' ? oml.$ui.publicList[index] + : list == 'Inbox' ? oml.$ui.inboxList[index] : oml.$ui.folderList[index]; $lists.forEach(function($list) { if ($list == $selectedList) { @@ -87,7 +87,7 @@ oml.ui.folders = function() { oml.$ui.folder = []; oml.$ui.libraryList = []; oml.$ui.folderList = []; - oml.$ui.publicList = []; + oml.$ui.inboxList = []; getUsersAndLists(function(users, lists) { @@ -120,10 +120,11 @@ oml.ui.folders = function() { var $content, items = lists.filter(function(list) { return list.user === user.name - && list.type != 'library' && list.name != 'Public'; + && list.type != 'library' && list.name != 'Inbox'; }), libraryId = user.name + ':', - publicId = user.name + ':Public'; + inboxId = user.name + ':Inbox', + offset = 16; userIndex[user.name] = index; @@ -175,7 +176,8 @@ oml.ui.folders = function() { $content = oml.$ui.folder[index].$content .css({ - height: (2 + items.length) * 16 + 'px' + // user also has inbox + height: ((index ? 1 : 2) + items.length) * 16 + 'px' }); $lists.push( @@ -193,7 +195,7 @@ oml.ui.folders = function() { oml.UI.set({find: getFind(data.ids[0])}); }, selectnext: function() { - oml.UI.set({find: getFind(publicId)}); + oml.UI.set({find: getFind(inboxId)}); }, selectprevious: function() { // FIXME: ugly @@ -216,27 +218,29 @@ oml.ui.folders = function() { }) .appendTo($content) ); - - $lists.push( - oml.$ui.publicList[index] = oml.ui.folderList({ - items: lists.filter(function(list) { - return list.user == user.name && list.name == 'Public'; + if (user.name == '') { + $lists.push( + oml.$ui.inboxList[index] = oml.ui.folderList({ + items: lists.filter(function(list) { + return list.user == user.name && list.name == 'Inbox'; + }) }) - }) - .bindEvent({ - select: function(data) { - oml.UI.set({find: getFind(data.ids[0])}); - }, - selectnext: function() { - oml.UI.set({find: getFind(items[0].id)}); - }, - selectprevious: function() { - oml.UI.set({find: getFind(libraryId)}); - } - }) - .appendTo($content) - ); - oml.$ui.publicList[index].$body.css({top: '16px'}); + .bindEvent({ + select: function(data) { + oml.UI.set({find: getFind(data.ids[0])}); + }, + selectnext: function() { + oml.UI.set({find: getFind(items[0].id)}); + }, + selectprevious: function() { + oml.UI.set({find: getFind(libraryId)}); + } + }) + .appendTo($content) + ); + oml.$ui.inboxList[index].$body.css({top: offset + 'px'}); + offset += 16; + } $lists.push( oml.$ui.folderList[index] = oml.ui.folderList({ @@ -258,7 +262,7 @@ oml.ui.folders = function() { } }, 'delete': function(data) { - !index && !Ox.contains(data.ids, ':Public') && oml.ui.deleteListDialog().open(); + !index && !Ox.contains(data.ids, ':Inbox') && oml.ui.deleteListDialog().open(); }, key_control_d: function() { oml.addList(ui._list); @@ -275,7 +279,7 @@ oml.ui.folders = function() { }); }, open: function(data) { - !index && !Ox.contains(data.ids, ':Public') && oml.ui.listDialog().open(); + !index && !Ox.contains(data.ids, ':Inbox') && oml.ui.listDialog().open(); }, select: function(data) { oml.UI.set({find: getFind(data.ids[0])}); @@ -290,14 +294,14 @@ oml.ui.folders = function() { } }, selectprevious: function() { - oml.UI.set({find: getFind(publicId)}); + oml.UI.set({find: getFind(inboxId)}); } }) .css({height: items.length * 16 + 'px'}) .appendTo($content) ); - oml.$ui.folderList[index].$body.css({top: '32px'}); + oml.$ui.folderList[index].$body.css({top: offset + 'px'}); }); @@ -353,7 +357,7 @@ oml.ui.folders = function() { oml.$ui.folder[index].options({title: Ox.encodeHTMLEntities(name)}); oml.getLists(function(lists) { var items = lists.filter(function(list) { - return list.user == name && list.type != 'library' && list.name != 'Public'; + return list.user == name && list.type != 'library' && list.name != 'Inbox'; }), library = lists.filter(function(list) { return list.user == name && list.type == 'library'; @@ -362,8 +366,9 @@ oml.ui.folders = function() { oml.$ui.libraryList[index].options({ items: library }); + // library + inbox + lists oml.$ui.folder[index].$content - .css({height: 16 + items.length * 16 + 'px'}); + .css({height: (index ? 16 : 32) + items.length * 16 + 'px'}); oml.$ui.folderList[index].options({ items: items }) diff --git a/static/js/mainMenu.js b/static/js/mainMenu.js index e8c4c35..501cc03 100644 --- a/static/js/mainMenu.js +++ b/static/js/mainMenu.js @@ -601,6 +601,7 @@ oml.ui.mainMenu = function() { that[data.value ? 'enableItem' : 'disableItem']('book'); that[data.value ? 'disableItem' : 'enableItem']('showfilters'); that[data.value ? 'enableItem' : 'disableItem']('showbrowser'); + that[data.value ? 'enableItem' : 'disableItem']('showannotations'); } }, oml_itemview: function(data) { @@ -955,7 +956,7 @@ oml.ui.mainMenu = function() { isLibrary = Ox.endsWith(ui._list, ':'), isList = !isLibraries && !isLibrary, isOwnList = ui._list[0] == ':', - isPublic = ui._list == ':Public'; + isInbox = ui._list == ':Inbox'; if (oml.readOnly) { return { @@ -1019,13 +1020,13 @@ oml.ui.mainMenu = function() { id: 'editlist', title: Ox._('Edit List...'), keyboard: 'return', - disabled: !isList || !isOwnList || isPublic + disabled: !isList || !isOwnList || isInbox }, { id: 'deletelist', title: Ox._('Delete List...'), keyboard: 'delete', - disabled: !isList || !isOwnList || isPublic + disabled: !isList || !isOwnList || isInbox } ]) }; diff --git a/static/js/utils.js b/static/js/utils.js index f64412a..bdbdb27 100644 --- a/static/js/utils.js +++ b/static/js/utils.js @@ -316,7 +316,7 @@ oml.enableDragAndDrop = function($list, canMove) { $list.bindEvent({ draganddropstart: function(data) { - var $lists = oml.$ui.libraryList.concat(oml.$ui.publicList).concat(oml.$ui.folderList); + var $lists = oml.$ui.libraryList.concat(oml.$ui.inboxList).concat(oml.$ui.folderList); drag.action = 'copy'; drag.ids = $list.options('selected'); drag.item = drag.ids.length == 1 @@ -335,9 +335,9 @@ oml.enableDragAndDrop = function($list, canMove) { && drag.source.user != '' && data.user == '' ) || ( - data.type == 'static' - && data.name == 'Public' + data.type == 'library' && drag.source.user == '' + && data.user != '' ), selected: data.id == ui._list }, data); @@ -492,8 +492,12 @@ oml.enableDragAndDrop = function($list, canMove) { text = Ox._('You cannot move books
out of a smart list.'); } } else if (drag.target) { + console.log(drag.target) targetText = drag.target.type == 'libraries' ? Ox._('a library') - : drag.target.type == 'library' ? Ox._('your library') + : drag.target.type == 'library' ? + drag.target.user == '' + ? Ox._('your library') + : Ox._('{0}\'s library', [Ox.encodeHTMLEntities(Ox.truncate(drag.target.user, 32))]) : Ox._('the list "{0}"', [Ox.encodeHTMLEntities(Ox.truncate(drag.target.name, 32))]); if ( ( @@ -507,7 +511,7 @@ oml.enableDragAndDrop = function($list, canMove) { Ox._(itemText[0] == '"' ? '' : 'These ') + itemText, targetText ]); - } else if (drag.target.user != '' && drag.target.name != 'Public') { + } else if (drag.target.user != '' && drag.target.type != 'library') { text = Ox._( 'You can only {0} books
to your own {1}.', [actionText, drag.target.type == 'library' ? 'library' : 'lists'] @@ -992,7 +996,7 @@ oml.resizeListFolders = function() { oml.$ui.libraryList[index] .css({width: width + 'px'}) .resizeColumn('name', columnWidth); - oml.$ui.publicList[index] + oml.$ui.inboxList[index] && oml.$ui.inboxList[index] .css({width: width + 'px'}) .resizeColumn('name', columnWidth); oml.$ui.folderList[index] diff --git a/static/js/viewer.js b/static/js/viewer.js index 8cb4842..848364a 100644 --- a/static/js/viewer.js +++ b/static/js/viewer.js @@ -46,6 +46,7 @@ oml.ui.viewer = function() { function saveAnnotations(data) { if (data) { data.created = data.created || (new Date).toISOString(); + data.comments = data.comments || []; annotations.push(data); } localStorage[item + '.annotations'] = JSON.stringify(annotations) @@ -57,6 +58,14 @@ oml.ui.viewer = function() { saveAnnotations() } + var annotationEvents = { + change: function() { + console.log('change...') + console.log(annotations) + saveAnnotations() + } + } + that.updateElement = function() { item = ui.item; if (item && item.length) { @@ -75,7 +84,7 @@ oml.ui.viewer = function() { if (event == 'addAnnotation') { console.log('adding', data.id) saveAnnotations(data); - var $annotation = oml.ui.annotation(data, $iframe) + var $annotation = oml.ui.annotation(data, $iframe).bindEvent(annotationEvents) oml.$ui.annotationFolder.append($annotation); $annotation.annotate(); } else if (event == 'removeAnnotation') { @@ -93,7 +102,7 @@ oml.ui.viewer = function() { init: function() { loadAnnotations(function(annotations) { annotations.forEach(function(data) { - var $annotation = oml.ui.annotation(data, $iframe) + var $annotation = oml.ui.annotation(data, $iframe).bindEvent(annotationEvents) oml.$ui.annotationFolder.append($annotation); }) // fixme: trigger loaded event from reader instead? diff --git a/static/reader/pdf.js b/static/reader/pdf.js index b4bbed3..15b4487 100644 --- a/static/reader/pdf.js +++ b/static/reader/pdf.js @@ -11,14 +11,21 @@ Ox.load({ console.log('got', event, 'data', data) if (event == 'selectAnnotation') { var annotation = annotations.filter(function(a) { return a.id == data.id })[0] + var delay = 0 if ( annotation && annotation.page && PDFViewerApplication.pdfViewer.currentPageNumber != annotation.page ) { - //FIXME: scroll into view PDFViewerApplication.pdfViewer.currentPageNumber = annotation.page; + delay = 250 } + setTimeout(function() { + var el = document.querySelector('.a' + annotation.id); + if (el) { + document.querySelector('#viewerContainer').scrollTop = el.offsetTop + el.parentElement.offsetTop - 64; + } + }, delay) selectAnnotation(data.id) } else if (event == 'addAnnotations') { data.annotations.forEach(function(annotation) { @@ -144,7 +151,7 @@ function removeAnnotation(id) { annotations = annotations.filter(function(annotation) { return annotation.id != id }) - Ox.$parent.postMessage('removeAnnotation', {id: selected.dataset.id}) + Ox.$parent.postMessage('removeAnnotation', {id: id}) } function loadAnnotations(page) {