diff --git a/README b/README index adacb44..7557231 100644 --- a/README +++ b/README @@ -21,6 +21,11 @@ you need a working python 2.7.x installation and the following packages: python-simplejson python-lxml pip install -r requirements.txt +On Linux you need to always install: + + apt-get install \ + python-imaging python-lxml ghostscript + == Development == mkdir client diff --git a/oml/changelog.py b/oml/changelog.py index 926e76d..e6e0679 100644 --- a/oml/changelog.py +++ b/oml/changelog.py @@ -56,6 +56,15 @@ class Changelog(db.Model): def timestamp(self): return self.created.strftime('%s') + @classmethod + def apply_changes(cls, user, changes): + for change in changes: + if not Changelog.apply_change(user, change): + print 'FAIL', change + break + return False + return True + @classmethod def apply_change(cls, user, change, rebuild=False): revision, timestamp, sig, data = change diff --git a/oml/item/api.py b/oml/item/api.py index 96be84b..9f43121 100644 --- a/oml/item/api.py +++ b/oml/item/api.py @@ -183,6 +183,8 @@ actions.register(scan, cache=False) @returns_json def _import(request): - state.main.add_callback(state.websockets[0].put, json.dumps(['import', {}])) + data = json.loads(request.form['data']) if 'data' in request.form else {} + print 'api.import', data + state.main.add_callback(state.websockets[0].put, json.dumps(['import', data])) return {} actions.register(_import, 'import', cache=False) diff --git a/oml/item/scan.py b/oml/item/scan.py index 52f16d2..0d76ec5 100644 --- a/oml/item/scan.py +++ b/oml/item/scan.py @@ -12,13 +12,15 @@ from app import app import settings from settings import db from item.models import File -from user.models import User +from user.models import User, List from changelog import Changelog import media from websocket import trigger_event +extensions = ['epub', 'pdf', 'txt'] + def remove_missing(): dirty = False with app.app_context(): @@ -49,7 +51,6 @@ def run_scan(): prefix += '/' user = User.get_or_create(settings.USER_ID) assert isinstance(prefix, unicode) - extensions = ['pdf', 'epub', 'txt'] books = [] for root, folders, files in os.walk(prefix): for f in files: @@ -62,8 +63,7 @@ def run_scan(): books.append(f) trigger_event('scan', { - 'path': prefix, - 'files': len(books) + 'progress': [0, len(books)], }) position = 0 added = 0 @@ -96,29 +96,37 @@ def run_scan(): item.scrape() added += 1 trigger_event('scan', { - 'position': position, - 'length': len(books), - 'path': path, - 'progress': position/len(books), 'added': added, + 'progress': [position, len(books)], + 'path': path, }) trigger_event('scan', { - 'progress': 1, + 'progress': [position, len(books)], 'added': added, - 'done': True + 'status': {'code': 200, 'text': ''} }) -def run_import(): +def run_import(options=None): + options = options or {} + with app.app_context(): prefs = settings.preferences - prefix = os.path.expanduser(prefs['importPath']) + prefix = options.get('path', os.path.expanduser(prefs['importPath'])) prefix_books = os.path.join(os.path.expanduser(prefs['libraryPath']), 'Books/') prefix_imported = os.path.join(prefix_books, 'Imported/') if not prefix[-1] == '/': prefix += '/' + + if not os.path.exists(prefix): + trigger_event('import', { + 'progress': [0, 0], + 'status': {'code': 404, 'text': 'path not found'} + }) user = User.get_or_create(settings.USER_ID) + listname = options.get('list') + if listname: + listitems = [] assert isinstance(prefix, unicode) - extensions = ['pdf', 'epub', 'txt'] books = [] for root, folders, files in os.walk(prefix): for f in files: @@ -131,8 +139,7 @@ def run_import(): books.append(f) trigger_event('import', { - 'path': prefix, - 'files': len(books) + 'progress': [0, len(books)], }) position = 0 added = 0 @@ -145,7 +152,10 @@ def run_import(): f_import = f f = f.replace(prefix, prefix_imported) ox.makedirs(os.path.dirname(f)) - shutil.move(f_import, f) + if options.get('mode') == 'move': + shutil.move(f_import, f) + else: + shutil.copy(f_import, f) path = f[len(prefix_books):] data = media.metadata(f) ext = f.split('.')[-1] @@ -167,16 +177,19 @@ def run_import(): item.meta['mainid']: item.meta[item.meta['mainid']] }) item.scrape() + if listname: + listitems.append(item.id) added += 1 trigger_event('import', { - 'position': position, - 'length': len(books), + 'progress': [position, len(books)], 'path': path, - 'progress': position/len(books), 'added': added, }) + if listname: + l = List.get_or_create(settings.USER_ID, listname) + l.add_items(listitems) trigger_event('import', { - 'progress': 1, + 'progress': [position, len(books)], + 'status': {'code': 200, 'text': ''}, 'added': added, - 'done': True }) diff --git a/oml/media/epub.py b/oml/media/epub.py index 9aa3828..c7ddd61 100644 --- a/oml/media/epub.py +++ b/oml/media/epub.py @@ -46,6 +46,8 @@ def info(epub): isbn = extract_isbn(text) if isbn: data['isbn'] = isbn + if 'date' in data and 'T' in data['date']: + data['date'] = data['date'].split('T')[0] return data def extract_text(path): diff --git a/oml/node/api.py b/oml/node/api.py index 250d4e3..c5ccab4 100644 --- a/oml/node/api.py +++ b/oml/node/api.py @@ -29,11 +29,10 @@ def api_pullChanges(app, remote_id, user_id=None, from_=None, to=None): def api_pushChanges(app, user_id, changes): user = User.get(user_id) - for change in changes: - if not Changelog.apply_change(user, change): - print 'FAILED TO APPLY CHANGE', change - state.nodes.queue(user_id, 'pullChanges') - return False + if not Changelog.apply_changes(user, changes): + print 'FAILED TO APPLY CHANGE' + state.nodes.queue(user_id, 'pullChanges') + return False return True def api_requestPeering(app, user_id, username, message): diff --git a/oml/nodes.py b/oml/nodes.py index 3151164..282edea 100644 --- a/oml/nodes.py +++ b/oml/nodes.py @@ -164,12 +164,7 @@ class Node(object): changes = self.request('pullChanges', from_revision) if not changes: return False - for change in changes: - if not Changelog.apply_change(self.user, change): - print 'FAIL', change - break - return False - return True + return Changelog.apply_changes(self.user, changes) def pushChanges(self, changes): print 'pushing changes to', self.user_id, changes diff --git a/oml/websocket.py b/oml/websocket.py index 06643eb..dff2831 100644 --- a/oml/websocket.py +++ b/oml/websocket.py @@ -33,7 +33,7 @@ class Background: if action == 'ping': self.post(['pong', data]) elif action == 'import': - item.scan.run_import() + item.scan.run_import(data) elif action == 'scan': item.scan.run_scan() elif action == 'update': diff --git a/static/js/importDialog.js b/static/js/importDialog.js new file mode 100644 index 0000000..91da9e4 --- /dev/null +++ b/static/js/importDialog.js @@ -0,0 +1,117 @@ +'use strict'; + +oml.ui.importDialog = function() { + + var ui = oml.user.ui, + username = oml.user.preferences.username, + + lists = ui._lists.filter(function(list) { + return list.user == username && list.type == 'static'; + }), + items = [ + {id: ':', title: Ox._('Library')} + ].concat( + lists.length ? [{}] : [] + ).concat( + lists.map(function(list) { + return {id: list.id, title: list.name}; + }) + ).concat([ + {}, + {id: '', title: Ox._('New List...')} + ]), + + listNames = ui._lists.filter(function(list) { + return list.user == username; + }).map(function(list) { + return list.name; + }), + + $form = Ox.Form({ + items: [ + Ox.Input({ + changeOnKeypress: true, + id: 'path', + label: 'Source Path', + labelWidth: 128, + width: 480 + }), + Ox.SelectInput({ + id: 'list', + inputValue: oml.validateName(Ox._('Untitled'), listNames), + inputWidth: 224, + items: items, + label: 'Destination', + labelWidth: 128, + max: 1, + min: 1, + value: ':', + width: 480 + }), + Ox.Select({ + id: 'action', + items: [ + {id: 'copy', title: Ox._('Keep files in source path')}, + {id: 'move', title: Ox._('Remove files from source path')} + ], + label: Ox._('Import Mode'), + labelWidth: 128, + width: 480 + }) + ] + }) + .css({margin: '16px'}) + .bindEvent({ + change: function(data) { + var values = $form.values(); + Ox.print('FORM CHANGE', data); + if (data.id == 'list') { + // FIXME: WRONG + if (data.data.value[0] != ':') { + $form.values('list', oml.validateName(data.data.value, listNames)) + } + } + that[ + values.path && values.list ? 'enableButton' : 'disableButton' + ]('import'); + } + }), + + that = Ox.Dialog({ + buttons: [ + Ox.Button({ + id: 'dontimport', + title: Ox._('No, Don\'t Import') + }) + .bindEvent({ + click: function() { + that.close(); + } + }), + Ox.Button({ + disabled: true, + id: 'import', + title: Ox._('Yes, Import') + }) + .bindEvent({ + click: function() { + var data = $form.values(); + oml.api.import({ + path: data.path, + list: data.list == ':' ? null : data.list + }, function() { + // ... + }) + that.close(); + } + }) + ], + content: $form, + height: 128, + title: Ox._('Import Books'), + width: 512 + }); + + return that; + +}; \ No newline at end of file diff --git a/static/js/mainMenu.js b/static/js/mainMenu.js index 91bde1d..46295f6 100644 --- a/static/js/mainMenu.js +++ b/static/js/mainMenu.js @@ -370,6 +370,8 @@ oml.ui.mainMenu = function() { oml.ui.listDialog.open(); } else if (id == 'deletelist') { oml.ui.deleteListDialog().open(); + } else if (id == 'import') { + oml.ui.importDialog().open(); } else if (id == 'selectall') { oml.$ui.list.selectAll(); } else if (id == 'selectnone') { @@ -545,11 +547,11 @@ oml.ui.mainMenu = function() { title: Ox._('Edit'), items: [ { - id: 'importitems', + id: 'import', title: Ox._('Import Books...') }, { - id: 'exportitems', + id: 'export', title: Ox._('Export Books...') }, {}, diff --git a/static/json/js.json b/static/json/js.json index 0d5dfa2..665c0bb 100644 --- a/static/json/js.json +++ b/static/json/js.json @@ -25,6 +25,7 @@ "gridView.js", "iconDialog.js", "identifyDialog.js", + "importDialog.js", "info.js", "infoView.js", "itemInnerPanel.js",