import/lists/autocompleteFolder

This commit is contained in:
j 2014-05-19 01:24:04 +02:00
parent 94443ee667
commit d6f350e5a1
42 changed files with 955 additions and 436 deletions

View file

@ -10,7 +10,7 @@ Networking
---------- ----------
At this time you need a working IPv6 connection to use Open Media Libary. At this time you need a working IPv6 connection to use Open Media Libary.
If you dont have native IPv6 you can use Teredo/Miredo (apt-get install miredo) If you dont have native IPv6 you can use Teredo/Miredo (`apt-get install miredo`)
or get a tunnel Hurricane Electric (https://tunnelbroker.net/) or get a tunnel Hurricane Electric (https://tunnelbroker.net/)
or SixSS (https://sixxs.net). or SixSS (https://sixxs.net).

View file

@ -2,5 +2,80 @@
# vi:si:et:sw=4:sts=4:ts=4 # vi:si:et:sw=4:sts=4:ts=4
from __future__ import division from __future__ import division
import subprocess
import json
import os
import ox
from oxflask.api import actions
from oxflask.shortcuts import returns_json
import item.api import item.api
import user.api import user.api
@returns_json
def selectFolder(request):
'''
returns {
path
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {}
cmd = ['./ctl', 'ui', 'folder']
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
stdout, stderr = p.communicate()
path = stdout.decode('utf-8').strip()
return {
'path': path
}
actions.register(selectFolder, cache=False)
@returns_json
def selectFile(request):
'''
returns {
path
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {}
cmd = ['./ctl', 'ui', 'file']
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
stdout, stderr = p.communicate()
path = stdout.decode('utf-8').strip()
return {
'path': path
}
actions.register(selectFile, cache=False)
@returns_json
def autocompleteFolder(request):
'''
takes {
path
}
returns {
items
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {}
path = data['path']
path = os.path.expanduser(path)
if os.path.isdir(path):
if path.endswith('/') and path != '/':
path = path[:-1]
folder = path
name = ''
else:
folder, name = os.path.split(path)
if os.path.exists(folder):
prefix, folders, files = os.walk(folder).next()
folders = [os.path.join(prefix, f) for f in folders if not name or f.startswith(name)]
if prefix == path:
folders = [path] + folders
else:
folders = []
return {
'items': ox.sorted_strings(folders)
}
actions.register(autocompleteFolder, cache=False)

View file

@ -18,8 +18,7 @@ import item.models
import user.models import user.models
import item.person import item.person
import item.api import api
import user.api
import item.views import item.views
import commands import commands

View file

@ -23,7 +23,7 @@ class Downloads(Thread):
import item.models import item.models
for i in item.models.Item.query.filter( for i in item.models.Item.query.filter(
item.models.Item.transferadded!=None).filter( item.models.Item.transferadded!=None).filter(
item.models.Item.transferprogress<1): item.models.Item.transferprogress<1).order_by(item.models.Item.transferadded):
logger.debug('DOWNLOAD %s %s', i, i.users) logger.debug('DOWNLOAD %s %s', i, i.users)
for p in i.users: for p in i.users:
if state.nodes.check_online(p.id): if state.nodes.check_online(p.id):

View file

@ -2,11 +2,12 @@
# vi:si:et:sw=4:sts=4:ts=4 # vi:si:et:sw=4:sts=4:ts=4
from __future__ import division from __future__ import division
import logging import os
import json import json
from oxflask.api import actions from oxflask.api import actions
from oxflask.shortcuts import returns_json from oxflask.shortcuts import returns_json
from sqlalchemy.orm import load_only
import query import query
@ -18,12 +19,22 @@ import meta
import utils import utils
import logging
logger = logging.getLogger('oml.item.api') logger = logging.getLogger('oml.item.api')
@returns_json @returns_json
def find(request): def find(request):
''' '''
find items takes {
query {
conditions [{}]
operator string
}
group string
keys [string]
sort [{}]
range [int, int]
}
''' '''
response = {} response = {}
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
@ -31,7 +42,7 @@ def find(request):
if 'group' in q: if 'group' in q:
names = {} names = {}
groups = {} groups = {}
items = [i.id for i in q['qs']] items = [i.id for i in q['qs'].options(load_only('id'))]
qs = models.Find.query.filter_by(key=q['group']) qs = models.Find.query.filter_by(key=q['group'])
if items: if items:
qs = qs.filter(models.Find.item_id.in_(items)) qs = qs.filter(models.Find.item_id.in_(items))
@ -58,16 +69,12 @@ def find(request):
else: else:
response['items'] = len(g) response['items'] = len(g)
elif 'position' in data: elif 'position' in data:
ids = [i.id for i in q['qs']] ids = [i.id for i in q['qs'].options(load_only('id'))]
response['position'] = utils.get_positions(ids, [data['qs'][0].id])[0] response['position'] = utils.get_positions(ids, [data['qs'][0].id])[0]
elif 'positions' in data: elif 'positions' in data:
ids = [i.id for i in q['qs']] ids = [i.id for i in q['qs'].options(load_only('id'))]
response['positions'] = utils.get_positions(ids, data['positions']) response['positions'] = utils.get_positions(ids, data['positions'])
elif 'keys' in data: elif 'keys' in data:
'''
qs = qs[q['range'][0]:q['range'][1]]
response['items'] = [p.json(data['keys']) for p in qs]
'''
response['items'] = [] response['items'] = []
for i in q['qs'][q['range'][0]:q['range'][1]]: for i in q['qs'][q['range'][0]:q['range'][1]]:
j = i.json() j = i.json()
@ -77,12 +84,18 @@ def find(request):
#from sqlalchemy.sql import func #from sqlalchemy.sql import func
#models.db.session.query(func.sum(models.Item.sort_size).label("size")) #models.db.session.query(func.sum(models.Item.sort_size).label("size"))
#response['size'] = x.scalar() #response['size'] = x.scalar()
response['size'] = sum([i.sort_size or 0 for i in q['qs']]) response['size'] = sum([i.sort_size or 0 for i in q['qs'].options(load_only('id', 'sort_size'))])
return response return response
actions.register(find) actions.register(find)
@returns_json @returns_json
def get(request): def get(request):
'''
takes {
id
keys
}
'''
response = {} response = {}
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
item = models.Item.get(data['id']) item = models.Item.get(data['id'])
@ -93,29 +106,48 @@ actions.register(get)
@returns_json @returns_json
def edit(request): def edit(request):
'''
takes {
id
...
}
setting identifier or base metadata is possible not both at the same time
'''
response = {} response = {}
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
logger.debug('edit', data) logger.debug('edit %s', data)
item = models.Item.get(data['id']) item = models.Item.get(data['id'])
keys = filter(lambda k: k in models.Item.id_keys, data.keys()) keys = filter(lambda k: k in models.Item.id_keys, data.keys())
logger.debug(item, keys) logger.debug('edit of %s id keys: %s', item, keys)
if item and keys and item.json()['mediastate'] == 'available': if item and item.json()['mediastate'] == 'available':
if keys:
key = keys[0] key = keys[0]
logger.debug('update mainid %s %s', key, data[key]) logger.debug('update mainid %s %s', key, data[key])
if key in ('isbn10', 'isbn13'): if key in ('isbn10', 'isbn13'):
data[key] = utils.normalize_isbn(data[key]) data[key] = utils.normalize_isbn(data[key])
item.update_mainid(key, data[key]) item.update_mainid(key, data[key])
response = item.json() response = item.json()
elif not item.meta.get('mainid'):
logger.debug('chustom data %s', data)
for key in ('title', 'author', 'date', 'publisher', 'edition'):
if key in data:
item.meta[key] = data[key]
item.update()
logger.debug('FIXME: custom metadata not published to changelog!!!')
else: else:
logger.info('can only edit available items') logger.info('can only edit available items')
response = item.json()
return response return response
actions.register(edit, cache=False) actions.register(edit, cache=False)
@returns_json @returns_json
def remove(request): def remove(request):
'''
takes {
id
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
logger.debug('remove files', data) logger.debug('remove files %s', data)
if 'ids' in data and data['ids']: if 'ids' in data and data['ids']:
for i in models.Item.query.filter(models.Item.id.in_(data['ids'])): for i in models.Item.query.filter(models.Item.id.in_(data['ids'])):
i.remove_file() i.remove_file()
@ -132,10 +164,11 @@ def findMetadata(request):
date: string date: string
} }
returns { returns {
title: string, items: [{
autor: [string], key: value
date: string, }]
} }
key is one of the supported identifiers: isbn10, isbn13...
''' '''
response = {} response = {}
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
@ -146,18 +179,30 @@ actions.register(findMetadata)
@returns_json @returns_json
def getMetadata(request): def getMetadata(request):
'''
takes {
key: value
}
key can be one of the supported identifiers: isbn10, isbn13, oclc, olid,...
'''
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
logger.debug('getMetadata %s', data) logger.debug('getMetadata %s', data)
key, value = data.iteritems().next() key, value = data.iteritems().next()
if key in ('isbn10', 'isbn13'): if key in ('isbn10', 'isbn13'):
value = utils.normalize_isbn(value) value = utils.normalize_isbn(value)
response = meta.lookup(key, value) response = meta.lookup(key, value)
if response:
response['mainid'] = key response['mainid'] = key
return response return response
actions.register(getMetadata) actions.register(getMetadata)
@returns_json @returns_json
def download(request): def download(request):
'''
takes {
id
}
'''
response = {} response = {}
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
item = models.Item.get(data['id']) item = models.Item.get(data['id'])
@ -170,6 +215,11 @@ actions.register(download, cache=False)
@returns_json @returns_json
def cancelDownloads(request): def cancelDownloads(request):
'''
takes {
ids
}
'''
response = {} response = {}
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
ids = data['ids'] ids = data['ids']
@ -195,8 +245,21 @@ actions.register(scan, cache=False)
@returns_json @returns_json
def _import(request): def _import(request):
'''
takes {
path absolute path to import
list listename (add new items to this list)
mode copy|move
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
logger.debug('api.import %s', data) logger.debug('api.import %s', data)
state.main.add_callback(state.websockets[0].put, json.dumps(['import', data])) state.main.add_callback(state.websockets[0].put, json.dumps(['import', data]))
return {} return {}
actions.register(_import, 'import', cache=False) actions.register(_import, 'import', cache=False)
@returns_json
def cancelImport(request):
state.activity['cancel'] = True
return {}
actions.register(cancelImport, cache=False)

View file

@ -33,6 +33,7 @@ from oxflask.db import MutableDict
from covers import covers from covers import covers
from changelog import Changelog from changelog import Changelog
from websocket import trigger_event from websocket import trigger_event
from utils import remove_empty_folders
logger = logging.getLogger('oml.item.model') logger = logging.getLogger('oml.item.model')
@ -296,8 +297,8 @@ class Item(db.Model):
def extract_cover(self): def extract_cover(self):
path = self.get_path() path = self.get_path()
if not path: if path:
return getattr(media, self.meta['extensions']).cover(path) return getattr(media, self.info['extension']).cover(path)
def update_cover(self): def update_cover(self):
cover = None cover = None
@ -327,8 +328,6 @@ class Item(db.Model):
if mainid: if mainid:
m = meta.lookup(mainid, self.meta[mainid]) m = meta.lookup(mainid, self.meta[mainid])
self.meta.update(m) self.meta.update(m)
else:
logger.debug('FIX UPDATE %s', mainid)
self.update() self.update()
def queue_download(self): def queue_download(self):
@ -360,6 +359,7 @@ class Item(db.Model):
Changelog.record(u, 'additem', self.id, self.info) Changelog.record(u, 'additem', self.id, self.info)
self.update() self.update()
f.move() f.move()
self.update_cover()
trigger_event('transfer', { trigger_event('transfer', {
'id': self.id, 'progress': 1 'id': self.id, 'progress': 1
}) })
@ -376,6 +376,7 @@ class Item(db.Model):
logger.debug('remove file %s', path) logger.debug('remove file %s', path)
if os.path.exists(path): if os.path.exists(path):
os.unlink(path) os.unlink(path)
remove_empty_folders(os.path.dirname(path))
db.session.delete(f) db.session.delete(f)
user = state.user() user = state.user()
self.users.remove(user) self.users.remove(user)
@ -399,7 +400,7 @@ for key in config['itemKeys']:
col = db.Column(db.String(1000), index=True) col = db.Column(db.String(1000), index=True)
setattr(Item, 'sort_%s' % key['id'], col) setattr(Item, 'sort_%s' % key['id'], col)
Item.id_keys = ['isbn10', 'isbn13', 'lccn', 'olid', 'oclc'] Item.id_keys = ['isbn10', 'isbn13', 'lccn', 'olid', 'oclc', 'asin']
Item.item_keys = config['itemKeys'] Item.item_keys = config['itemKeys']
Item.filter_keys = [k['id'] for k in config['itemKeys'] if k.get('filter')] Item.filter_keys = [k['id'] for k in config['itemKeys'] if k.get('filter')]

View file

@ -19,6 +19,10 @@ from changelog import Changelog
import media import media
from websocket import trigger_event from websocket import trigger_event
import state import state
from utils import remove_empty_folders
import logging
logger = logging.getLogger('oml.item.scan')
extensions = ['epub', 'pdf', 'txt'] extensions = ['epub', 'pdf', 'txt']
@ -35,38 +39,10 @@ def remove_missing():
if dirty: if dirty:
db.session.commit() db.session.commit()
def run_scan(): def add_file(id, f, prefix):
remove_missing() user = state.user()
with app.app_context():
prefs = settings.preferences
prefix = os.path.join(os.path.expanduser(prefs['libraryPath']), 'Books/')
if not prefix[-1] == '/':
prefix += '/'
user = User.get_or_create(settings.USER_ID)
assert isinstance(prefix, unicode)
books = []
for root, folders, files in os.walk(prefix):
for f in files:
#if f.startswith('._') or f == '.DS_Store':
if f.startswith('.'):
continue
f = os.path.join(root, f)
ext = f.split('.')[-1]
if ext in extensions:
books.append(f)
position = 0
added = 0
for f in ox.sorted_strings(books):
position += 1
id = media.get_id(f)
file = File.get(id)
path = f[len(prefix):] path = f[len(prefix):]
if not file:
data = media.metadata(f) data = media.metadata(f)
ext = f.split('.')[-1]
data['extension'] = ext
data['size'] = os.stat(f).st_size
file = File.get_or_create(id, data, path) file = File.get_or_create(id, data, path)
item = file.item item = file.item
if 'mainid' in file.info: if 'mainid' in file.info:
@ -84,6 +60,35 @@ def run_scan():
}) })
item.added = datetime.now() item.added = datetime.now()
item.scrape() item.scrape()
return file
def run_scan():
remove_missing()
with app.app_context():
prefs = settings.preferences
prefix = os.path.join(os.path.expanduser(prefs['libraryPath']), 'Books/')
if not prefix[-1] == '/':
prefix += '/'
assert isinstance(prefix, unicode)
books = []
for root, folders, files in os.walk(prefix):
for f in files:
#if f.startswith('._') or f == '.DS_Store':
if f.startswith('.'):
continue
f = os.path.join(root, f)
ext = f.split('.')[-1]
if ext in extensions:
books.append(f)
position = 0
added = 0
for f in ox.sorted_strings(books):
position += 1
id = media.get_id(f)
file = File.get(id)
if not file:
file = add_file(id, f, prefix)
added += 1 added += 1
trigger_event('change', {}) trigger_event('change', {})
@ -93,18 +98,28 @@ def run_import(options=None):
with app.app_context(): with app.app_context():
prefs = settings.preferences prefs = settings.preferences
prefix = os.path.expanduser(options.get('path', prefs['importPath'])) prefix = os.path.expanduser(options.get('path', prefs['importPath']))
if os.path.islink(prefix):
prefix = os.path.realpath(prefix)
if not prefix[-1] == '/': if not prefix[-1] == '/':
prefix += '/' prefix += '/'
prefix_books = os.path.join(os.path.expanduser(prefs['libraryPath']), 'Books/') prefix_books = os.path.join(os.path.expanduser(prefs['libraryPath']), 'Books/')
prefix_imported = os.path.join(prefix_books, 'Imported/') prefix_imported = os.path.join(prefix_books, 'Imported/')
if not os.path.exists(prefix): if prefix_books.startswith(prefix) or prefix.startswith(prefix_books):
error = 'invalid path'
elif not os.path.exists(prefix):
error = 'path not found'
elif not os.path.isdir(prefix):
error = 'path must be a folder'
else:
error = None
if error:
trigger_event('activity', { trigger_event('activity', {
'activity': 'import', 'activity': 'import',
'progress': [0, 0], 'progress': [0, 0],
'status': {'code': 404, 'text': 'path not found'} 'status': {'code': 404, 'text': error}
}) })
state.activity = {} state.activity = {}
user = User.get_or_create(settings.USER_ID) return
listname = options.get('list') listname = options.get('list')
if listname: if listname:
listitems = [] listitems = []
@ -122,6 +137,7 @@ def run_import(options=None):
state.activity = { state.activity = {
'activity': 'import', 'activity': 'import',
'path': prefix,
'progress': [0, len(books)], 'progress': [0, len(books)],
} }
trigger_event('activity', state.activity) trigger_event('activity', state.activity)
@ -133,7 +149,6 @@ def run_import(options=None):
continue continue
id = media.get_id(f) id = media.get_id(f)
file = File.get(id) file = File.get(id)
path = f[len(prefix):]
if not file: if not file:
f_import = f f_import = f
f = f.replace(prefix, prefix_imported) f = f.replace(prefix, prefix_imported)
@ -142,45 +157,38 @@ def run_import(options=None):
shutil.move(f_import, f) shutil.move(f_import, f)
else: else:
shutil.copy(f_import, f) shutil.copy(f_import, f)
path = f[len(prefix_books):] file = add_file(id, f, prefix_books)
data = media.metadata(f)
ext = f.split('.')[-1]
data['extension'] = ext
data['size'] = os.stat(f).st_size
file = File.get_or_create(id, data, path)
item = file.item
if 'mainid' in file.info:
del file.info['mainid']
db.session.add(file)
if 'mainid' in item.info:
item.meta['mainid'] = item.info.pop('mainid')
item.meta[item.meta['mainid']] = item.info[item.meta['mainid']]
db.session.add(item)
item.users.append(user)
Changelog.record(user, 'additem', item.id, item.info)
if item.meta.get('mainid'):
Changelog.record(user, 'edititem', item.id, {
item.meta['mainid']: item.meta[item.meta['mainid']]
})
item.scrape()
file.move() file.move()
item = file.item
if listname: if listname:
listitems.append(item.id) listitems.append(item.id)
added += 1 added += 1
if state.activity.get('cancel'):
state.activity = {}
trigger_event('activity', {
'activity': 'import',
'status': {'code': 200, 'text': 'canceled'}
})
return
state.activity = { state.activity = {
'activity': 'import', 'activity': 'import',
'progress': [position, len(books)], 'progress': [position, len(books)],
'path': path, 'path': prefix,
'added': added, 'added': added,
} }
trigger_event('activity', state.activity) trigger_event('activity', state.activity)
if listname: if listname and listitems:
l = List.get_or_create(settings.USER_ID, listname) l = List.get(settings.USER_ID, listname)
if l:
l.add_items(listitems) l.add_items(listitems)
trigger_event('activity', { trigger_event('activity', {
'activity': 'import', 'activity': 'import',
'progress': [position, len(books)], 'progress': [position, len(books)],
'path': prefix,
'status': {'code': 200, 'text': ''}, 'status': {'code': 200, 'text': ''},
'added': added, 'added': added,
}) })
state.activity = {} state.activity = {}
remove_empty_folders(prefix_books)
if options.get('mode') == 'move':
remove_empty_folders(prefix)

View file

@ -22,6 +22,8 @@ def get_id(f=None, data=None):
def metadata(f): def metadata(f):
ext = f.split('.')[-1] ext = f.split('.')[-1]
data = {} data = {}
data['extension'] = ext
data['size'] = os.stat(f).st_size
if ext == 'pdf': if ext == 'pdf':
info = pdf.info(f) info = pdf.info(f)
elif ext == 'epub': elif ext == 'epub':

View file

@ -71,9 +71,11 @@ def info(pdf):
with open(pdf, 'rb') as fd: with open(pdf, 'rb') as fd:
try: try:
pdfreader = PdfFileReader(fd) pdfreader = PdfFileReader(fd)
data['pages'] = pdfreader.numPages
info = pdfreader.getDocumentInfo() info = pdfreader.getDocumentInfo()
if info: if info:
for key in info: for key in info:
print key, info
if info[key]: if info[key]:
data[key[1:].lower()] = info[key] data[key[1:].lower()] = info[key]
xmp =pdfreader.getXmpMetadata() xmp =pdfreader.getXmpMetadata()

View file

@ -2,8 +2,7 @@
# vi:si:et:sw=4:sts=4:ts=4 # vi:si:et:sw=4:sts=4:ts=4
from __future__ import division from __future__ import division
import logging import stdnum.isbn
logger = logging.getLogger('meta')
import abebooks import abebooks
import loc import loc
@ -13,6 +12,10 @@ import worldcat
import google import google
import duckduckgo import duckduckgo
import logging
logger = logging.getLogger('meta')
providers = [ providers = [
('openlibrary', 'olid'), ('openlibrary', 'olid'),
('loc', 'lccn'), ('loc', 'lccn'),
@ -32,6 +35,8 @@ def find(title, author=None, publisher=None, date=None):
return results return results
def lookup(key, value): def lookup(key, value):
if not isvalid_id(key, value):
return {}
data = {key: value} data = {key: value}
ids = [(key, value)] ids = [(key, value)]
provider_data = {} provider_data = {}
@ -59,4 +64,13 @@ def lookup(key, value):
data[k_] = v_ data[k_] = v_
return data return data
def isvalid_id(key, value):
if key in ('isbn10', 'isbn13'):
if 'isbn%d'%len(value) != key or not stdnum.isbn.is_valid(value):
return False
if key == 'asin' and len(value) != 10:
return False
if key == 'olid' and not (value.startswith('OL') and value.endswith('M')):
return False
return True

View file

@ -9,10 +9,11 @@ import lxml.html
import logging import logging
logger = logging.getLogger('meta.abebooks') logger = logging.getLogger('meta.abebooks')
base = 'http://www.abebooks.com'
def get_ids(key, value): def get_ids(key, value):
ids = [] ids = []
if key in ('isbn10', 'isbn13'): if key in ('isbn10', 'isbn13'):
base = 'http://www.abebooks.com'
url = '%s/servlet/SearchResults?isbn=%s&sts=t' % (base, id) url = '%s/servlet/SearchResults?isbn=%s&sts=t' % (base, id)
data = read_url(url) data = read_url(url)
urls = re.compile('href="(/servlet/BookDetailsPL[^"]+)"').findall(data) urls = re.compile('href="(/servlet/BookDetailsPL[^"]+)"').findall(data)
@ -24,21 +25,20 @@ def get_ids(key, value):
def lookup(id): def lookup(id):
logger.debug('lookup %s', id) logger.debug('lookup %s', id)
return {} data = {}
def get_data(id):
info = {}
base = 'http://www.abebooks.com'
url = '%s/servlet/SearchResults?isbn=%s&sts=t' % (base, id) url = '%s/servlet/SearchResults?isbn=%s&sts=t' % (base, id)
data = read_url(url) html = read_url(url)
urls = re.compile('href="(/servlet/BookDetailsPL[^"]+)"').findall(data) urls = re.compile('href="(/servlet/BookDetailsPL[^"]+)"').findall(html)
keys = {
'pubdate': 'date'
}
if urls: if urls:
details = '%s%s' % (base, urls[0]) details = '%s%s' % (base, urls[0])
data = read_url(details) html = read_url(details)
doc = lxml.html.document_fromstring(data) doc = lxml.html.document_fromstring(html)
for e in doc.xpath("//*[contains(@id, 'biblio')]"): for e in doc.xpath("//*[contains(@id, 'biblio')]"):
key = e.attrib['id'].replace('biblio-', '') key = e.attrib['id'].replace('biblio-', '')
value = e.text_content() value = e.text_content()
if value and key not in ('bookcondition', 'binding'): if value and key not in ('bookcondition', 'binding', 'edition-amz'):
info[key] = value data[keys.get(key, key)] = value
return info return data

View file

@ -37,6 +37,6 @@ def find(title, author=None, publisher=None, date=None):
done.add(isbn) done.add(isbn)
if len(isbn) == 10: if len(isbn) == 10:
done.add(stdnum.isbn.to_isbn13(isbn)) done.add(stdnum.isbn.to_isbn13(isbn))
if len(isbn) == 13: if len(isbn) == 13 and isbn.startswith('978'):
done.add(stdnum.isbn.to_isbn10(isbn)) done.add(stdnum.isbn.to_isbn10(isbn))
return results return results

View file

@ -45,9 +45,9 @@ def lookup(id):
} }
for key in keys: for key in keys:
r[key] = find_re(data, '<span class="title">%s:</span>(.*?)</li>'% re.escape(keys[key])) r[key] = find_re(data, '<span class="title">%s:</span>(.*?)</li>'% re.escape(keys[key]))
if r[key] == '--': if r[key] == '--' or not r[key]:
r[key] = '' del r[key]
if key == 'pages' and r[key]: if key == 'pages' and key in r:
r[key] = int(r[key]) r[key] = int(r[key])
desc = find_re(data, '<h2>Description:<\/h2>(.*?)<div ') desc = find_re(data, '<h2>Description:<\/h2>(.*?)<div ')
desc = desc.replace('<br /><br />', ' ').replace('<br /> ', ' ').replace('<br />', ' ') desc = desc.replace('<br /><br />', ' ').replace('<br /> ', ' ').replace('<br />', ' ')

View file

@ -62,6 +62,8 @@ def api_requestPeering(app, user_id, username, message):
def api_acceptPeering(app, user_id, username, message): def api_acceptPeering(app, user_id, username, message):
user = User.get(user_id) user = User.get(user_id)
logger.debug('incoming acceptPeering event: pending: %s', user.pending) logger.debug('incoming acceptPeering event: pending: %s', user.pending)
if user and user.peered:
return True
if user and user.pending == 'sent': if user and user.pending == 'sent':
if not user.info: if not user.info:
user.info = {} user.info = {}

View file

@ -120,7 +120,7 @@ def publish_node(app):
for u in user.models.User.query.filter_by(queued=True): for u in user.models.User.query.filter_by(queued=True):
logger.debug('adding queued node... %s', u.id) logger.debug('adding queued node... %s', u.id)
state.nodes.queue('add', u.id) state.nodes.queue('add', u.id)
state.check_nodes = PeriodicCallback(lambda: check_nodes(app), 60000) state.check_nodes = PeriodicCallback(lambda: check_nodes(app), 120000)
state.check_nodes.start() state.check_nodes.start()
def check_nodes(app): def check_nodes(app):
@ -135,7 +135,7 @@ def start(app):
application = Application([ application = Application([
(r"/get/(.*)", ShareHandler, dict(app=app)), (r"/get/(.*)", ShareHandler, dict(app=app)),
(r".*", NodeHandler, dict(app=app)), (r".*", NodeHandler, dict(app=app)),
]) ], gzip=True)
if not os.path.exists(settings.ssl_cert_path): if not os.path.exists(settings.ssl_cert_path):
settings.server['cert'] = cert.generate_ssl() settings.server['cert'] = cert.generate_ssl()

View file

@ -6,13 +6,15 @@ from Queue import Queue
from threading import Thread from threading import Thread
import json import json
import socket import socket
from StringIO import StringIO
import gzip
import urllib2
from datetime import datetime from datetime import datetime
import os import os
import ox import ox
import ed25519 import ed25519
import urllib2 from tornado.ioloop import PeriodicCallback
import settings import settings
import user.models import user.models
@ -42,6 +44,8 @@ class Node(object):
self.vk = ed25519.VerifyingKey(key, encoding=ENCODING) self.vk = ed25519.VerifyingKey(key, encoding=ENCODING)
self.go_online() self.go_online()
logger.debug('new Node %s online=%s', self.user_id, self.online) logger.debug('new Node %s online=%s', self.user_id, self.online)
self._ping = PeriodicCallback(self.ping, 120000)
self._ping.start()
@property @property
def url(self): def url(self):
@ -120,6 +124,8 @@ class Node(object):
self.online = False self.online = False
return None return None
data = r.read() data = r.read()
if r.headers.get('content-encoding', None) == 'gzip':
data = gzip.GzipFile(fileobj=StringIO(data)).read()
sig = r.headers.get('X-Ed25519-Signature') sig = r.headers.get('X-Ed25519-Signature')
if sig and self._valid(data, sig): if sig and self._valid(data, sig):
response = json.loads(data) response = json.loads(data)
@ -151,6 +157,13 @@ class Node(object):
pass pass
return False return False
def ping(self):
with self._app.app_context():
if self.online:
self.online = self.can_connect()
else:
self.go_online()
def go_online(self): def go_online(self):
self.resolve() self.resolve()
u = self.user u = self.user
@ -179,7 +192,7 @@ class Node(object):
self.online = False self.online = False
trigger_event('status', { trigger_event('status', {
'id': self.user_id, 'id': self.user_id,
'status': 'online' if self.online else 'offline' 'online': self.online
}) })
def pullChanges(self): def pullChanges(self):
@ -199,7 +212,7 @@ class Node(object):
self.online = False self.online = False
trigger_event('status', { trigger_event('status', {
'id': self.user_id, 'id': self.user_id,
'status': 'offline' 'online': self.online
}) })
r = False r = False
logger.debug('pushedChanges %s %s', r, self.user_id) logger.debug('pushedChanges %s %s', r, self.user_id)
@ -210,7 +223,7 @@ class Node(object):
r = self.request(action, settings.preferences['username'], u.info.get('message')) r = self.request(action, settings.preferences['username'], u.info.get('message'))
else: else:
r = self.request(action, u.info.get('message')) r = self.request(action, u.info.get('message'))
if r: if r != None:
u.queued = False u.queued = False
if 'message' in u.info: if 'message' in u.info:
del u.info['message'] del u.info['message']
@ -237,7 +250,21 @@ class Node(object):
self._opener.addheaders = zip(headers.keys(), headers.values()) self._opener.addheaders = zip(headers.keys(), headers.values())
r = self._opener.open(url, timeout=self.TIMEOUT) r = self._opener.open(url, timeout=self.TIMEOUT)
if r.getcode() == 200: if r.getcode() == 200:
if r.headers.get('content-encoding', None) == 'gzip':
content = gzip.GzipFile(fileobj=r).read()
else:
'''
content = ''
for chunk in iter(lambda: r.read(1024*1024), ''):
content += chunk
item.transferprogress = len(content) / item.info['size']
item.save()
trigger_event('transfer', {
'id': item.id, 'progress': item.transferprogress
})
'''
content = r.read() content = r.read()
t2 = datetime.now() t2 = datetime.now()
duration = (t2-t1).total_seconds() duration = (t2-t1).total_seconds()
if duration: if duration:
@ -308,7 +335,6 @@ class Nodes(Thread):
from user.models import User from user.models import User
self._nodes[user_id] = Node(self, User.get_or_create(user_id)) self._nodes[user_id] = Node(self, User.get_or_create(user_id))
else: else:
logger.debug('bring existing node online %s', user_id)
if not self._nodes[user_id].online: if not self._nodes[user_id].online:
self._nodes[user_id].go_online() self._nodes[user_id].go_online()

View file

@ -5,10 +5,14 @@ from sqlalchemy.sql.expression import and_, not_, or_, ClauseElement
from datetime import datetime from datetime import datetime
import unicodedata import unicodedata
from sqlalchemy.sql import operators, extract from sqlalchemy.sql import operators, extract
from sqlalchemy.orm import load_only
import utils import utils
import settings import settings
import logging
logger = logging.getLogger('oxflask.query')
def get_operator(op, type='str'): def get_operator(op, type='str'):
return { return {
'str': { 'str': {
@ -56,6 +60,9 @@ class Parser(object):
} }
... ...
''' '''
logger.debug('parse_condition %s', condition)
if not 'value' in condition:
return None
k = condition.get('key', '*') k = condition.get('key', '*')
if not k: if not k:
k = '*' k = '*'
@ -105,34 +112,7 @@ class Parser(object):
q = ~q q = ~q
return q return q
elif k == 'list': elif k == 'list':
''' nickname, name = v.split(':', 1)
q = Q(id=0)
l = v.split(":")
if len(l) == 1:
vqs = Volume.objects.filter(name=v, user=user)
if vqs.count() == 1:
v = vqs[0]
q = Q(files__instances__volume__id=v.id)
elif len(l) >= 2:
l = (l[0], ":".join(l[1:]))
lqs = list(List.objects.filter(name=l[1], user__username=l[0]))
if len(lqs) == 1 and lqs[0].accessible(user):
l = lqs[0]
if l.query.get('static', False) == False:
data = l.query
q = self.parse_conditions(data.get('conditions', []),
data.get('operator', '&'),
user, l.user)
else:
q = Q(id__in=l.items.all())
if exclude:
q = ~q
else:
q = Q(id=0)
'''
l = v.split(":")
nickname = l[0]
name = ':'.join(l[1:])
if nickname: if nickname:
p = self._user.query.filter_by(nickname=nickname).first() p = self._user.query.filter_by(nickname=nickname).first()
v = '%s:%s' % (p.id, name) v = '%s:%s' % (p.id, name)
@ -150,6 +130,16 @@ class Parser(object):
data = l._query data = l._query
q = self.parse_conditions(data.get('conditions', []), q = self.parse_conditions(data.get('conditions', []),
data.get('operator', '&')) data.get('operator', '&'))
else:
if exclude:
q = (self._find.key == 'list') & (self._find.value == v)
ids = [i.id
for i in self._model.query.join(self._find).filter(q).group_by(self._model.id).options(load_only('id'))]
if ids:
q = ~self._model.id.in_(ids)
else:
q = (self._model.id != 0)
else: else:
q = (self._find.key == 'list') & (self._find.value == v) q = (self._find.key == 'list') & (self._find.value == v)
return q return q

View file

@ -4,7 +4,6 @@ from __future__ import division
import os import os
from copy import deepcopy from copy import deepcopy
import subprocess
import json import json
from oxflask.api import actions from oxflask.api import actions
@ -12,7 +11,7 @@ from oxflask.shortcuts import returns_json
import models import models
from utils import get_position_by_id from utils import update_dict
import settings import settings
import state import state
@ -24,7 +23,14 @@ logger = logging.getLogger('oml.user.api')
@returns_json @returns_json
def init(request): def init(request):
''' '''
this is an init request to test stuff takes {
}
returns {
config
user
preferences
ui
}
''' '''
response = {} response = {}
if os.path.exists(settings.oml_config_path): if os.path.exists(settings.oml_config_path):
@ -43,26 +49,14 @@ def init(request):
return response return response
actions.register(init) actions.register(init)
def update_dict(root, data):
for key in data:
keys = map(lambda p: p.replace('\0', '\\.'), key.replace('\\.', '\0').split('.'))
value = data[key]
p = root
while len(keys)>1:
key = keys.pop(0)
if isinstance(p, list):
p = p[get_position_by_id(p, key)]
else:
if key not in p:
p[key] = {}
p = p[key]
if value == None and keys[0] in p:
del p[keys[0]]
else:
p[keys[0]] = value
@returns_json @returns_json
def setPreferences(request): def setPreferences(request):
'''
takes {
key: value,
'sub.key': value
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
update_dict(settings.preferences, data) update_dict(settings.preferences, data)
return settings.preferences return settings.preferences
@ -70,6 +64,12 @@ actions.register(setPreferences)
@returns_json @returns_json
def setUI(request): def setUI(request):
'''
takes {
key: value,
'sub.key': value
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
update_dict(settings.ui, data) update_dict(settings.ui, data)
return settings.ui return settings.ui
@ -77,6 +77,11 @@ actions.register(setUI)
@returns_json @returns_json
def getUsers(request): def getUsers(request):
'''
returns {
users: []
}
'''
users = [] users = []
for u in models.User.query.filter(models.User.id!=settings.USER_ID).all(): for u in models.User.query.filter(models.User.id!=settings.USER_ID).all():
users.append(u.json()) users.append(u.json())
@ -87,7 +92,20 @@ actions.register(getUsers)
@returns_json @returns_json
def getLists(request): def getLists(request):
'''
returns {
lists: []
}
'''
from item.models import Item
lists = [] lists = []
lists.append({
'id': '',
'items': Item.query.count(),
'name': 'Libraries',
'type': 'libraries',
'user': '',
})
for u in models.User.query.filter((models.User.peered==True)|(models.User.id==settings.USER_ID)): for u in models.User.query.filter((models.User.peered==True)|(models.User.id==settings.USER_ID)):
lists += u.lists_json() lists += u.lists_json()
return { return {
@ -95,30 +113,47 @@ def getLists(request):
} }
actions.register(getLists) actions.register(getLists)
def validate_query(query):
for condition in query['conditions']:
if not list(sorted(condition.keys())) in (
['conditions', 'operator'],
['key', 'operator', 'value']
):
raise Exception('invalid query condition', condition)
@returns_json @returns_json
def addList(request): def addList(request):
'''
takes {
name
items
query
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
logger.debug('addList %s', data)
user_id = settings.USER_ID user_id = settings.USER_ID
l = models.List.get(user_id, data['name']) if 'query' in data:
if not l: validate_query(data['query'])
if data['name']:
l = models.List.create(user_id, data['name'], data.get('query')) l = models.List.create(user_id, data['name'], data.get('query'))
if 'items' in data: if 'items' in data:
l.add_items(data['items']) l.add_items(data['items'])
return l.json() return l.json()
else:
raise Exception('name not set')
return {} return {}
actions.register(addList, cache=False) actions.register(addList, cache=False)
@returns_json
def removeList(request):
data = json.loads(request.form['data']) if 'data' in request.form else {}
l = models.List.get(data['id'])
if l:
l.remove()
return {}
actions.register(removeList, cache=False)
@returns_json @returns_json
def editList(request): def editList(request):
'''
takes {
id
name
query
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
logger.debug('editList %s', data) logger.debug('editList %s', data)
l = models.List.get_or_create(data['id']) l = models.List.get_or_create(data['id'])
@ -126,6 +161,7 @@ def editList(request):
if 'name' in data: if 'name' in data:
l.name = data['name'] l.name = data['name']
if 'query' in data: if 'query' in data:
validate_query(data['query'])
l._query = data['query'] l._query = data['query']
if l.type == 'static' and name != l.name: if l.type == 'static' and name != l.name:
Changelog.record(state.user(), 'editlist', name, {'name': l.name}) Changelog.record(state.user(), 'editlist', name, {'name': l.name})
@ -133,8 +169,29 @@ def editList(request):
return l.json() return l.json()
actions.register(editList, cache=False) actions.register(editList, cache=False)
@returns_json
def removeList(request):
'''
takes {
id
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {}
l = models.List.get(data['id'])
if l:
l.remove()
return {}
actions.register(removeList, cache=False)
@returns_json @returns_json
def addListItems(request): def addListItems(request):
'''
takes {
list
items
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
if data['list'] == ':': if data['list'] == ':':
from item.models import Item from item.models import Item
@ -153,6 +210,12 @@ actions.register(addListItems, cache=False)
@returns_json @returns_json
def removeListItems(request): def removeListItems(request):
'''
takes {
list
items
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
l = models.List.get(data['list']) l = models.List.get(data['list'])
if l: if l:
@ -163,6 +226,11 @@ actions.register(removeListItems, cache=False)
@returns_json @returns_json
def sortLists(request): def sortLists(request):
'''
takes {
ids
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
n = 0 n = 0
logger.debug('sortLists %s', data) logger.debug('sortLists %s', data)
@ -177,6 +245,12 @@ actions.register(sortLists, cache=False)
@returns_json @returns_json
def editUser(request): def editUser(request):
'''
takes {
id
nickname
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
if 'nickname' in data: if 'nickname' in data:
p = models.User.get_or_create(data['id']) p = models.User.get_or_create(data['id'])
@ -187,6 +261,12 @@ actions.register(editUser, cache=False)
@returns_json @returns_json
def requestPeering(request): def requestPeering(request):
'''
takes {
id
message
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
if len(data.get('id', '')) != 43: if len(data.get('id', '')) != 43:
logger.debug('invalid user id') logger.debug('invalid user id')
@ -203,6 +283,12 @@ actions.register(requestPeering, cache=False)
@returns_json @returns_json
def acceptPeering(request): def acceptPeering(request):
'''
takes {
id
message
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
if len(data.get('id', '')) != 43: if len(data.get('id', '')) != 43:
logger.debug('invalid user id') logger.debug('invalid user id')
@ -218,6 +304,12 @@ actions.register(acceptPeering, cache=False)
@returns_json @returns_json
def rejectPeering(request): def rejectPeering(request):
'''
takes {
id
message
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
if len(data.get('id', '')) != 43: if len(data.get('id', '')) != 43:
logger.debug('invalid user id') logger.debug('invalid user id')
@ -232,6 +324,12 @@ actions.register(rejectPeering, cache=False)
@returns_json @returns_json
def removePeering(request): def removePeering(request):
'''
takes {
id
message
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
if len(data.get('id', '')) != 43: if len(data.get('id', '')) != 43:
logger.debug('invalid user id') logger.debug('invalid user id')
@ -246,6 +344,10 @@ actions.register(removePeering, cache=False)
@returns_json @returns_json
def cancelPeering(request): def cancelPeering(request):
'''
takes {
}
'''
data = json.loads(request.form['data']) if 'data' in request.form else {} data = json.loads(request.form['data']) if 'data' in request.form else {}
if len(data.get('id', '')) != 43: if len(data.get('id', '')) != 43:
logger.debug('invalid user id') logger.debug('invalid user id')
@ -260,29 +362,12 @@ actions.register(cancelPeering, cache=False)
@returns_json @returns_json
def getActivity(request): def getActivity(request):
'''
return {
activity
progress
}
'''
return state.activity return state.activity
actions.register(getActivity, cache=False) actions.register(getActivity, cache=False)
@returns_json
def selectFolder(request):
data = json.loads(request.form['data']) if 'data' in request.form else {}
cmd = ['./ctl', 'ui', 'folder']
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
stdout, stderr = p.communicate()
path = stdout.decode('utf-8').strip()
return {
'path': path
}
actions.register(selectFolder, cache=False)
@returns_json
def selectFile(request):
data = json.loads(request.form['data']) if 'data' in request.form else {}
cmd = ['./ctl', 'ui', 'file']
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
stdout, stderr = p.communicate()
path = stdout.decode('utf-8').strip()
return {
'path': path
}
actions.register(selectFile, cache=False)

View file

@ -66,7 +66,13 @@ class User(db.Model):
return state.nodes and state.nodes.check_online(self.id) return state.nodes and state.nodes.check_online(self.id)
def lists_json(self): def lists_json(self):
return [l.json() for l in self.lists.order_by('position')] return [{
'id': '%s:' % ('' if self.id == settings.USER_ID else self.nickname),
'name': 'Library',
'type': 'library',
'items': self.items.count(),
'user': self.nickname if self.id != settings.USER_ID else settings.preferences['username'],
}] + [l.json() for l in self.lists.order_by('position')]
def update_peering(self, peered, username=None): def update_peering(self, peered, username=None):
was_peering = self.peered was_peering = self.peered
@ -132,15 +138,13 @@ class List(db.Model):
@classmethod @classmethod
def get(cls, user_id, name=None): def get(cls, user_id, name=None):
if not name: if name is None:
user_id, name = cls.get_user_name(user_id) user_id, name = cls.get_user_name(user_id)
return cls.query.filter_by(user_id=user_id, name=name).first() return cls.query.filter_by(user_id=user_id, name=name).first()
@classmethod @classmethod
def get_user_name(cls, user_id): def get_user_name(cls, user_id):
l = user_id.split(':') nickname, name = user_id.split(':', 1)
nickname = l[0]
name = ':'.join(l[1:])
if nickname: if nickname:
user = User.query.filter_by(nickname=nickname).first() user = User.query.filter_by(nickname=nickname).first()
user_id = user.id user_id = user.id
@ -149,19 +153,22 @@ class List(db.Model):
return user_id, name return user_id, name
@classmethod @classmethod
def get_or_create(cls, user_id, name=None): def get_or_create(cls, user_id, name=None, query=None):
if not name: if name is None:
user_id, name = cls.get_user_name(user_id) user_id, name = cls.get_user_name(user_id)
l = cls.get(user_id, name) l = cls.get(user_id, name)
if not l: if not l:
l = cls(name=name, user_id=user_id) l = cls.create(user_id, name, query)
db.session.add(l)
db.session.commit()
return l return l
@classmethod @classmethod
def create(cls, user_id, name, query=None): def create(cls, user_id, name, query=None):
l = cls(name=name, user_id=user_id) prefix = name
n = 2
while cls.get(user_id, name):
name = '%s [%s]' % (prefix, n)
n += 1
l = cls(user_id=user_id, name=name)
l._query = query l._query = query
l.type = 'smart' if l._query else 'static' l.type = 'smart' if l._query else 'static'
l.position = cls.query.filter_by(user_id=user_id).count() l.position = cls.query.filter_by(user_id=user_id).count()

View file

@ -2,17 +2,22 @@
# vi:si:et:sw=4:sts=4:ts=4 # vi:si:et:sw=4:sts=4:ts=4
from __future__ import division from __future__ import division
import os
import Image import Image
from StringIO import StringIO from StringIO import StringIO
import re import re
import stdnum.isbn import stdnum.isbn
import socket import socket
import cStringIO
import gzip
import ox import ox
import ed25519 import ed25519
from meta.utils import normalize_isbn, find_isbns from meta.utils import normalize_isbn, find_isbns
import logging
logger = logging.getLogger('oml.utils')
ENCODING='base64' ENCODING='base64'
@ -108,3 +113,38 @@ def get_public_ipv6():
s.close() s.close()
return ip return ip
def update_dict(root, data):
for key in data:
keys = map(lambda part: part.replace('\0', '\\.'), key.replace('\\.', '\0').split('.'))
value = data[key]
p = root
while len(keys)>1:
key = keys.pop(0)
if isinstance(p, list):
p = p[get_position_by_id(p, key)]
else:
if key not in p:
p[key] = {}
p = p[key]
if value == None and keys[0] in p:
del p[keys[0]]
else:
p[keys[0]] = value
def remove_empty_folders(prefix):
empty = []
for root, folders, files in os.walk(prefix):
if not folders and not files:
empty.append(root)
for folder in empty:
remove_empty_tree(folder)
def remove_empty_tree(leaf):
while leaf:
if not os.path.exists(leaf):
leaf = os.path.dirname(leaf)
elif os.path.isdir(leaf) and not os.listdir(leaf):
logger.debug('rmdir %s', leaf)
os.rmdir(leaf)
else:
break

View file

@ -45,7 +45,6 @@ oml.ui.appPanel = function() {
oml.$ui.importExportDialog oml.$ui.importExportDialog
&& oml.$ui.importExportDialog.is(':visible') && oml.$ui.importExportDialog.is(':visible')
) { ) {
Ox.print('AAAAAAAA')
oml.$ui.importExportDialog.select(page); oml.$ui.importExportDialog.select(page);
} else { } else {
oml.$ui.importExportDialog = oml.ui.importExportDialog(page).open(); oml.$ui.importExportDialog = oml.ui.importExportDialog(page).open();
@ -56,6 +55,7 @@ oml.ui.appPanel = function() {
that.reload = function() { that.reload = function() {
Ox.Request.cancel(); Ox.Request.cancel();
Ox.Request.clearCache(); Ox.Request.clearCache();
oml.unbindEvent();
oml.$ui.appPanel.remove(); oml.$ui.appPanel.remove();
oml.$ui.appPanel = oml.ui.appPanel().appendTo(Ox.$body); oml.$ui.appPanel = oml.ui.appPanel().appendTo(Ox.$body);
return that; return that;

View file

@ -16,7 +16,10 @@ oml.ui.backButton = function() {
}) })
.bindEvent({ .bindEvent({
click: function() { click: function() {
oml.UI.set({item: ''}); oml.UI.set({
item: '',
itemView: 'info'
});
} }
}); });

View file

@ -72,8 +72,10 @@ oml.ui.browser = function() {
unique: 'id' unique: 'id'
}) })
.bindEvent({ .bindEvent({
open: function() { open: function(data) {
if (that.value(data.ids[0], 'mediastate') == 'available') {
oml.UI.set({itemView: 'book'}); oml.UI.set({itemView: 'book'});
}
}, },
select: function(data) { select: function(data) {
oml.UI.set({ oml.UI.set({

View file

@ -27,7 +27,6 @@ oml.ui.deleteItemsDialog = function() {
}, function() { }, function() {
oml.UI.set({listSelection: []}); oml.UI.set({listSelection: []});
Ox.Request.clearCache('find'); Ox.Request.clearCache('find');
oml.$ui.folders.updateElement();
oml.$ui.list.updateElement(); oml.$ui.list.updateElement();
}); });
}); });

View file

@ -62,7 +62,6 @@ oml.ui.filter = function(id) {
); );
}, },
select: function(data) { select: function(data) {
Ox.print('UI FILTER STATE', ui._filterState)
// fixme: cant index be an empty array, instead of -1? // fixme: cant index be an empty array, instead of -1?
// FIXME: this is still incorrect when deselecting a filter item // FIXME: this is still incorrect when deselecting a filter item
// makes a selected item in another filter disappear // makes a selected item in another filter disappear

View file

@ -59,7 +59,6 @@ oml.ui.filtersOuterPanel = function() {
}, },
oml_find: function() { oml_find: function() {
var previousUI = oml.UI.getPrevious(); var previousUI = oml.UI.getPrevious();
Ox.print('WTF', ui, oml.user.ui, Object.keys(ui), Object.keys(previousUI));
ui._filterState.forEach(function(data, index) { ui._filterState.forEach(function(data, index) {
if (!Ox.isEqual(data.selected, previousUI._filterState[index].selected)) { if (!Ox.isEqual(data.selected, previousUI._filterState[index].selected)) {
oml.$ui.filters[index].options( oml.$ui.filters[index].options(
@ -72,7 +71,6 @@ oml.ui.filtersOuterPanel = function() {
); );
} }
if (!Ox.isEqual(data.find, previousUI._filterState[index].find)) { if (!Ox.isEqual(data.find, previousUI._filterState[index].find)) {
Ox.print('::::', index, 'UNEQUAL', data.find, previousUI._filterState[index].find)
if (!ui.showFilters) { if (!ui.showFilters) {
oml.$ui.filters[index].options({ oml.$ui.filters[index].options({
_selected: data.selected _selected: data.selected

View file

@ -188,7 +188,6 @@ oml.ui.findElement = function() {
$select.superValue = $select.value; $select.superValue = $select.value;
$select.value = function(value) { $select.value = function(value) {
if (arguments.length == 1) { if (arguments.length == 1) {
Ox.print('I AM HERE')
$select.options({title: value == 'all' ? 'data' : value}); $select.options({title: value == 'all' ? 'data' : value});
} }
$select.superValue.apply($select, arguments); $select.superValue.apply($select, arguments);

View file

@ -2,8 +2,6 @@
oml.ui.findForm = function(list) { oml.ui.findForm = function(list) {
//Ox.print('FIND FORM LIST QUERY', list.query);
var ui = oml.user.ui, var ui = oml.user.ui,
that = Ox.Element(), that = Ox.Element(),
@ -19,7 +17,7 @@ oml.ui.findForm = function(list) {
title: Ox._('List'), title: Ox._('List'),
type: 'item', type: 'item',
values: ui._lists.filter(function(list) { values: ui._lists.filter(function(list) {
return list.type != 'smart' return Ox.contains(['library', 'static'], list.type);
}).map(function(list) { }).map(function(list) {
return {id: list.id, title: list.title}; return {id: list.id, title: list.title};
}) })

View file

@ -3,8 +3,10 @@
oml.ui.folders = function() { oml.ui.folders = function() {
var ui = oml.user.ui, var ui = oml.user.ui,
username = oml.user.preferences.username,
userIndex, userIndex,
users,
$lists, $lists,
@ -28,6 +30,24 @@ oml.ui.folders = function() {
}; };
} }
function getFolderList(list) {
var index = users.map(function(user) {
return user.nickname;
}).indexOf(list.user);
return list.id == '' ? oml.$ui.librariesList
: Ox.endsWith(list.id, ':') ? oml.$ui.libraryList[index]
: oml.$ui.folderList[index];
}
function getUsersAndLists(callback) {
oml.getUsers(function() {
users = arguments[0];
oml.getLists(function(lists) {
callback(users, lists);
});
});
}
function selectList() { function selectList() {
var split = ui._list.split(':'), var split = ui._list.split(':'),
index = userIndex[split[0] || oml.user.preferences.username], index = userIndex[split[0] || oml.user.preferences.username],
@ -50,6 +70,15 @@ oml.ui.folders = function() {
$lists = []; $lists = [];
oml.$ui.folder = [];
oml.$ui.libraryList = [];
oml.$ui.folderList = [];
getUsersAndLists(function(users, lists) {
Ox.print('GOT USERS AND LISTS', users, lists);
userIndex = {};
$lists.push( $lists.push(
oml.$ui.librariesList = oml.ui.folderList({ oml.$ui.librariesList = oml.ui.folderList({
items: [ items: [
@ -57,16 +86,11 @@ oml.ui.folders = function() {
id: '', id: '',
name: Ox._('All Libraries'), name: Ox._('All Libraries'),
type: 'libraries', type: 'libraries',
items: -1 items: Ox.getObjectById(lists, '').items
} }
] ]
}) })
.bindEvent({ .bindEvent({
load: function() {
oml.api.find({query: getFind('')}, function(result) {
oml.$ui.librariesList.value('', 'items', result.data.items);
});
},
select: function() { select: function() {
oml.UI.set({find: getFind('')}); oml.UI.set({find: getFind('')});
oml.$ui.librariesList.options({selected: ['']}); oml.$ui.librariesList.options({selected: ['']});
@ -74,7 +98,7 @@ oml.ui.folders = function() {
selectnext: function() { selectnext: function() {
oml.UI.set(Ox.extend( oml.UI.set(Ox.extend(
{find: getFind(':')}, {find: getFind(':')},
'showFolder.' + oml.user.preferences.username, 'showFolder.' + username,
true true
)); ));
}, },
@ -84,15 +108,6 @@ oml.ui.folders = function() {
); );
oml.$ui.librariesList.$body.css({height: '16px'}); // FIXME! oml.$ui.librariesList.$body.css({height: '16px'}); // FIXME!
oml.$ui.folder = [];
oml.$ui.libraryList = [];
oml.$ui.folderList = [];
oml.getUsersAndLists(function(users, lists) {
Ox.print('GOT USERS AND LISTS', users, lists);
userIndex = {};
users.forEach(function(user, index) { users.forEach(function(user, index) {
var $content, var $content,
@ -107,11 +122,7 @@ oml.ui.folders = function() {
oml.$ui.folder[index] = Ox.CollapsePanel({ oml.$ui.folder[index] = Ox.CollapsePanel({
collapsed: false, collapsed: false,
extras: [ extras: [
oml.ui.statusIcon( oml.ui.statusIcon(user, index),
!oml.user.online && index ? 'unknown'
: user.online ? 'connected'
: 'disconnected'
),
{}, {},
Ox.Button({ Ox.Button({
style: 'symbol', style: 'symbol',
@ -162,7 +173,7 @@ oml.ui.folders = function() {
id: libraryId, id: libraryId,
name: Ox._('Library'), name: Ox._('Library'),
type: 'library', type: 'library',
items: -1 items: Ox.getObjectById(lists, libraryId).items
} }
] ]
}) })
@ -170,15 +181,6 @@ oml.ui.folders = function() {
add: function() { add: function() {
!index && oml.addList(); !index && oml.addList();
}, },
load: function() {
oml.api.find({
query: getFind(libraryId)
}, function(result) {
oml.$ui.libraryList[index].value(
libraryId, 'items', result.data.items
);
});
},
select: function(data) { select: function(data) {
oml.UI.set({find: getFind(data.ids[0])}); oml.UI.set({find: getFind(data.ids[0])});
}, },
@ -186,16 +188,20 @@ oml.ui.folders = function() {
oml.UI.set({find: getFind(items[0].id)}); oml.UI.set({find: getFind(items[0].id)});
}, },
selectprevious: function() { selectprevious: function() {
var userId = !index ? null : users[index - 1].id, // FIXME: ugly
set = { var set, user, userLists;
find: getFind( if (!index) {
!index set = {find: getFind('')};
? '' } else {
: Ox.last(lists[userId]).id user = users[index - 1].nickname;
) userLists = lists.filter(function(list) {
}; return list.user == user;
if (userId) { });
Ox.extend(set, 'showFolder.' + userId, true); set = {find: getFind(
!userLists.length ? (user == oml.user.preferences.username ? '' : user) + ':'
: Ox.last(userLists).id
)};
Ox.extend(set, 'showFolder.' + user, true);
} }
oml.UI.set(set); oml.UI.set(set);
} }
@ -219,14 +225,6 @@ oml.ui.folders = function() {
key_control_d: function() { key_control_d: function() {
oml.addList(ui._list); oml.addList(ui._list);
}, },
load: function() {
// FIXME: too much
items.forEach(function(item) {
oml.api.find({query: getFind(item.id)}, function(result) {
oml.$ui.folderList[index].value(item.id, 'items', result.data.items);
});
});
},
move: function(data) { move: function(data) {
lists[user.id] = data.ids.map(function(listId) { lists[user.id] = data.ids.map(function(listId) {
return Ox.getObjectById(items, listId); return Ox.getObjectById(items, listId);
@ -273,32 +271,53 @@ oml.ui.folders = function() {
}; };
that.updateItems = function() { that.updateItems = function(items) {
oml.getUsersAndLists(function(users, lists) { Ox.print('UPDATE ITEMS', items);
Ox.Request.clearCache('find'); var $list;
$lists.forEach(function($list) { if (arguments.length == 0) {
$list.reloadList(); oml.getLists(function(lists) {
lists.forEach(function(list) {
$list = getFolderList(list);
if ($list) {
$list.value(list.id, 'items', list.items);
}
}); });
}); });
} else {
$list = $lists.filter(function($list) {
return $list.options('selected').length;
})[0];
if ($list && !Ox.isEmpty($list.value(ui._list))) {
$list.value(ui._list, 'items', items);
}
}
}; };
that.updateOwnLists = function(callback) { that.updateOwnLists = function(callback) {
oml.getUsersAndLists(function(users, lists) { oml.getLists(function(lists) {
var items = lists.filter(function(list) { var items = lists.filter(function(list) {
return list.user == oml.user.preferences.username return list.user == oml.user.preferences.username
&& list.type != 'library'; && list.type != 'library';
}); });
oml.$ui.folder[0].$content
.css({height: 16 + items.length * 16 + 'px'});
oml.$ui.folderList[0].options({ oml.$ui.folderList[0].options({
items: items items: items
}) })
.css({height: items.length * 16 + 'px'}) .css({height: items.length * 16 + 'px'})
.size(); .size();
oml.$ui.folder[0].$content
.css({height: 16 + items.length * 16 + 'px'});
callback && callback(); callback && callback();
}); });
}; };
oml.bindEvent({
activity: function(data) {
if (data.activity == 'import') {
that.updateItems();
}
}
});
return that.updateElement(); return that.updateElement();
}; };

View file

@ -34,6 +34,8 @@ oml.ui.identifyDialog = function(data) {
originalData = Ox.clone(data, true), originalData = Ox.clone(data, true),
selected = data.mainid ? 'id' : 'title',
idValue, titleValue, idValue, titleValue,
$idInputs, $idButtons = {}, $idInputs, $idButtons = {},
@ -59,7 +61,7 @@ oml.ui.identifyDialog = function(data) {
$titlePanel = Ox.SplitPanel({ $titlePanel = Ox.SplitPanel({
elements: [ elements: [
{element: $titleForm, size: 96}, {element: $titleForm, size: 96},
{element: renderResults([])} {element: Ox.Element()}
], ],
orientation: 'vertical' orientation: 'vertical'
}), }),
@ -72,7 +74,7 @@ oml.ui.identifyDialog = function(data) {
{id: 'title', title: Ox._('Find by Title')} {id: 'title', title: Ox._('Find by Title')}
], ],
selectable: true, selectable: true,
selected: 'id' value: selected
}) })
.css({ .css({
width: '768px', width: '768px',
@ -81,7 +83,8 @@ oml.ui.identifyDialog = function(data) {
}) })
.bindEvent({ .bindEvent({
change: function(data) { change: function(data) {
$innerPanel.options({selected: data.value}); selected = data.value;
$innerPanel.options({selected: selected});
} }
}) })
.appendTo($bar), .appendTo($bar),
@ -92,7 +95,7 @@ oml.ui.identifyDialog = function(data) {
{id: 'title', element: $titlePanel} {id: 'title', element: $titlePanel}
], ],
orientation: 'horizontal', orientation: 'horizontal',
selected: 'id', selected: selected,
size: 768 size: 768
}), }),
@ -122,16 +125,16 @@ oml.ui.identifyDialog = function(data) {
}) })
.bindEvent({ .bindEvent({
click: function() { click: function() {
Ox.print('$$$', idValue);
var edit = Ox.extend( var edit = Ox.extend(
{id: data.id}, {id: data.id},
$innerPanel.options('selected') == 'id' $innerPanel.options('selected') == 'id'
? idValue ? idValue || {mainid: ''}
: titleValue : titleValue
); );
that.options({content: Ox.LoadingScreen().start()}); that.options({content: Ox.LoadingScreen().start()});
that.disableButtons(); that.disableButtons();
oml.api.edit(edit, function(result) { oml.api.edit(edit, function(result) {
Ox.print('EDITED', result.data);
that.close(); that.close();
Ox.Request.clearCache('find'); Ox.Request.clearCache('find');
oml.$ui.browser.reloadList(true); oml.$ui.browser.reloadList(true);
@ -145,28 +148,36 @@ oml.ui.identifyDialog = function(data) {
content: $outerPanel, content: $outerPanel,
fixedSize: true, fixedSize: true,
height: 384, height: 384,
removeOnClose: true,
title: Ox._('Identify Book'), title: Ox._('Identify Book'),
width: 768 width: 768
}); });
function disableButtons() {
Ox.forEach(selected == 'id' ? $idButtons : $titleButtons, function($button) {
$button.options({disabled: true});
});
}
function findMetadata(data) { function findMetadata(data) {
disableButtons();
$titlePanel.replaceElement(1, Ox.LoadingScreen().start()); $titlePanel.replaceElement(1, Ox.LoadingScreen().start());
oml.api.findMetadata(data, function(result) { oml.api.findMetadata(data, function(result) {
Ox.print('GOT RESULTS', result.data);
// FIXME: CONCAT HERE // FIXME: CONCAT HERE
var items = result.data.items.map(function(item, index) { var items = result.data.items.map(function(item, index) {
return Ox.extend({index: (index + 1).toString()}, item); return Ox.extend({index: (index + 1).toString()}, item);
}); });
updateTitleButtons();
$titlePanel.replaceElement(1, renderResults(items)); $titlePanel.replaceElement(1, renderResults(items));
}); });
} }
function getMetadata(key, value) { function getMetadata(key, value) {
disableButtons();
$idPanel.replaceElement(1, Ox.LoadingScreen().start()); $idPanel.replaceElement(1, Ox.LoadingScreen().start());
oml.api.getMetadata(Ox.extend({}, key, value), function(result) { oml.api.getMetadata(Ox.extend({}, key, value), function(result) {
Ox.print('GOT RESULT', result.data);
$idForm = renderIdForm(result.data); $idForm = renderIdForm(result.data);
$idPreview = oml.ui.infoView(result.data); $idPreview = Ox.isEmpty(data) ? Ox.Element() : oml.ui.infoView(result.data);
$idPanel $idPanel
.replaceElement(0, $idForm) .replaceElement(0, $idForm)
.replaceElement(1, $idPreview); .replaceElement(1, $idPreview);
@ -174,7 +185,6 @@ oml.ui.identifyDialog = function(data) {
} }
function idInputValues(key, values) { function idInputValues(key, values) {
Ox.print('WTF,', $idInputs);
var $input = $idInputs[ids.map(function(id) { var $input = $idInputs[ids.map(function(id) {
return id.id; return id.id;
}).indexOf(key)]; }).indexOf(key)];
@ -190,30 +200,13 @@ oml.ui.identifyDialog = function(data) {
return values; return values;
} }
function titleInputValue(key, value) {
var $input = $titleInputs[keys.map(function(key) {
return key.id;
}).indexOf(key)];
if (Ox.isUndefined(value)) {
value = $input.value();
if (key == 'author') {
value = value ? value.split(', ') : [];
}
} else {
$input.value(
key == 'author' ? (value || []).join(', ') : value
);
}
return value;
}
function isEmpty(data) { function isEmpty(data) {
return Ox.every(data, Ox.isEmpty); return Ox.every(data, Ox.isEmpty);
} }
function isOriginal(data) { function isOriginal(data) {
return Ox.every(data, function(value, key) { return Ox.every(Object.keys(data), function(key) {
return value == originalData[key]; return data[key] == originalData[key];
}); });
} }
@ -268,7 +261,7 @@ oml.ui.identifyDialog = function(data) {
}); });
}); });
getMetadata(id.id, data.value, function() { getMetadata(id.id, data.value, function() {
// ... Ox.print('GOT METADATA');
updateIdButtons(); updateIdButtons();
}); });
} }
@ -335,15 +328,27 @@ oml.ui.identifyDialog = function(data) {
}) })
.bindEvent({ .bindEvent({
click: function() { click: function() {
var key, value;
Ox.forEach(ids, function(id) {
var values = idInputValues(id.id);
if (values[0]) {
key = id.id;
value = values[1];
return false;
}
});
getMetadata(key, value, function() {
// ...
})
Ox.print('NOT IMPLEMENTED') Ox.print('NOT IMPLEMENTED')
} }
}) })
.appendTo($element); .appendTo($element);
updateIdButtons();
return $element; return $element;
} }
function renderResults(items) { function renderResults(items) {
Ox.print('LIST ITEMS::::', items);
var $list = Ox.TableList({ var $list = Ox.TableList({
columns: [ columns: [
{ {
@ -375,7 +380,6 @@ oml.ui.identifyDialog = function(data) {
select: function(data) { select: function(data) {
var index = data.ids[0], mainid; var index = data.ids[0], mainid;
mainid = $list.value(index, 'mainid'); mainid = $list.value(index, 'mainid');
Ox.print('MAINID', mainid)
titleValue = Ox.extend({}, mainid, $list.value(index, mainid)); titleValue = Ox.extend({}, mainid, $list.value(index, mainid));
$results.replaceElement(1, Ox.LoadingScreen().start()); $results.replaceElement(1, Ox.LoadingScreen().start());
oml.api.getMetadata(titleValue, function(result) { oml.api.getMetadata(titleValue, function(result) {
@ -476,6 +480,23 @@ oml.ui.identifyDialog = function(data) {
return $element; return $element;
} }
function titleInputValue(key, value) {
var $input = $titleInputs[keys.map(function(key) {
return key.id;
}).indexOf(key)];
if (Ox.isUndefined(value)) {
value = $input.value();
if (key == 'author') {
value = value ? value.split(', ') : [];
}
} else {
$input.value(
key == 'author' ? (value || []).join(', ') : value
);
}
return value;
}
function updateIdButtons() { function updateIdButtons() {
var data = {}, empty, original; var data = {}, empty, original;
ids.forEach(function(id) { ids.forEach(function(id) {
@ -486,7 +507,7 @@ oml.ui.identifyDialog = function(data) {
$idButtons.clear.options({disabled: empty}); $idButtons.clear.options({disabled: empty});
$idButtons.reset.options({disabled: original}); $idButtons.reset.options({disabled: original});
$idButtons.find.options({disabled: empty}); $idButtons.find.options({disabled: empty});
that[original ? 'disableButton' : 'enableButton']('update'); that && that[original ? 'disableButton' : 'enableButton']('update');
} }
function updateTitleButtons() { function updateTitleButtons() {

View file

@ -50,8 +50,8 @@ oml.ui.importExportDialog = function(selected) {
that = Ox.Dialog({ that = Ox.Dialog({
buttons: [ buttons: [
Ox.Button({ Ox.Button({
id: 'close', id: 'hide',
title: Ox._('Close') title: Ox._('Hide')
}) })
.bindEvent({ .bindEvent({
click: function() { click: function() {
@ -87,7 +87,7 @@ oml.ui.importExportDialog = function(selected) {
$status = {}, $status = {},
$progressButton = {}; $progressButton = {};
oml.getUsersAndLists(function() { oml.getLists(function() {
oml.api.getActivity(function(result) { oml.api.getActivity(function(result) {
var isActive = !Ox.isEmpty(result.data), var isActive = !Ox.isEmpty(result.data),
activity = result.data.activity; activity = result.data.activity;
@ -98,7 +98,6 @@ oml.ui.importExportDialog = function(selected) {
progress: [0,42] progress: [0,42]
}; };
*/ */
Ox.print(result.data, '!!!!!!!!')
$innerPanel $innerPanel
.replaceElement(0, .replaceElement(0,
activity == 'import' ? renderActivity(result.data) activity == 'import' ? renderActivity(result.data)
@ -208,6 +207,12 @@ oml.ui.importExportDialog = function(selected) {
$form = Ox.Form({ $form = Ox.Form({
items: selected == 'import' ? [ items: selected == 'import' ? [
Ox.Input({ Ox.Input({
autocomplete: function(value, callback) {
oml.api.autocompleteFolder({path: value}, function(result) {
callback(result.data.items);
});
},
autocompleteSelect: true,
changeOnKeypress: true, changeOnKeypress: true,
id: 'path', id: 'path',
label: 'Source Path', label: 'Source Path',
@ -216,7 +221,7 @@ oml.ui.importExportDialog = function(selected) {
}), }),
Ox.SelectInput({ Ox.SelectInput({
id: 'list', id: 'list',
inputValue: oml.validateName(Ox._('Untitled'), getListNames()), inputValue: oml.getValidName(Ox._('Untitled'), getListNames()),
inputWidth: 224, inputWidth: 224,
items: getListItems('import'), items: getListItems('import'),
label: 'Destination', label: 'Destination',
@ -229,8 +234,8 @@ oml.ui.importExportDialog = function(selected) {
Ox.Select({ Ox.Select({
id: 'mode', id: 'mode',
items: [ items: [
{id: 'copy', title: Ox._('Copy files')}, {id: 'copy', title: Ox._('Copy (keep files in source path)')},
{id: 'move', title: Ox._('Move files')} {id: 'move', title: Ox._('Move (delete files from source path)')}
], ],
label: Ox._('Import Mode'), label: Ox._('Import Mode'),
labelWidth: 128, labelWidth: 128,
@ -255,10 +260,10 @@ oml.ui.importExportDialog = function(selected) {
Ox.Select({ Ox.Select({
id: 'mode', id: 'mode',
items: [ items: [
{id: 'keep', title: Ox._('Keep existing files')}, {id: 'add', title: Ox._('Add (keep files in destination path)')},
{id: 'remove', title: Ox._('Remove existing files')} {id: 'replace', title: Ox._('Replace (delete files from destination path)')}
], ],
label: Ox._('Import Mode'), label: Ox._('Export Mode'),
labelWidth: 128, labelWidth: 128,
width: 480 width: 480
}) })
@ -268,15 +273,8 @@ oml.ui.importExportDialog = function(selected) {
.bindEvent({ .bindEvent({
change: function(data) { change: function(data) {
var values = $form.values(); 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, getListNames()))
}
}
$activityButton[selected].options({ $activityButton[selected].options({
disabled: !values.path //|| !values.list disabled: !values.path
}); });
} }
}) })
@ -310,7 +308,11 @@ oml.ui.importExportDialog = function(selected) {
}) })
.bindEvent({ .bindEvent({
click: function() { click: function() {
var data = $form.values(); var data = $form.values(),
addList = data.list && !Ox.contains(
oml.getOwnListNames(),
data.list
);
$innerPanel.replaceElement(0, $innerPanel.replaceElement(0,
renderActivity({ renderActivity({
activity: 'import', activity: 'import',
@ -319,13 +321,15 @@ oml.ui.importExportDialog = function(selected) {
}) })
); );
$label['export'].show(); $label['export'].show();
(addList ? oml.addList : Ox.noop)(false, false, data.list, function() {
oml.api.import({ oml.api.import({
list: data.list, // FIXME: WRONG for Library list: data.list,
mode: data.mode, mode: data.mode,
path: data.path, path: data.path,
}, function() { }, function() {
// ... // ...
}) });
});
} }
}) })
.appendTo($element); .appendTo($element);
@ -341,7 +345,6 @@ oml.ui.importExportDialog = function(selected) {
} }
function setProgress(data) { function setProgress(data) {
Ox.print('SET PROGRESS', data, $progress)
var progress = data.status ? 1 var progress = data.status ? 1
: !data.progress[0] || !data.progress[1] ? -1 : !data.progress[0] || !data.progress[1] ? -1
: data.progress[0] / data.progress[1]; : data.progress[0] / data.progress[1];
@ -379,7 +382,6 @@ oml.ui.importExportDialog = function(selected) {
oml.bindEvent({ oml.bindEvent({
activity: function(data) { activity: function(data) {
Ox.print('activity', arguments);
setProgress(data); setProgress(data);
setStatus(data); setStatus(data);
setButton(data); setButton(data);

View file

@ -321,6 +321,11 @@ oml.ui.infoView = function(identifyData) {
tooltip: isEditable ? oml.getEditTooltip() : '', tooltip: isEditable ? oml.getEditTooltip() : '',
value: data.title || '' value: data.title || ''
}) })
.bindEvent({
submit: function(data) {
editMetadata('title', data.value);
}
})
.css({ .css({
fontSize: '13px', fontSize: '13px',
fontWeight: 'bold' fontWeight: 'bold'
@ -353,7 +358,9 @@ oml.ui.infoView = function(identifyData) {
fontWeight: 'bold' fontWeight: 'bold'
}) })
.bindEvent({ .bindEvent({
// ... submit: function(data) {
editMetadata('author', value.split(', '));
}
}) })
) )
.appendTo($info); .appendTo($info);
@ -502,12 +509,13 @@ oml.ui.infoView = function(identifyData) {
// FIXME: identify dialog should call this too // FIXME: identify dialog should call this too
function editMetadata(key, value) { function editMetadata(key, value) {
var edit; var edit;
Ox.print('EM', key, value, data[key])
if (value != data[key]) { if (value != data[key]) {
edit = Ox.extend({id: ui.item}, key, value); edit = Ox.extend({id: ui.item}, key, value);
oml.api.edit(edit, function(result) { oml.api.edit(edit, function(result) {
Ox.Request.clearCache('find'); Ox.Request.clearCache('find');
oml.$ui.browser.reloadList(); oml.$ui.browser.reloadList();
that.updateElement(result.data, $data); //that.updateElement(result.data, $info);
}); });
} }
} }

View file

@ -50,6 +50,14 @@ oml.ui.list = function() {
} }
}, },
init: function(data) { init: function(data) {
Ox.print('MAIN LIST INIT', data);
if (ui.find.conditions.length == 0 || (
ui.find.conditions.length == 1
&& ui.find.conditions[0].key == 'list'
&& ui.find.conditions[0].operator == '=='
)) {
oml.$ui.folders.updateItems(data.items);
}
oml.$ui.statusbar.set('total', data); oml.$ui.statusbar.set('total', data);
}, },
key_control_delete: function() { key_control_delete: function() {
@ -58,6 +66,15 @@ oml.ui.list = function() {
oml.ui.deleteItemsDialog().open(); oml.ui.deleteItemsDialog().open();
} }
}, },
key_shift_enter: function() {
var selected = that.options('selected');
if (selected.length) {
oml.UI.set({
item: selected[0],
itemView: 'book'
});
}
},
open: function(data) { open: function(data) {
oml.UI.set({ oml.UI.set({
item: data.ids[0], item: data.ids[0],

View file

@ -65,14 +65,18 @@ oml.ui.listDialog = function() {
}) })
.bindEvent({ .bindEvent({
change: function(data) { change: function(data) {
var value = oml.validateName(data.value, listNames); var value = oml.getValidName(
data.value || Ox._('Untitled'),
listNames.filter(function(listName) {
return listName != listData.name;
})
);
that.options({title: getTitle(':' + value)}) that.options({title: getTitle(':' + value)})
$nameInput.value(value); $nameInput.value(value);
// FIXME: UGLY // FIXME: UGLY
listNames[listNames.indexOf(listData.name)] = value; listNames[listNames.indexOf(listData.name)] = value;
listData.name = value; listData.name = value;
// //
Ox.print(listData.name, 'LIST NAMES ???', listNames)
oml.api.editList({ oml.api.editList({
id: ui._list, id: ui._list,
name: value name: value
@ -94,13 +98,13 @@ oml.ui.listDialog = function() {
}) })
.appendTo($content), .appendTo($content),
$findForm; $findForm;
Ox.print('DEBUG:', list, listData)
if (listData.type == 'smart') { if (listData.type == 'smart') {
$findForm = oml.ui.findForm(listData) $findForm = oml.ui.findForm(listData)
.css({marginTop: '8px'}) .css({marginTop: '8px'})
.appendTo($content); .appendTo($content);
} }
that.options({content: $content}); that.options({content: $content});
$nameInput.focusInput(true);
}); });
function getTitle(list) { function getTitle(list) {

View file

@ -19,7 +19,7 @@ oml.ui.openButton = function() {
oml.UI.set({item: ui.listSelection[0]}); oml.UI.set({item: ui.listSelection[0]});
}, },
oml_listselection: function() { oml_listselection: function() {
that.update(); that.updateElement();
} }
}); });

View file

@ -12,7 +12,7 @@ oml.ui.preferencesDialog = function() {
title: 'Username', title: 'Username',
value: preferences.username, value: preferences.username,
placeholder: 'anonymous', placeholder: 'anonymous',
help: 'Your username doesn\'t have to be your real name, and you can change it at any time. You can also leave it blank, in which case you will appear as "anonymous". Any characters other than leading, trailing or consecutive spaces are okay.' help: 'Your username doesn\'t have to be your real name, and you can change it at any time. You can also leave it blank, in which case you will appear as "anonymous". Any characters other than colons and leading, trailing or consecutive spaces are okay.'
}, },
{ {
id: 'contact', id: 'contact',
@ -25,12 +25,24 @@ oml.ui.preferencesDialog = function() {
{ {
id: 'libraryPath', id: 'libraryPath',
title: 'Library Path', title: 'Library Path',
autocomplete: function(value, callback) {
oml.api.autocompleteFolder({path: value}, function(result) {
callback(result.data.items);
});
},
autocompleteSelect: true,
value: preferences.libraryPath, value: preferences.libraryPath,
help: 'The directory in which your "Books" folder is located. This is where your media files are stored. It can be on your local machine, but just as well on an external drive or networked volume.' help: 'The directory in which your "Books" folder is located. This is where your media files are stored. It can be on your local machine, but just as well on an external drive or networked volume.'
}, },
{ {
id: 'importPath', id: 'importPath',
title: 'Import Path', title: 'Import Path',
autocomplete: function(value, callback) {
oml.api.autocompleteFolder({path: value}, function(result) {
callback(result.data.items);
});
},
autocompleteSelect: true,
value: preferences.importPath, value: preferences.importPath,
help: 'Any media files that you put in this folder will be added to your library. Once added, they will be removed from this folder.' help: 'Any media files that you put in this folder will be added to your library. Once added, they will be removed from this folder.'
} }
@ -376,6 +388,8 @@ oml.ui.preferencesDialog = function() {
width: 384 width: 384
}) })
: Ox.Input({ : Ox.Input({
autocomplete: item.autocomplete || null,
autocompleteSelect: item.autocompleteSelect || false,
label: Ox._(item.title), label: Ox._(item.title),
labelWidth: 128, labelWidth: 128,
placeholder: item.placeholder || '', placeholder: item.placeholder || '',
@ -415,6 +429,9 @@ oml.ui.preferencesDialog = function() {
change: function(data) { change: function(data) {
var key = data.id, var key = data.id,
value = data.data.value[0]; value = data.data.value[0];
if (key == 'username') {
value = getValidName(value, [], ':');
}
if (key in oml.config.user.preferences) { if (key in oml.config.user.preferences) {
oml.Preferences.set(key, value); oml.Preferences.set(key, value);
} else { } else {

View file

@ -26,6 +26,7 @@ oml.ui.rightPanel = function() {
oml.$ui.itemViewPanel.options({size: data.size}); oml.$ui.itemViewPanel.options({size: data.size});
}, },
oml_item: function(data) { oml_item: function(data) {
Ox.print('rightPanel, oml_item', data);
if (!!data.value != !!data.previousValue) { if (!!data.value != !!data.previousValue) {
that.options({selected: !ui.item ? 'list' : 'item'}); that.options({selected: !ui.item ? 'list' : 'item'});
} }

View file

@ -1,18 +1,10 @@
'use strict'; 'use strict';
oml.ui.statusIcon = function(status) { oml.ui.statusIcon = function(user, index) {
// FIXME: not only '-webkit' // FIXME: not only '-webkit'
var color = { var status = getStatus(user),
connected: [[64, 255, 64], [0, 192, 0]],
disconnected: [[255, 64, 64], [192, 0, 0]],
transferring: [[64, 255, 255], [0, 192, 192]],
unknown: [[255, 255, 64], [192, 192, 0]]
}[status].map(function(rgb) {
return 'rgb(' + rgb.join(', ') + ')';
}).join(', '),
that = Ox.Element({ that = Ox.Element({
tooltip: Ox._({ tooltip: Ox._({
connected: 'Connected', connected: 'Connected',
@ -24,7 +16,6 @@ oml.ui.statusIcon = function(status) {
width: '10px', width: '10px',
height: '10px', height: '10px',
margin: '3px', margin: '3px',
background: '-webkit-linear-gradient(bottom, ' + color + ')',
borderRadius: '5px' borderRadius: '5px'
}) })
.append( .append(
@ -33,11 +24,65 @@ oml.ui.statusIcon = function(status) {
width: '8px', width: '8px',
height: '8px', height: '8px',
margin: '1px', margin: '1px',
background: '-webkit-linear-gradient(top, ' + color + ')',
borderRadius: '4px' borderRadius: '4px'
}) })
); );
return that; render();
if (user) {
var superRemove = that.remove;
that.remove = function() {
oml.unbindEvent({
status: update
})
superRemove();
};
oml.bindEvent({
status: update
});
}
function getStatus(data) {
return !oml.user.online && index ? 'unknown'
: data.online ? 'connected'
: 'disconnected';
}
function render() {
var color = {
connected: [[64, 255, 64], [0, 192, 0]],
disconnected: [[255, 64, 64], [192, 0, 0]],
transferring: [[64, 255, 255], [0, 192, 192]],
unknown: [[255, 255, 64], [192, 192, 0]]
}[status].map(function(rgb) {
return 'rgb(' + rgb.join(', ') + ')';
}).join(', ');
that.options({
tooltip: Ox._({
connected: 'Connected',
disconnected: 'Disconnected',
transferring: 'Transferring'
}[status])
}).css({
background: '-webkit-linear-gradient(bottom, ' + color + ')',
});
that.find('div').css({
background: '-webkit-linear-gradient(top, ' + color + ')',
});
}
function update(data) {
if (data.id == user.id) {
var newStatus = getStatus(data);
if (status != newStatus) {
status = newStatus;
render();
}
}
}
return that;
}; };

View file

@ -35,7 +35,9 @@ oml.ui.statusbar = function() {
}); });
function getText(data) { function getText(data) {
return Ox.toTitleCase(Ox.formatCount(data.items, 'book')); return Ox.toTitleCase(Ox.formatCount(data.items, 'book')) + (
data.items ? ', ' + Ox.formatValue(data.size, 'B') : ''
);
} }
that.set = function(key, data) { that.set = function(key, data) {

View file

@ -21,7 +21,7 @@ oml.ui.transfersDialog = function() {
visible: true, visible: true,
width: id == 'title' ? 240 width: id == 'title' ? 240
: id == 'transferadded' ? 144 : id == 'transferadded' ? 144
: id == 'transferprogress' ? 80 : id == 'transferprogress' ? 80 - Ox.UI.SCROLLBAR_SIZE
: key.columnWidth : key.columnWidth
}; };
}), }),
@ -40,6 +40,7 @@ oml.ui.transfersDialog = function() {
}), callback); }), callback);
}, },
keys: ['author'], keys: ['author'],
scrollbarVisible: true,
sort: [{key: 'transferprogress', operator: '-'}], sort: [{key: 'transferprogress', operator: '-'}],
unique: 'id' unique: 'id'
}), }),

View file

@ -38,7 +38,15 @@ oml.ui.usersDialog = function() {
width: 768 width: 768
}) })
.bindEvent({ .bindEvent({
open: function() {
oml.bindEvent({
peering: peering,
});
},
close: function() { close: function() {
oml.unbindEvent({
peering: peering,
});
if (ui.page == 'users') { if (ui.page == 'users') {
oml.UI.set({page: ''}); oml.UI.set({page: ''});
} }
@ -112,6 +120,10 @@ oml.ui.usersDialog = function() {
.appendTo($users); .appendTo($users);
}); });
var peering = Ox.throttle(function() {
updateUsers();
}, 1000);
function renderSectionList(folder) { function renderSectionList(folder) {
var $list = Ox.TableList({ var $list = Ox.TableList({
@ -254,11 +266,15 @@ oml.ui.usersDialog = function() {
}) })
.bindEvent({ .bindEvent({
change: function(data) { change: function(data) {
var value = oml.validateName( var value = oml.getValidName(
data.value, data.value || 'anonymous',
users.map(function(user) { // FIXME: WRONG
return user.nickname; users.filter(function(user_) {
}) return user_.nickname != user.nickname;
}).map(function(u) {
return user_.nickname;
}),
':'
); );
this.value(value); this.value(value);
oml.api.editUser({ oml.api.editUser({
@ -447,9 +463,7 @@ oml.ui.usersDialog = function() {
columns: [ columns: [
{ {
format: function(value, data) { format: function(value, data) {
return oml.ui.statusIcon( return oml.ui.statusIcon(data)
value ? 'connected' : 'disconnected'
)
.css({ .css({
margin: '2px 3px 3px 0' margin: '2px 3px 3px 0'
}); });
@ -581,7 +595,6 @@ oml.ui.usersDialog = function() {
}); });
} }
that.updateElement = function() { that.updateElement = function() {
that.options({ that.options({

View file

@ -1,8 +1,10 @@
oml.addList = function() { oml.addList = function() {
// addList(isSmart, isFrom) or addList(list) [=dupicate] // addList(isSmart, isFrom[, name[, callback]])
// or addList(list) [=duplicate]
var args = arguments, var args = arguments,
isDuplicate = args.length == 1, isDuplicate = args.length == 1,
isSmart, isFrom, list, listData, data, isSmart, isFrom, name, callback,
list, listData, data,
username = oml.user.preferences.username; username = oml.user.preferences.username;
Ox.Request.clearCache('getLists'); Ox.Request.clearCache('getLists');
oml.api.getLists(function(result) { oml.api.getLists(function(result) {
@ -16,8 +18,10 @@ oml.addList = function() {
if (!isDuplicate) { if (!isDuplicate) {
isSmart = args[0]; isSmart = args[0];
isFrom = args[1]; isFrom = args[1];
name = args[2] || Ox._('Untitled');
callback = args[3];
data = { data = {
name: oml.validateName(Ox._('Untitled'), listNames), name: oml.getValidName(name, listNames),
type: !isSmart ? 'static' : 'smart' type: !isSmart ? 'static' : 'smart'
}; };
if (!isSmart) { if (!isSmart) {
@ -36,7 +40,7 @@ oml.addList = function() {
list = args[0]; list = args[0];
listData = Ox.getObjectById(Ox.flatten(Ox.values(lists)), list); listData = Ox.getObjectById(Ox.flatten(Ox.values(lists)), list);
data = Ox.extend({ data = Ox.extend({
name: oml.validateName(listData.name, listNames), name: oml.getValidName(listData.name, listNames),
type: listData.type type: listData.type
}, listData.query ? { }, listData.query ? {
query: listData.query query: listData.query
@ -74,6 +78,10 @@ oml.addList = function() {
$folderList = oml.$ui.folderList[0]; $folderList = oml.$ui.folderList[0];
oml.$ui.folder[0].options({collapsed: false}); // FIXME: SET UI! oml.$ui.folder[0].options({collapsed: false}); // FIXME: SET UI!
// FIXME: DOESN'T WORK // FIXME: DOESN'T WORK
if (
!oml.$ui.importExportDialog
|| !oml.$ui.importExportDialog.is(':visible')
) {
$folderList $folderList
.bindEventOnce({ .bindEventOnce({
load: function() { load: function() {
@ -93,7 +101,9 @@ oml.addList = function() {
oml.$ui.listDialog = oml.ui.listDialog().open(); oml.$ui.listDialog = oml.ui.listDialog().open();
} }
}); });
}
oml.$ui.folders.updateOwnLists(); oml.$ui.folders.updateOwnLists();
callback && callback();
}); });
} }
}; };
@ -267,7 +277,6 @@ oml.enableDragAndDrop = function($list, canMove) {
$list.bindEvent({ $list.bindEvent({
draganddropstart: function(data) { draganddropstart: function(data) {
Ox.print('DND START', data);
var $lists = oml.$ui.libraryList.concat(oml.$ui.folderList); var $lists = oml.$ui.libraryList.concat(oml.$ui.folderList);
drag.action = 'copy'; drag.action = 'copy';
drag.ids = $list.options('selected'); drag.ids = $list.options('selected');
@ -732,7 +741,6 @@ oml.getListData = function(list) {
oml.getListFoldersHeight = function() { oml.getListFoldersHeight = function() {
var ui = oml.user.ui; var ui = oml.user.ui;
return Object.keys(ui.showFolder).reduce(function(value, id, index) { return Object.keys(ui.showFolder).reduce(function(value, id, index) {
Ox.print('WTF WTF', index)
var items = oml.$ui.folderList[index].options('items').length; var items = oml.$ui.folderList[index].options('items').length;
return value + 16 + ui.showFolder[id] * (1 + items) * 16; return value + 16 + ui.showFolder[id] * (1 + items) * 16;
}, 16); }, 16);
@ -748,6 +756,35 @@ oml.getListFoldersWidth = function() {
); );
}; };
oml.getLists = function(callback) {
var ui = oml.user.ui;
Ox.Request.clearCache('getLists');
oml.api.getLists(function(result) {
var username = oml.user.preferences.username;
ui._lists = result.data.lists.map(function(list) {
// FIXME: 'editable' is notoriously vague
list.name = list.type == 'libraries' ? Ox._('Libraries')
: list.type == 'library' ? Ox._('Library') : list.name;
return Ox.extend(list, {
editable: list.user == username && list.type == 'static',
own: list.user == username,
title: (list.user ? list.user + ': ' : '') + list.name
});
});
callback(ui._lists);
});
};
oml.getOwnListNames = function() {
var ui = oml.user.ui,
username = oml.user.preferences.username;
return ui._lists.filter(function(list) {
return list.user == username;
}).filter(function(list) {
return list.name;
});
}
oml.getPageTitle = function(stateOrURL) { oml.getPageTitle = function(stateOrURL) {
var page = Ox.getObjectById( var page = Ox.getObjectById(
oml.config.pages, oml.config.pages,
@ -765,6 +802,22 @@ oml.getSortOperator = function(key) {
) ? '+' : '-'; ) ? '+' : '-';
}; };
oml.getUsers = function(callback) {
var users = [{
id: oml.user.id,
nickname: oml.user.preferences.username,
online: oml.user.online
}];
oml.api.getUsers(function(result) {
users = users.concat(
result.data.users.filter(function(user) {
return user.peered;
})
);
callback(users);
});
}
oml.getUsersAndLists = function(callback) { oml.getUsersAndLists = function(callback) {
var lists = [{ var lists = [{
id: '', id: '',
@ -811,12 +864,30 @@ oml.getUsersAndLists = function(callback) {
oml.$ui.mainMenu.updateElement(); oml.$ui.mainMenu.updateElement();
} }
ui._lists = lists; ui._lists = lists;
Ox.print('UI._LISTS', JSON.stringify(ui._lists)); Ox.print('GOT LISTS ::::', lists);
callback(users, lists); callback(users, lists);
}); });
}) })
}; };
oml.getValidName = function(value, names, chars) {
var index = 1, length = 256, suffix;
if (chars) {
value = value.replace(
new RegExp('[' + Ox.escapeRegExp(chars) + ']', 'g'),
''
);
}
value = Ox.clean(Ox.clean(value).slice(0, length));
names = names || [];
while (Ox.contains(names, value)) {
suffix = ' [' + (++index) + ']';
value = value.replace(/ \[\d+\]$/, '')
.slice(0, length - suffix.length) + suffix;
};
return value;
};
oml.hasDialogOrScreen = function() { oml.hasDialogOrScreen = function() {
return !!$('.OxDialog:visible').length return !!$('.OxDialog:visible').length
|| !!$('.OxFullscreen').length || !!$('.OxFullscreen').length
@ -834,7 +905,6 @@ oml.resizeFilters = function() {
oml.resizeListFolders = function() { oml.resizeListFolders = function() {
// FIXME: does this have to be here? // FIXME: does this have to be here?
Ox.print('RESIZING LIST FOLDERS', 'WIDTH', oml.getListFoldersWidth(), 'HEIGHT', oml.getListFoldersHeight())
var width = oml.getListFoldersWidth(), var width = oml.getListFoldersWidth(),
columnWidth = width - 58; columnWidth = width - 58;
oml.$ui.librariesList oml.$ui.librariesList
@ -842,7 +912,6 @@ oml.resizeListFolders = function() {
.resizeColumn('name', columnWidth); .resizeColumn('name', columnWidth);
Ox.forEach(oml.$ui.folder, function($folder, index) { Ox.forEach(oml.$ui.folder, function($folder, index) {
$folder.css({width: width + 'px'}); $folder.css({width: width + 'px'});
Ox.print('SHOULD BE:', width);
oml.$ui.libraryList[index] oml.$ui.libraryList[index]
.css({width: width + 'px'}) .css({width: width + 'px'})
.resizeColumn('name', columnWidth); .resizeColumn('name', columnWidth);
@ -881,18 +950,6 @@ oml.updateFilterMenus = function() {
}); });
}; };
oml.validateName = function(value, names) {
var index = 1, length = 256, suffix;
value = Ox.clean(Ox.clean(value).slice(0, length));
names = names || [];
while (Ox.contains(names, value)) {
suffix = ' [' + (++index) + ']';
value = value.replace(/ \[\d+\]$/, '')
.slice(0, length - suffix.length) + suffix;
};
return value;
};
oml.validatePublicKey = function(value) { oml.validatePublicKey = function(value) {
return /^[A-Za-z0-9+\/]{43}$/.test(value); return /^[A-Za-z0-9+\/]{43}$/.test(value);
}; };