diff --git a/README.md b/README.md
index 517e9ae..bceafd1 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ Networking
----------
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 SixSS (https://sixxs.net).
diff --git a/oml/api.py b/oml/api.py
index c9bb492..e4c9f09 100644
--- a/oml/api.py
+++ b/oml/api.py
@@ -2,5 +2,80 @@
# vi:si:et:sw=4:sts=4:ts=4
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 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)
diff --git a/oml/app.py b/oml/app.py
index b1701eb..e7f3a84 100644
--- a/oml/app.py
+++ b/oml/app.py
@@ -18,8 +18,7 @@ import item.models
import user.models
import item.person
-import item.api
-import user.api
+import api
import item.views
import commands
diff --git a/oml/downloads.py b/oml/downloads.py
index c716cb3..22633e6 100644
--- a/oml/downloads.py
+++ b/oml/downloads.py
@@ -23,7 +23,7 @@ class Downloads(Thread):
import item.models
for i in item.models.Item.query.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)
for p in i.users:
if state.nodes.check_online(p.id):
diff --git a/oml/item/api.py b/oml/item/api.py
index d0da9d0..a04407a 100644
--- a/oml/item/api.py
+++ b/oml/item/api.py
@@ -2,11 +2,12 @@
# vi:si:et:sw=4:sts=4:ts=4
from __future__ import division
-import logging
-
+import os
import json
+
from oxflask.api import actions
from oxflask.shortcuts import returns_json
+from sqlalchemy.orm import load_only
import query
@@ -18,12 +19,22 @@ import meta
import utils
+import logging
logger = logging.getLogger('oml.item.api')
@returns_json
def find(request):
'''
- find items
+ takes {
+ query {
+ conditions [{}]
+ operator string
+ }
+ group string
+ keys [string]
+ sort [{}]
+ range [int, int]
+ }
'''
response = {}
data = json.loads(request.form['data']) if 'data' in request.form else {}
@@ -31,7 +42,7 @@ def find(request):
if 'group' in q:
names = {}
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'])
if items:
qs = qs.filter(models.Find.item_id.in_(items))
@@ -58,16 +69,12 @@ def find(request):
else:
response['items'] = len(g)
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]
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'])
elif 'keys' in data:
- '''
- qs = qs[q['range'][0]:q['range'][1]]
- response['items'] = [p.json(data['keys']) for p in qs]
- '''
response['items'] = []
for i in q['qs'][q['range'][0]:q['range'][1]]:
j = i.json()
@@ -77,12 +84,18 @@ def find(request):
#from sqlalchemy.sql import func
#models.db.session.query(func.sum(models.Item.sort_size).label("size"))
#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
actions.register(find)
@returns_json
def get(request):
+ '''
+ takes {
+ id
+ keys
+ }
+ '''
response = {}
data = json.loads(request.form['data']) if 'data' in request.form else {}
item = models.Item.get(data['id'])
@@ -93,29 +106,48 @@ actions.register(get)
@returns_json
def edit(request):
+ '''
+ takes {
+ id
+ ...
+ }
+ setting identifier or base metadata is possible not both at the same time
+ '''
response = {}
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'])
keys = filter(lambda k: k in models.Item.id_keys, data.keys())
- logger.debug(item, keys)
- if item and keys and item.json()['mediastate'] == 'available':
- key = keys[0]
- logger.debug('update mainid %s %s', key, data[key])
- if key in ('isbn10', 'isbn13'):
- data[key] = utils.normalize_isbn(data[key])
- item.update_mainid(key, data[key])
- response = item.json()
+ logger.debug('edit of %s id keys: %s', item, keys)
+ if item and item.json()['mediastate'] == 'available':
+ if keys:
+ key = keys[0]
+ logger.debug('update mainid %s %s', key, data[key])
+ if key in ('isbn10', 'isbn13'):
+ data[key] = utils.normalize_isbn(data[key])
+ item.update_mainid(key, data[key])
+ 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:
logger.info('can only edit available items')
- response = item.json()
return response
actions.register(edit, cache=False)
@returns_json
def remove(request):
+ '''
+ takes {
+ id
+ }
+ '''
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']:
for i in models.Item.query.filter(models.Item.id.in_(data['ids'])):
i.remove_file()
@@ -132,10 +164,11 @@ def findMetadata(request):
date: string
}
returns {
- title: string,
- autor: [string],
- date: string,
+ items: [{
+ key: value
+ }]
}
+ key is one of the supported identifiers: isbn10, isbn13...
'''
response = {}
data = json.loads(request.form['data']) if 'data' in request.form else {}
@@ -146,18 +179,30 @@ actions.register(findMetadata)
@returns_json
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 {}
logger.debug('getMetadata %s', data)
key, value = data.iteritems().next()
if key in ('isbn10', 'isbn13'):
value = utils.normalize_isbn(value)
response = meta.lookup(key, value)
- response['mainid'] = key
+ if response:
+ response['mainid'] = key
return response
actions.register(getMetadata)
@returns_json
def download(request):
+ '''
+ takes {
+ id
+ }
+ '''
response = {}
data = json.loads(request.form['data']) if 'data' in request.form else {}
item = models.Item.get(data['id'])
@@ -170,6 +215,11 @@ actions.register(download, cache=False)
@returns_json
def cancelDownloads(request):
+ '''
+ takes {
+ ids
+ }
+ '''
response = {}
data = json.loads(request.form['data']) if 'data' in request.form else {}
ids = data['ids']
@@ -195,8 +245,21 @@ actions.register(scan, cache=False)
@returns_json
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 {}
logger.debug('api.import %s', data)
state.main.add_callback(state.websockets[0].put, json.dumps(['import', data]))
return {}
actions.register(_import, 'import', cache=False)
+
+@returns_json
+def cancelImport(request):
+ state.activity['cancel'] = True
+ return {}
+actions.register(cancelImport, cache=False)
diff --git a/oml/item/models.py b/oml/item/models.py
index 4d74bff..74931e7 100644
--- a/oml/item/models.py
+++ b/oml/item/models.py
@@ -33,6 +33,7 @@ from oxflask.db import MutableDict
from covers import covers
from changelog import Changelog
from websocket import trigger_event
+from utils import remove_empty_folders
logger = logging.getLogger('oml.item.model')
@@ -296,8 +297,8 @@ class Item(db.Model):
def extract_cover(self):
path = self.get_path()
- if not path:
- return getattr(media, self.meta['extensions']).cover(path)
+ if path:
+ return getattr(media, self.info['extension']).cover(path)
def update_cover(self):
cover = None
@@ -327,8 +328,6 @@ class Item(db.Model):
if mainid:
m = meta.lookup(mainid, self.meta[mainid])
self.meta.update(m)
- else:
- logger.debug('FIX UPDATE %s', mainid)
self.update()
def queue_download(self):
@@ -360,6 +359,7 @@ class Item(db.Model):
Changelog.record(u, 'additem', self.id, self.info)
self.update()
f.move()
+ self.update_cover()
trigger_event('transfer', {
'id': self.id, 'progress': 1
})
@@ -376,6 +376,7 @@ class Item(db.Model):
logger.debug('remove file %s', path)
if os.path.exists(path):
os.unlink(path)
+ remove_empty_folders(os.path.dirname(path))
db.session.delete(f)
user = state.user()
self.users.remove(user)
@@ -399,7 +400,7 @@ for key in config['itemKeys']:
col = db.Column(db.String(1000), index=True)
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.filter_keys = [k['id'] for k in config['itemKeys'] if k.get('filter')]
diff --git a/oml/item/scan.py b/oml/item/scan.py
index 0c247c8..b32ecfc 100644
--- a/oml/item/scan.py
+++ b/oml/item/scan.py
@@ -19,6 +19,10 @@ from changelog import Changelog
import media
from websocket import trigger_event
import state
+from utils import remove_empty_folders
+
+import logging
+logger = logging.getLogger('oml.item.scan')
extensions = ['epub', 'pdf', 'txt']
@@ -35,6 +39,29 @@ def remove_missing():
if dirty:
db.session.commit()
+def add_file(id, f, prefix):
+ user = state.user()
+ path = f[len(prefix):]
+ data = media.metadata(f)
+ 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.added = datetime.now()
+ item.scrape()
+ return file
+
def run_scan():
remove_missing()
with app.app_context():
@@ -42,7 +69,6 @@ def run_scan():
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):
@@ -61,29 +87,8 @@ def run_scan():
position += 1
id = media.get_id(f)
file = File.get(id)
- path = f[len(prefix):]
if not file:
- 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.added = datetime.now()
- item.scrape()
+ file = add_file(id, f, prefix)
added += 1
trigger_event('change', {})
@@ -93,18 +98,28 @@ def run_import(options=None):
with app.app_context():
prefs = settings.preferences
prefix = os.path.expanduser(options.get('path', prefs['importPath']))
+ if os.path.islink(prefix):
+ prefix = os.path.realpath(prefix)
if not prefix[-1] == '/':
prefix += '/'
prefix_books = os.path.join(os.path.expanduser(prefs['libraryPath']), 'Books/')
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', {
'activity': 'import',
'progress': [0, 0],
- 'status': {'code': 404, 'text': 'path not found'}
+ 'status': {'code': 404, 'text': error}
})
state.activity = {}
- user = User.get_or_create(settings.USER_ID)
+ return
listname = options.get('list')
if listname:
listitems = []
@@ -122,6 +137,7 @@ def run_import(options=None):
state.activity = {
'activity': 'import',
+ 'path': prefix,
'progress': [0, len(books)],
}
trigger_event('activity', state.activity)
@@ -133,7 +149,6 @@ def run_import(options=None):
continue
id = media.get_id(f)
file = File.get(id)
- path = f[len(prefix):]
if not file:
f_import = f
f = f.replace(prefix, prefix_imported)
@@ -142,45 +157,38 @@ def run_import(options=None):
shutil.move(f_import, f)
else:
shutil.copy(f_import, f)
- path = f[len(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 = add_file(id, f, prefix_books)
file.move()
+ item = file.item
if listname:
listitems.append(item.id)
added += 1
+ if state.activity.get('cancel'):
+ state.activity = {}
+ trigger_event('activity', {
+ 'activity': 'import',
+ 'status': {'code': 200, 'text': 'canceled'}
+ })
+ return
state.activity = {
'activity': 'import',
'progress': [position, len(books)],
- 'path': path,
+ 'path': prefix,
'added': added,
}
trigger_event('activity', state.activity)
- if listname:
- l = List.get_or_create(settings.USER_ID, listname)
- l.add_items(listitems)
+ if listname and listitems:
+ l = List.get(settings.USER_ID, listname)
+ if l:
+ l.add_items(listitems)
trigger_event('activity', {
'activity': 'import',
'progress': [position, len(books)],
+ 'path': prefix,
'status': {'code': 200, 'text': ''},
'added': added,
})
state.activity = {}
+ remove_empty_folders(prefix_books)
+ if options.get('mode') == 'move':
+ remove_empty_folders(prefix)
diff --git a/oml/media/__init__.py b/oml/media/__init__.py
index 20fa6a0..0bb6f6c 100644
--- a/oml/media/__init__.py
+++ b/oml/media/__init__.py
@@ -22,6 +22,8 @@ def get_id(f=None, data=None):
def metadata(f):
ext = f.split('.')[-1]
data = {}
+ data['extension'] = ext
+ data['size'] = os.stat(f).st_size
if ext == 'pdf':
info = pdf.info(f)
elif ext == 'epub':
diff --git a/oml/media/pdf.py b/oml/media/pdf.py
index b95c2bb..dfac951 100644
--- a/oml/media/pdf.py
+++ b/oml/media/pdf.py
@@ -71,9 +71,11 @@ def info(pdf):
with open(pdf, 'rb') as fd:
try:
pdfreader = PdfFileReader(fd)
+ data['pages'] = pdfreader.numPages
info = pdfreader.getDocumentInfo()
if info:
for key in info:
+ print key, info
if info[key]:
data[key[1:].lower()] = info[key]
xmp =pdfreader.getXmpMetadata()
diff --git a/oml/meta/__init__.py b/oml/meta/__init__.py
index 25c93f5..329a832 100644
--- a/oml/meta/__init__.py
+++ b/oml/meta/__init__.py
@@ -2,8 +2,7 @@
# vi:si:et:sw=4:sts=4:ts=4
from __future__ import division
-import logging
-logger = logging.getLogger('meta')
+import stdnum.isbn
import abebooks
import loc
@@ -13,6 +12,10 @@ import worldcat
import google
import duckduckgo
+import logging
+logger = logging.getLogger('meta')
+
+
providers = [
('openlibrary', 'olid'),
('loc', 'lccn'),
@@ -32,6 +35,8 @@ def find(title, author=None, publisher=None, date=None):
return results
def lookup(key, value):
+ if not isvalid_id(key, value):
+ return {}
data = {key: value}
ids = [(key, value)]
provider_data = {}
@@ -59,4 +64,13 @@ def lookup(key, value):
data[k_] = v_
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
diff --git a/oml/meta/abebooks.py b/oml/meta/abebooks.py
index c0f29bc..7ee715d 100644
--- a/oml/meta/abebooks.py
+++ b/oml/meta/abebooks.py
@@ -9,10 +9,11 @@ import lxml.html
import logging
logger = logging.getLogger('meta.abebooks')
+base = 'http://www.abebooks.com'
+
def get_ids(key, value):
ids = []
if key in ('isbn10', 'isbn13'):
- base = 'http://www.abebooks.com'
url = '%s/servlet/SearchResults?isbn=%s&sts=t' % (base, id)
data = read_url(url)
urls = re.compile('href="(/servlet/BookDetailsPL[^"]+)"').findall(data)
@@ -24,21 +25,20 @@ def get_ids(key, value):
def lookup(id):
logger.debug('lookup %s', id)
- return {}
-
-def get_data(id):
- info = {}
- base = 'http://www.abebooks.com'
+ data = {}
url = '%s/servlet/SearchResults?isbn=%s&sts=t' % (base, id)
- data = read_url(url)
- urls = re.compile('href="(/servlet/BookDetailsPL[^"]+)"').findall(data)
+ html = read_url(url)
+ urls = re.compile('href="(/servlet/BookDetailsPL[^"]+)"').findall(html)
+ keys = {
+ 'pubdate': 'date'
+ }
if urls:
details = '%s%s' % (base, urls[0])
- data = read_url(details)
- doc = lxml.html.document_fromstring(data)
+ html = read_url(details)
+ doc = lxml.html.document_fromstring(html)
for e in doc.xpath("//*[contains(@id, 'biblio')]"):
key = e.attrib['id'].replace('biblio-', '')
value = e.text_content()
- if value and key not in ('bookcondition', 'binding'):
- info[key] = value
- return info
+ if value and key not in ('bookcondition', 'binding', 'edition-amz'):
+ data[keys.get(key, key)] = value
+ return data
diff --git a/oml/meta/duckduckgo.py b/oml/meta/duckduckgo.py
index f5d2288..3400189 100644
--- a/oml/meta/duckduckgo.py
+++ b/oml/meta/duckduckgo.py
@@ -37,6 +37,6 @@ def find(title, author=None, publisher=None, date=None):
done.add(isbn)
if len(isbn) == 10:
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))
return results
diff --git a/oml/meta/lookupbyisbn.py b/oml/meta/lookupbyisbn.py
index 469e1d2..1b50b83 100644
--- a/oml/meta/lookupbyisbn.py
+++ b/oml/meta/lookupbyisbn.py
@@ -45,9 +45,9 @@ def lookup(id):
}
for key in keys:
r[key] = find_re(data, '%s:(.*?)'% re.escape(keys[key]))
- if r[key] == '--':
- r[key] = ''
- if key == 'pages' and r[key]:
+ if r[key] == '--' or not r[key]:
+ del r[key]
+ if key == 'pages' and key in r:
r[key] = int(r[key])
desc = find_re(data, '
Description:<\/h2>(.*?)
', ' ').replace('
', ' ').replace('
', ' ')
diff --git a/oml/node/nodeapi.py b/oml/node/nodeapi.py
index e6ddf54..4405b5d 100644
--- a/oml/node/nodeapi.py
+++ b/oml/node/nodeapi.py
@@ -62,6 +62,8 @@ def api_requestPeering(app, user_id, username, message):
def api_acceptPeering(app, user_id, username, message):
user = User.get(user_id)
logger.debug('incoming acceptPeering event: pending: %s', user.pending)
+ if user and user.peered:
+ return True
if user and user.pending == 'sent':
if not user.info:
user.info = {}
diff --git a/oml/node/server.py b/oml/node/server.py
index c42482a..66ca02f 100644
--- a/oml/node/server.py
+++ b/oml/node/server.py
@@ -120,7 +120,7 @@ def publish_node(app):
for u in user.models.User.query.filter_by(queued=True):
logger.debug('adding queued node... %s', 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()
def check_nodes(app):
@@ -135,7 +135,7 @@ def start(app):
application = Application([
(r"/get/(.*)", ShareHandler, dict(app=app)),
(r".*", NodeHandler, dict(app=app)),
- ])
+ ], gzip=True)
if not os.path.exists(settings.ssl_cert_path):
settings.server['cert'] = cert.generate_ssl()
diff --git a/oml/nodes.py b/oml/nodes.py
index c47109e..8bdbfb6 100644
--- a/oml/nodes.py
+++ b/oml/nodes.py
@@ -6,13 +6,15 @@ from Queue import Queue
from threading import Thread
import json
import socket
-
+from StringIO import StringIO
+import gzip
+import urllib2
from datetime import datetime
import os
import ox
import ed25519
-import urllib2
+from tornado.ioloop import PeriodicCallback
import settings
import user.models
@@ -42,6 +44,8 @@ class Node(object):
self.vk = ed25519.VerifyingKey(key, encoding=ENCODING)
self.go_online()
logger.debug('new Node %s online=%s', self.user_id, self.online)
+ self._ping = PeriodicCallback(self.ping, 120000)
+ self._ping.start()
@property
def url(self):
@@ -120,6 +124,8 @@ class Node(object):
self.online = False
return None
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')
if sig and self._valid(data, sig):
response = json.loads(data)
@@ -151,6 +157,13 @@ class Node(object):
pass
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):
self.resolve()
u = self.user
@@ -179,7 +192,7 @@ class Node(object):
self.online = False
trigger_event('status', {
'id': self.user_id,
- 'status': 'online' if self.online else 'offline'
+ 'online': self.online
})
def pullChanges(self):
@@ -199,7 +212,7 @@ class Node(object):
self.online = False
trigger_event('status', {
'id': self.user_id,
- 'status': 'offline'
+ 'online': self.online
})
r = False
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'))
else:
r = self.request(action, u.info.get('message'))
- if r:
+ if r != None:
u.queued = False
if 'message' in u.info:
del u.info['message']
@@ -237,7 +250,21 @@ class Node(object):
self._opener.addheaders = zip(headers.keys(), headers.values())
r = self._opener.open(url, timeout=self.TIMEOUT)
if r.getcode() == 200:
- content = r.read()
+ 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()
+
t2 = datetime.now()
duration = (t2-t1).total_seconds()
if duration:
@@ -308,7 +335,6 @@ class Nodes(Thread):
from user.models import User
self._nodes[user_id] = Node(self, User.get_or_create(user_id))
else:
- logger.debug('bring existing node online %s', user_id)
if not self._nodes[user_id].online:
self._nodes[user_id].go_online()
diff --git a/oml/oxflask/query.py b/oml/oxflask/query.py
index 97341a4..ee14103 100644
--- a/oml/oxflask/query.py
+++ b/oml/oxflask/query.py
@@ -5,10 +5,14 @@ from sqlalchemy.sql.expression import and_, not_, or_, ClauseElement
from datetime import datetime
import unicodedata
from sqlalchemy.sql import operators, extract
+from sqlalchemy.orm import load_only
import utils
import settings
+import logging
+logger = logging.getLogger('oxflask.query')
+
def get_operator(op, type='str'):
return {
'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', '*')
if not k:
k = '*'
@@ -105,34 +112,7 @@ class Parser(object):
q = ~q
return q
elif k == 'list':
- '''
- 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:])
+ nickname, name = v.split(':', 1)
if nickname:
p = self._user.query.filter_by(nickname=nickname).first()
v = '%s:%s' % (p.id, name)
@@ -151,7 +131,17 @@ class Parser(object):
q = self.parse_conditions(data.get('conditions', []),
data.get('operator', '&'))
else:
- q = (self._find.key == 'list') & (self._find.value == v)
+ 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:
+ q = (self._find.key == 'list') & (self._find.value == v)
return q
elif key_type == 'date':
def parse_date(d):
diff --git a/oml/user/api.py b/oml/user/api.py
index 2793206..b700c6c 100644
--- a/oml/user/api.py
+++ b/oml/user/api.py
@@ -4,7 +4,6 @@ from __future__ import division
import os
from copy import deepcopy
-import subprocess
import json
from oxflask.api import actions
@@ -12,7 +11,7 @@ from oxflask.shortcuts import returns_json
import models
-from utils import get_position_by_id
+from utils import update_dict
import settings
import state
@@ -24,7 +23,14 @@ logger = logging.getLogger('oml.user.api')
@returns_json
def init(request):
'''
- this is an init request to test stuff
+ takes {
+ }
+ returns {
+ config
+ user
+ preferences
+ ui
+ }
'''
response = {}
if os.path.exists(settings.oml_config_path):
@@ -43,26 +49,14 @@ def init(request):
return response
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
def setPreferences(request):
+ '''
+ takes {
+ key: value,
+ 'sub.key': value
+ }
+ '''
data = json.loads(request.form['data']) if 'data' in request.form else {}
update_dict(settings.preferences, data)
return settings.preferences
@@ -70,6 +64,12 @@ actions.register(setPreferences)
@returns_json
def setUI(request):
+ '''
+ takes {
+ key: value,
+ 'sub.key': value
+ }
+ '''
data = json.loads(request.form['data']) if 'data' in request.form else {}
update_dict(settings.ui, data)
return settings.ui
@@ -77,6 +77,11 @@ actions.register(setUI)
@returns_json
def getUsers(request):
+ '''
+ returns {
+ users: []
+ }
+ '''
users = []
for u in models.User.query.filter(models.User.id!=settings.USER_ID).all():
users.append(u.json())
@@ -87,7 +92,20 @@ actions.register(getUsers)
@returns_json
def getLists(request):
+ '''
+ returns {
+ lists: []
+ }
+ '''
+ from item.models import Item
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)):
lists += u.lists_json()
return {
@@ -95,30 +113,47 @@ def getLists(request):
}
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
def addList(request):
+ '''
+ takes {
+ name
+ items
+ query
+ }
+ '''
data = json.loads(request.form['data']) if 'data' in request.form else {}
+ logger.debug('addList %s', data)
user_id = settings.USER_ID
- l = models.List.get(user_id, data['name'])
- if not l:
+ if 'query' in data:
+ validate_query(data['query'])
+ if data['name']:
l = models.List.create(user_id, data['name'], data.get('query'))
if 'items' in data:
l.add_items(data['items'])
return l.json()
+ else:
+ raise Exception('name not set')
return {}
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
def editList(request):
+ '''
+ takes {
+ id
+ name
+ query
+ }
+ '''
data = json.loads(request.form['data']) if 'data' in request.form else {}
logger.debug('editList %s', data)
l = models.List.get_or_create(data['id'])
@@ -126,6 +161,7 @@ def editList(request):
if 'name' in data:
l.name = data['name']
if 'query' in data:
+ validate_query(data['query'])
l._query = data['query']
if l.type == 'static' and name != l.name:
Changelog.record(state.user(), 'editlist', name, {'name': l.name})
@@ -133,8 +169,29 @@ def editList(request):
return l.json()
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
def addListItems(request):
+ '''
+ takes {
+ list
+ items
+ }
+ '''
data = json.loads(request.form['data']) if 'data' in request.form else {}
if data['list'] == ':':
from item.models import Item
@@ -153,6 +210,12 @@ actions.register(addListItems, cache=False)
@returns_json
def removeListItems(request):
+ '''
+ takes {
+ list
+ items
+ }
+ '''
data = json.loads(request.form['data']) if 'data' in request.form else {}
l = models.List.get(data['list'])
if l:
@@ -163,6 +226,11 @@ actions.register(removeListItems, cache=False)
@returns_json
def sortLists(request):
+ '''
+ takes {
+ ids
+ }
+ '''
data = json.loads(request.form['data']) if 'data' in request.form else {}
n = 0
logger.debug('sortLists %s', data)
@@ -177,6 +245,12 @@ actions.register(sortLists, cache=False)
@returns_json
def editUser(request):
+ '''
+ takes {
+ id
+ nickname
+ }
+ '''
data = json.loads(request.form['data']) if 'data' in request.form else {}
if 'nickname' in data:
p = models.User.get_or_create(data['id'])
@@ -187,6 +261,12 @@ actions.register(editUser, cache=False)
@returns_json
def requestPeering(request):
+ '''
+ takes {
+ id
+ message
+ }
+ '''
data = json.loads(request.form['data']) if 'data' in request.form else {}
if len(data.get('id', '')) != 43:
logger.debug('invalid user id')
@@ -203,6 +283,12 @@ actions.register(requestPeering, cache=False)
@returns_json
def acceptPeering(request):
+ '''
+ takes {
+ id
+ message
+ }
+ '''
data = json.loads(request.form['data']) if 'data' in request.form else {}
if len(data.get('id', '')) != 43:
logger.debug('invalid user id')
@@ -218,6 +304,12 @@ actions.register(acceptPeering, cache=False)
@returns_json
def rejectPeering(request):
+ '''
+ takes {
+ id
+ message
+ }
+ '''
data = json.loads(request.form['data']) if 'data' in request.form else {}
if len(data.get('id', '')) != 43:
logger.debug('invalid user id')
@@ -232,6 +324,12 @@ actions.register(rejectPeering, cache=False)
@returns_json
def removePeering(request):
+ '''
+ takes {
+ id
+ message
+ }
+ '''
data = json.loads(request.form['data']) if 'data' in request.form else {}
if len(data.get('id', '')) != 43:
logger.debug('invalid user id')
@@ -246,6 +344,10 @@ actions.register(removePeering, cache=False)
@returns_json
def cancelPeering(request):
+ '''
+ takes {
+ }
+ '''
data = json.loads(request.form['data']) if 'data' in request.form else {}
if len(data.get('id', '')) != 43:
logger.debug('invalid user id')
@@ -260,29 +362,12 @@ actions.register(cancelPeering, cache=False)
@returns_json
def getActivity(request):
+ '''
+ return {
+ activity
+ progress
+ }
+ '''
return state.activity
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)
diff --git a/oml/user/models.py b/oml/user/models.py
index 52d50d2..4e0a9d4 100644
--- a/oml/user/models.py
+++ b/oml/user/models.py
@@ -66,7 +66,13 @@ class User(db.Model):
return state.nodes and state.nodes.check_online(self.id)
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):
was_peering = self.peered
@@ -128,19 +134,17 @@ class List(db.Model):
user = db.relationship('User', backref=db.backref('lists', lazy='dynamic'))
items = db.relationship('Item', secondary=list_items,
- backref=db.backref('lists', lazy='dynamic'))
+ backref=db.backref('lists', lazy='dynamic'))
@classmethod
def get(cls, user_id, name=None):
- if not name:
+ if name is None:
user_id, name = cls.get_user_name(user_id)
return cls.query.filter_by(user_id=user_id, name=name).first()
@classmethod
def get_user_name(cls, user_id):
- l = user_id.split(':')
- nickname = l[0]
- name = ':'.join(l[1:])
+ nickname, name = user_id.split(':', 1)
if nickname:
user = User.query.filter_by(nickname=nickname).first()
user_id = user.id
@@ -149,19 +153,22 @@ class List(db.Model):
return user_id, name
@classmethod
- def get_or_create(cls, user_id, name=None):
- if not name:
+ def get_or_create(cls, user_id, name=None, query=None):
+ if name is None:
user_id, name = cls.get_user_name(user_id)
l = cls.get(user_id, name)
if not l:
- l = cls(name=name, user_id=user_id)
- db.session.add(l)
- db.session.commit()
+ l = cls.create(user_id, name, query)
return l
@classmethod
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.type = 'smart' if l._query else 'static'
l.position = cls.query.filter_by(user_id=user_id).count()
diff --git a/oml/utils.py b/oml/utils.py
index 5c691d3..0efbdd7 100644
--- a/oml/utils.py
+++ b/oml/utils.py
@@ -2,17 +2,22 @@
# vi:si:et:sw=4:sts=4:ts=4
from __future__ import division
+import os
import Image
from StringIO import StringIO
import re
import stdnum.isbn
import socket
+import cStringIO
+import gzip
import ox
import ed25519
from meta.utils import normalize_isbn, find_isbns
+import logging
+logger = logging.getLogger('oml.utils')
ENCODING='base64'
@@ -108,3 +113,38 @@ def get_public_ipv6():
s.close()
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
diff --git a/static/js/appPanel.js b/static/js/appPanel.js
index 6ec1a9f..335769d 100644
--- a/static/js/appPanel.js
+++ b/static/js/appPanel.js
@@ -45,7 +45,6 @@ oml.ui.appPanel = function() {
oml.$ui.importExportDialog
&& oml.$ui.importExportDialog.is(':visible')
) {
- Ox.print('AAAAAAAA')
oml.$ui.importExportDialog.select(page);
} else {
oml.$ui.importExportDialog = oml.ui.importExportDialog(page).open();
@@ -56,6 +55,7 @@ oml.ui.appPanel = function() {
that.reload = function() {
Ox.Request.cancel();
Ox.Request.clearCache();
+ oml.unbindEvent();
oml.$ui.appPanel.remove();
oml.$ui.appPanel = oml.ui.appPanel().appendTo(Ox.$body);
return that;
diff --git a/static/js/backButton.js b/static/js/backButton.js
index fdd7ab3..b600743 100644
--- a/static/js/backButton.js
+++ b/static/js/backButton.js
@@ -16,7 +16,10 @@ oml.ui.backButton = function() {
})
.bindEvent({
click: function() {
- oml.UI.set({item: ''});
+ oml.UI.set({
+ item: '',
+ itemView: 'info'
+ });
}
});
diff --git a/static/js/browser.js b/static/js/browser.js
index d012e4c..90629d2 100644
--- a/static/js/browser.js
+++ b/static/js/browser.js
@@ -72,8 +72,10 @@ oml.ui.browser = function() {
unique: 'id'
})
.bindEvent({
- open: function() {
- oml.UI.set({itemView: 'book'});
+ open: function(data) {
+ if (that.value(data.ids[0], 'mediastate') == 'available') {
+ oml.UI.set({itemView: 'book'});
+ }
},
select: function(data) {
oml.UI.set({
diff --git a/static/js/deleteItemsDialog.js b/static/js/deleteItemsDialog.js
index 72e451b..010e269 100644
--- a/static/js/deleteItemsDialog.js
+++ b/static/js/deleteItemsDialog.js
@@ -27,7 +27,6 @@ oml.ui.deleteItemsDialog = function() {
}, function() {
oml.UI.set({listSelection: []});
Ox.Request.clearCache('find');
- oml.$ui.folders.updateElement();
oml.$ui.list.updateElement();
});
});
diff --git a/static/js/filter.js b/static/js/filter.js
index 318e360..4d47ff9 100644
--- a/static/js/filter.js
+++ b/static/js/filter.js
@@ -62,7 +62,6 @@ oml.ui.filter = function(id) {
);
},
select: function(data) {
- Ox.print('UI FILTER STATE', ui._filterState)
// fixme: cant index be an empty array, instead of -1?
// FIXME: this is still incorrect when deselecting a filter item
// makes a selected item in another filter disappear
diff --git a/static/js/filtersOuterPanel.js b/static/js/filtersOuterPanel.js
index 8e14eaa..c8b2733 100644
--- a/static/js/filtersOuterPanel.js
+++ b/static/js/filtersOuterPanel.js
@@ -59,7 +59,6 @@ oml.ui.filtersOuterPanel = function() {
},
oml_find: function() {
var previousUI = oml.UI.getPrevious();
- Ox.print('WTF', ui, oml.user.ui, Object.keys(ui), Object.keys(previousUI));
ui._filterState.forEach(function(data, index) {
if (!Ox.isEqual(data.selected, previousUI._filterState[index].selected)) {
oml.$ui.filters[index].options(
@@ -72,7 +71,6 @@ oml.ui.filtersOuterPanel = function() {
);
}
if (!Ox.isEqual(data.find, previousUI._filterState[index].find)) {
- Ox.print('::::', index, 'UNEQUAL', data.find, previousUI._filterState[index].find)
if (!ui.showFilters) {
oml.$ui.filters[index].options({
_selected: data.selected
diff --git a/static/js/findElement.js b/static/js/findElement.js
index db2d6ed..24353ba 100644
--- a/static/js/findElement.js
+++ b/static/js/findElement.js
@@ -188,7 +188,6 @@ oml.ui.findElement = function() {
$select.superValue = $select.value;
$select.value = function(value) {
if (arguments.length == 1) {
- Ox.print('I AM HERE')
$select.options({title: value == 'all' ? 'data' : value});
}
$select.superValue.apply($select, arguments);
diff --git a/static/js/findForm.js b/static/js/findForm.js
index 52491ed..b7bd490 100644
--- a/static/js/findForm.js
+++ b/static/js/findForm.js
@@ -2,8 +2,6 @@
oml.ui.findForm = function(list) {
- //Ox.print('FIND FORM LIST QUERY', list.query);
-
var ui = oml.user.ui,
that = Ox.Element(),
@@ -19,7 +17,7 @@ oml.ui.findForm = function(list) {
title: Ox._('List'),
type: 'item',
values: ui._lists.filter(function(list) {
- return list.type != 'smart'
+ return Ox.contains(['library', 'static'], list.type);
}).map(function(list) {
return {id: list.id, title: list.title};
})
diff --git a/static/js/folders.js b/static/js/folders.js
index 2787f2b..5cf94f2 100644
--- a/static/js/folders.js
+++ b/static/js/folders.js
@@ -3,8 +3,10 @@
oml.ui.folders = function() {
var ui = oml.user.ui,
+ username = oml.user.preferences.username,
userIndex,
+ users,
$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() {
var split = ui._list.split(':'),
index = userIndex[split[0] || oml.user.preferences.username],
@@ -50,49 +70,44 @@ oml.ui.folders = function() {
$lists = [];
- $lists.push(
- oml.$ui.librariesList = oml.ui.folderList({
- items: [
- {
- id: '',
- name: Ox._('All Libraries'),
- type: 'libraries',
- items: -1
- }
- ]
- })
- .bindEvent({
- load: function() {
- oml.api.find({query: getFind('')}, function(result) {
- oml.$ui.librariesList.value('', 'items', result.data.items);
- });
- },
- select: function() {
- oml.UI.set({find: getFind('')});
- oml.$ui.librariesList.options({selected: ['']});
- },
- selectnext: function() {
- oml.UI.set(Ox.extend(
- {find: getFind(':')},
- 'showFolder.' + oml.user.preferences.username,
- true
- ));
- },
- })
- .css({height: '16px'})
- .appendTo(that)
- );
- oml.$ui.librariesList.$body.css({height: '16px'}); // FIXME!
-
oml.$ui.folder = [];
oml.$ui.libraryList = [];
oml.$ui.folderList = [];
- oml.getUsersAndLists(function(users, lists) {
+ getUsersAndLists(function(users, lists) {
Ox.print('GOT USERS AND LISTS', users, lists);
userIndex = {};
+ $lists.push(
+ oml.$ui.librariesList = oml.ui.folderList({
+ items: [
+ {
+ id: '',
+ name: Ox._('All Libraries'),
+ type: 'libraries',
+ items: Ox.getObjectById(lists, '').items
+ }
+ ]
+ })
+ .bindEvent({
+ select: function() {
+ oml.UI.set({find: getFind('')});
+ oml.$ui.librariesList.options({selected: ['']});
+ },
+ selectnext: function() {
+ oml.UI.set(Ox.extend(
+ {find: getFind(':')},
+ 'showFolder.' + username,
+ true
+ ));
+ },
+ })
+ .css({height: '16px'})
+ .appendTo(that)
+ );
+ oml.$ui.librariesList.$body.css({height: '16px'}); // FIXME!
+
users.forEach(function(user, index) {
var $content,
@@ -107,11 +122,7 @@ oml.ui.folders = function() {
oml.$ui.folder[index] = Ox.CollapsePanel({
collapsed: false,
extras: [
- oml.ui.statusIcon(
- !oml.user.online && index ? 'unknown'
- : user.online ? 'connected'
- : 'disconnected'
- ),
+ oml.ui.statusIcon(user, index),
{},
Ox.Button({
style: 'symbol',
@@ -162,7 +173,7 @@ oml.ui.folders = function() {
id: libraryId,
name: Ox._('Library'),
type: 'library',
- items: -1
+ items: Ox.getObjectById(lists, libraryId).items
}
]
})
@@ -170,15 +181,6 @@ oml.ui.folders = function() {
add: function() {
!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) {
oml.UI.set({find: getFind(data.ids[0])});
},
@@ -186,16 +188,20 @@ oml.ui.folders = function() {
oml.UI.set({find: getFind(items[0].id)});
},
selectprevious: function() {
- var userId = !index ? null : users[index - 1].id,
- set = {
- find: getFind(
- !index
- ? ''
- : Ox.last(lists[userId]).id
- )
- };
- if (userId) {
- Ox.extend(set, 'showFolder.' + userId, true);
+ // FIXME: ugly
+ var set, user, userLists;
+ if (!index) {
+ set = {find: getFind('')};
+ } else {
+ user = users[index - 1].nickname;
+ userLists = lists.filter(function(list) {
+ return list.user == user;
+ });
+ 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);
}
@@ -219,14 +225,6 @@ oml.ui.folders = function() {
key_control_d: function() {
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) {
lists[user.id] = data.ids.map(function(listId) {
return Ox.getObjectById(items, listId);
@@ -273,32 +271,53 @@ oml.ui.folders = function() {
};
- that.updateItems = function() {
- oml.getUsersAndLists(function(users, lists) {
- Ox.Request.clearCache('find');
- $lists.forEach(function($list) {
- $list.reloadList();
+ that.updateItems = function(items) {
+ Ox.print('UPDATE ITEMS', items);
+ var $list;
+ if (arguments.length == 0) {
+ 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) {
- oml.getUsersAndLists(function(users, lists) {
+ oml.getLists(function(lists) {
var items = lists.filter(function(list) {
return list.user == oml.user.preferences.username
&& list.type != 'library';
});
+ oml.$ui.folder[0].$content
+ .css({height: 16 + items.length * 16 + 'px'});
oml.$ui.folderList[0].options({
items: items
})
.css({height: items.length * 16 + 'px'})
.size();
- oml.$ui.folder[0].$content
- .css({height: 16 + items.length * 16 + 'px'});
callback && callback();
});
};
+ oml.bindEvent({
+ activity: function(data) {
+ if (data.activity == 'import') {
+ that.updateItems();
+ }
+ }
+ });
+
return that.updateElement();
-};
\ No newline at end of file
+};
diff --git a/static/js/identifyDialog.js b/static/js/identifyDialog.js
index c12adfc..4196f72 100644
--- a/static/js/identifyDialog.js
+++ b/static/js/identifyDialog.js
@@ -34,6 +34,8 @@ oml.ui.identifyDialog = function(data) {
originalData = Ox.clone(data, true),
+ selected = data.mainid ? 'id' : 'title',
+
idValue, titleValue,
$idInputs, $idButtons = {},
@@ -59,7 +61,7 @@ oml.ui.identifyDialog = function(data) {
$titlePanel = Ox.SplitPanel({
elements: [
{element: $titleForm, size: 96},
- {element: renderResults([])}
+ {element: Ox.Element()}
],
orientation: 'vertical'
}),
@@ -72,7 +74,7 @@ oml.ui.identifyDialog = function(data) {
{id: 'title', title: Ox._('Find by Title')}
],
selectable: true,
- selected: 'id'
+ value: selected
})
.css({
width: '768px',
@@ -81,7 +83,8 @@ oml.ui.identifyDialog = function(data) {
})
.bindEvent({
change: function(data) {
- $innerPanel.options({selected: data.value});
+ selected = data.value;
+ $innerPanel.options({selected: selected});
}
})
.appendTo($bar),
@@ -92,7 +95,7 @@ oml.ui.identifyDialog = function(data) {
{id: 'title', element: $titlePanel}
],
orientation: 'horizontal',
- selected: 'id',
+ selected: selected,
size: 768
}),
@@ -122,16 +125,16 @@ oml.ui.identifyDialog = function(data) {
})
.bindEvent({
click: function() {
+ Ox.print('$$$', idValue);
var edit = Ox.extend(
{id: data.id},
$innerPanel.options('selected') == 'id'
- ? idValue
+ ? idValue || {mainid: ''}
: titleValue
);
that.options({content: Ox.LoadingScreen().start()});
that.disableButtons();
oml.api.edit(edit, function(result) {
- Ox.print('EDITED', result.data);
that.close();
Ox.Request.clearCache('find');
oml.$ui.browser.reloadList(true);
@@ -145,28 +148,36 @@ oml.ui.identifyDialog = function(data) {
content: $outerPanel,
fixedSize: true,
height: 384,
+ removeOnClose: true,
title: Ox._('Identify Book'),
width: 768
});
+ function disableButtons() {
+ Ox.forEach(selected == 'id' ? $idButtons : $titleButtons, function($button) {
+ $button.options({disabled: true});
+ });
+ }
+
function findMetadata(data) {
+ disableButtons();
$titlePanel.replaceElement(1, Ox.LoadingScreen().start());
oml.api.findMetadata(data, function(result) {
- Ox.print('GOT RESULTS', result.data);
// FIXME: CONCAT HERE
var items = result.data.items.map(function(item, index) {
return Ox.extend({index: (index + 1).toString()}, item);
});
+ updateTitleButtons();
$titlePanel.replaceElement(1, renderResults(items));
});
}
function getMetadata(key, value) {
+ disableButtons();
$idPanel.replaceElement(1, Ox.LoadingScreen().start());
oml.api.getMetadata(Ox.extend({}, key, value), function(result) {
- Ox.print('GOT RESULT', result.data);
$idForm = renderIdForm(result.data);
- $idPreview = oml.ui.infoView(result.data);
+ $idPreview = Ox.isEmpty(data) ? Ox.Element() : oml.ui.infoView(result.data);
$idPanel
.replaceElement(0, $idForm)
.replaceElement(1, $idPreview);
@@ -174,7 +185,6 @@ oml.ui.identifyDialog = function(data) {
}
function idInputValues(key, values) {
- Ox.print('WTF,', $idInputs);
var $input = $idInputs[ids.map(function(id) {
return id.id;
}).indexOf(key)];
@@ -190,30 +200,13 @@ oml.ui.identifyDialog = function(data) {
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) {
return Ox.every(data, Ox.isEmpty);
}
function isOriginal(data) {
- return Ox.every(data, function(value, key) {
- return value == originalData[key];
+ return Ox.every(Object.keys(data), function(key) {
+ return data[key] == originalData[key];
});
}
@@ -268,7 +261,7 @@ oml.ui.identifyDialog = function(data) {
});
});
getMetadata(id.id, data.value, function() {
- // ...
+ Ox.print('GOT METADATA');
updateIdButtons();
});
}
@@ -335,15 +328,27 @@ oml.ui.identifyDialog = function(data) {
})
.bindEvent({
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')
}
})
.appendTo($element);
+ updateIdButtons();
return $element;
}
function renderResults(items) {
- Ox.print('LIST ITEMS::::', items);
var $list = Ox.TableList({
columns: [
{
@@ -375,7 +380,6 @@ oml.ui.identifyDialog = function(data) {
select: function(data) {
var index = data.ids[0], mainid;
mainid = $list.value(index, 'mainid');
- Ox.print('MAINID', mainid)
titleValue = Ox.extend({}, mainid, $list.value(index, mainid));
$results.replaceElement(1, Ox.LoadingScreen().start());
oml.api.getMetadata(titleValue, function(result) {
@@ -476,6 +480,23 @@ oml.ui.identifyDialog = function(data) {
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() {
var data = {}, empty, original;
ids.forEach(function(id) {
@@ -486,7 +507,7 @@ oml.ui.identifyDialog = function(data) {
$idButtons.clear.options({disabled: empty});
$idButtons.reset.options({disabled: original});
$idButtons.find.options({disabled: empty});
- that[original ? 'disableButton' : 'enableButton']('update');
+ that && that[original ? 'disableButton' : 'enableButton']('update');
}
function updateTitleButtons() {
diff --git a/static/js/importExportDialog.js b/static/js/importExportDialog.js
index 6982af3..f951431 100644
--- a/static/js/importExportDialog.js
+++ b/static/js/importExportDialog.js
@@ -50,8 +50,8 @@ oml.ui.importExportDialog = function(selected) {
that = Ox.Dialog({
buttons: [
Ox.Button({
- id: 'close',
- title: Ox._('Close')
+ id: 'hide',
+ title: Ox._('Hide')
})
.bindEvent({
click: function() {
@@ -87,7 +87,7 @@ oml.ui.importExportDialog = function(selected) {
$status = {},
$progressButton = {};
- oml.getUsersAndLists(function() {
+ oml.getLists(function() {
oml.api.getActivity(function(result) {
var isActive = !Ox.isEmpty(result.data),
activity = result.data.activity;
@@ -98,7 +98,6 @@ oml.ui.importExportDialog = function(selected) {
progress: [0,42]
};
*/
- Ox.print(result.data, '!!!!!!!!')
$innerPanel
.replaceElement(0,
activity == 'import' ? renderActivity(result.data)
@@ -208,6 +207,12 @@ oml.ui.importExportDialog = function(selected) {
$form = Ox.Form({
items: selected == 'import' ? [
Ox.Input({
+ autocomplete: function(value, callback) {
+ oml.api.autocompleteFolder({path: value}, function(result) {
+ callback(result.data.items);
+ });
+ },
+ autocompleteSelect: true,
changeOnKeypress: true,
id: 'path',
label: 'Source Path',
@@ -216,7 +221,7 @@ oml.ui.importExportDialog = function(selected) {
}),
Ox.SelectInput({
id: 'list',
- inputValue: oml.validateName(Ox._('Untitled'), getListNames()),
+ inputValue: oml.getValidName(Ox._('Untitled'), getListNames()),
inputWidth: 224,
items: getListItems('import'),
label: 'Destination',
@@ -229,8 +234,8 @@ oml.ui.importExportDialog = function(selected) {
Ox.Select({
id: 'mode',
items: [
- {id: 'copy', title: Ox._('Copy files')},
- {id: 'move', title: Ox._('Move files')}
+ {id: 'copy', title: Ox._('Copy (keep files in source path)')},
+ {id: 'move', title: Ox._('Move (delete files from source path)')}
],
label: Ox._('Import Mode'),
labelWidth: 128,
@@ -255,10 +260,10 @@ oml.ui.importExportDialog = function(selected) {
Ox.Select({
id: 'mode',
items: [
- {id: 'keep', title: Ox._('Keep existing files')},
- {id: 'remove', title: Ox._('Remove existing files')}
+ {id: 'add', title: Ox._('Add (keep files in destination path)')},
+ {id: 'replace', title: Ox._('Replace (delete files from destination path)')}
],
- label: Ox._('Import Mode'),
+ label: Ox._('Export Mode'),
labelWidth: 128,
width: 480
})
@@ -268,15 +273,8 @@ oml.ui.importExportDialog = function(selected) {
.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, getListNames()))
- }
- }
$activityButton[selected].options({
- disabled: !values.path //|| !values.list
+ disabled: !values.path
});
}
})
@@ -310,7 +308,11 @@ oml.ui.importExportDialog = function(selected) {
})
.bindEvent({
click: function() {
- var data = $form.values();
+ var data = $form.values(),
+ addList = data.list && !Ox.contains(
+ oml.getOwnListNames(),
+ data.list
+ );
$innerPanel.replaceElement(0,
renderActivity({
activity: 'import',
@@ -319,13 +321,15 @@ oml.ui.importExportDialog = function(selected) {
})
);
$label['export'].show();
- oml.api.import({
- list: data.list, // FIXME: WRONG for Library
- mode: data.mode,
- path: data.path,
- }, function() {
- // ...
- })
+ (addList ? oml.addList : Ox.noop)(false, false, data.list, function() {
+ oml.api.import({
+ list: data.list,
+ mode: data.mode,
+ path: data.path,
+ }, function() {
+ // ...
+ });
+ });
}
})
.appendTo($element);
@@ -341,7 +345,6 @@ oml.ui.importExportDialog = function(selected) {
}
function setProgress(data) {
- Ox.print('SET PROGRESS', data, $progress)
var progress = data.status ? 1
: !data.progress[0] || !data.progress[1] ? -1
: data.progress[0] / data.progress[1];
@@ -379,7 +382,6 @@ oml.ui.importExportDialog = function(selected) {
oml.bindEvent({
activity: function(data) {
- Ox.print('activity', arguments);
setProgress(data);
setStatus(data);
setButton(data);
diff --git a/static/js/infoView.js b/static/js/infoView.js
index 853292c..3261d57 100644
--- a/static/js/infoView.js
+++ b/static/js/infoView.js
@@ -321,6 +321,11 @@ oml.ui.infoView = function(identifyData) {
tooltip: isEditable ? oml.getEditTooltip() : '',
value: data.title || ''
})
+ .bindEvent({
+ submit: function(data) {
+ editMetadata('title', data.value);
+ }
+ })
.css({
fontSize: '13px',
fontWeight: 'bold'
@@ -353,7 +358,9 @@ oml.ui.infoView = function(identifyData) {
fontWeight: 'bold'
})
.bindEvent({
- // ...
+ submit: function(data) {
+ editMetadata('author', value.split(', '));
+ }
})
)
.appendTo($info);
@@ -502,12 +509,13 @@ oml.ui.infoView = function(identifyData) {
// FIXME: identify dialog should call this too
function editMetadata(key, value) {
var edit;
+ Ox.print('EM', key, value, data[key])
if (value != data[key]) {
edit = Ox.extend({id: ui.item}, key, value);
oml.api.edit(edit, function(result) {
Ox.Request.clearCache('find');
oml.$ui.browser.reloadList();
- that.updateElement(result.data, $data);
+ //that.updateElement(result.data, $info);
});
}
}
diff --git a/static/js/list.js b/static/js/list.js
index 84e599e..a3ba17a 100644
--- a/static/js/list.js
+++ b/static/js/list.js
@@ -50,6 +50,14 @@ oml.ui.list = function() {
}
},
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);
},
key_control_delete: function() {
@@ -58,6 +66,15 @@ oml.ui.list = function() {
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) {
oml.UI.set({
item: data.ids[0],
diff --git a/static/js/listDialog.js b/static/js/listDialog.js
index 03035ae..30ea96c 100644
--- a/static/js/listDialog.js
+++ b/static/js/listDialog.js
@@ -65,14 +65,18 @@ oml.ui.listDialog = function() {
})
.bindEvent({
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)})
$nameInput.value(value);
// FIXME: UGLY
listNames[listNames.indexOf(listData.name)] = value;
listData.name = value;
//
- Ox.print(listData.name, 'LIST NAMES ???', listNames)
oml.api.editList({
id: ui._list,
name: value
@@ -94,13 +98,13 @@ oml.ui.listDialog = function() {
})
.appendTo($content),
$findForm;
- Ox.print('DEBUG:', list, listData)
if (listData.type == 'smart') {
$findForm = oml.ui.findForm(listData)
.css({marginTop: '8px'})
.appendTo($content);
}
that.options({content: $content});
+ $nameInput.focusInput(true);
});
function getTitle(list) {
diff --git a/static/js/openButton.js b/static/js/openButton.js
index b665c2b..37201ff 100644
--- a/static/js/openButton.js
+++ b/static/js/openButton.js
@@ -19,7 +19,7 @@ oml.ui.openButton = function() {
oml.UI.set({item: ui.listSelection[0]});
},
oml_listselection: function() {
- that.update();
+ that.updateElement();
}
});
@@ -29,4 +29,4 @@ oml.ui.openButton = function() {
return that.updateElement();
-};
\ No newline at end of file
+};
diff --git a/static/js/preferencesDialog.js b/static/js/preferencesDialog.js
index 22f8724..13a957f 100644
--- a/static/js/preferencesDialog.js
+++ b/static/js/preferencesDialog.js
@@ -12,7 +12,7 @@ oml.ui.preferencesDialog = function() {
title: 'Username',
value: preferences.username,
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',
@@ -25,12 +25,24 @@ oml.ui.preferencesDialog = function() {
{
id: 'libraryPath',
title: 'Library Path',
+ autocomplete: function(value, callback) {
+ oml.api.autocompleteFolder({path: value}, function(result) {
+ callback(result.data.items);
+ });
+ },
+ autocompleteSelect: true,
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.'
},
{
id: 'importPath',
title: 'Import Path',
+ autocomplete: function(value, callback) {
+ oml.api.autocompleteFolder({path: value}, function(result) {
+ callback(result.data.items);
+ });
+ },
+ autocompleteSelect: true,
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.'
}
@@ -376,6 +388,8 @@ oml.ui.preferencesDialog = function() {
width: 384
})
: Ox.Input({
+ autocomplete: item.autocomplete || null,
+ autocompleteSelect: item.autocompleteSelect || false,
label: Ox._(item.title),
labelWidth: 128,
placeholder: item.placeholder || '',
@@ -415,6 +429,9 @@ oml.ui.preferencesDialog = function() {
change: function(data) {
var key = data.id,
value = data.data.value[0];
+ if (key == 'username') {
+ value = getValidName(value, [], ':');
+ }
if (key in oml.config.user.preferences) {
oml.Preferences.set(key, value);
} else {
diff --git a/static/js/rightPanel.js b/static/js/rightPanel.js
index 5a1b27c..9234129 100644
--- a/static/js/rightPanel.js
+++ b/static/js/rightPanel.js
@@ -26,6 +26,7 @@ oml.ui.rightPanel = function() {
oml.$ui.itemViewPanel.options({size: data.size});
},
oml_item: function(data) {
+ Ox.print('rightPanel, oml_item', data);
if (!!data.value != !!data.previousValue) {
that.options({selected: !ui.item ? 'list' : 'item'});
}
@@ -34,4 +35,4 @@ oml.ui.rightPanel = function() {
return that;
-};
\ No newline at end of file
+};
diff --git a/static/js/statusIcon.js b/static/js/statusIcon.js
index a5cf6ee..e3494ed 100644
--- a/static/js/statusIcon.js
+++ b/static/js/statusIcon.js
@@ -1,18 +1,10 @@
'use strict';
-oml.ui.statusIcon = function(status) {
+oml.ui.statusIcon = function(user, index) {
// FIXME: not only '-webkit'
- 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(', '),
-
+ var status = getStatus(user),
that = Ox.Element({
tooltip: Ox._({
connected: 'Connected',
@@ -24,7 +16,6 @@ oml.ui.statusIcon = function(status) {
width: '10px',
height: '10px',
margin: '3px',
- background: '-webkit-linear-gradient(bottom, ' + color + ')',
borderRadius: '5px'
})
.append(
@@ -33,11 +24,65 @@ oml.ui.statusIcon = function(status) {
width: '8px',
height: '8px',
margin: '1px',
- background: '-webkit-linear-gradient(top, ' + color + ')',
borderRadius: '4px'
})
);
- return that;
+ render();
-};
\ No newline at end of file
+ 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;
+};
diff --git a/static/js/statusbar.js b/static/js/statusbar.js
index 269ff24..4f5b6a5 100644
--- a/static/js/statusbar.js
+++ b/static/js/statusbar.js
@@ -35,7 +35,9 @@ oml.ui.statusbar = function() {
});
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) {
diff --git a/static/js/transfersDialog.js b/static/js/transfersDialog.js
index 63bad21..afa25bb 100644
--- a/static/js/transfersDialog.js
+++ b/static/js/transfersDialog.js
@@ -21,7 +21,7 @@ oml.ui.transfersDialog = function() {
visible: true,
width: id == 'title' ? 240
: id == 'transferadded' ? 144
- : id == 'transferprogress' ? 80
+ : id == 'transferprogress' ? 80 - Ox.UI.SCROLLBAR_SIZE
: key.columnWidth
};
}),
@@ -40,6 +40,7 @@ oml.ui.transfersDialog = function() {
}), callback);
},
keys: ['author'],
+ scrollbarVisible: true,
sort: [{key: 'transferprogress', operator: '-'}],
unique: 'id'
}),
diff --git a/static/js/usersDialog.js b/static/js/usersDialog.js
index fe4c897..c5389d6 100644
--- a/static/js/usersDialog.js
+++ b/static/js/usersDialog.js
@@ -38,7 +38,15 @@ oml.ui.usersDialog = function() {
width: 768
})
.bindEvent({
+ open: function() {
+ oml.bindEvent({
+ peering: peering,
+ });
+ },
close: function() {
+ oml.unbindEvent({
+ peering: peering,
+ });
if (ui.page == 'users') {
oml.UI.set({page: ''});
}
@@ -112,6 +120,10 @@ oml.ui.usersDialog = function() {
.appendTo($users);
});
+ var peering = Ox.throttle(function() {
+ updateUsers();
+ }, 1000);
+
function renderSectionList(folder) {
var $list = Ox.TableList({
@@ -254,11 +266,15 @@ oml.ui.usersDialog = function() {
})
.bindEvent({
change: function(data) {
- var value = oml.validateName(
- data.value,
- users.map(function(user) {
- return user.nickname;
- })
+ var value = oml.getValidName(
+ data.value || 'anonymous',
+ // FIXME: WRONG
+ users.filter(function(user_) {
+ return user_.nickname != user.nickname;
+ }).map(function(u) {
+ return user_.nickname;
+ }),
+ ':'
);
this.value(value);
oml.api.editUser({
@@ -447,9 +463,7 @@ oml.ui.usersDialog = function() {
columns: [
{
format: function(value, data) {
- return oml.ui.statusIcon(
- value ? 'connected' : 'disconnected'
- )
+ return oml.ui.statusIcon(data)
.css({
margin: '2px 3px 3px 0'
});
@@ -581,7 +595,6 @@ oml.ui.usersDialog = function() {
});
}
-
that.updateElement = function() {
that.options({
diff --git a/static/js/utils.js b/static/js/utils.js
index a701265..56f8bfb 100644
--- a/static/js/utils.js
+++ b/static/js/utils.js
@@ -1,8 +1,10 @@
oml.addList = function() {
- // addList(isSmart, isFrom) or addList(list) [=dupicate]
+ // addList(isSmart, isFrom[, name[, callback]])
+ // or addList(list) [=duplicate]
var args = arguments,
isDuplicate = args.length == 1,
- isSmart, isFrom, list, listData, data,
+ isSmart, isFrom, name, callback,
+ list, listData, data,
username = oml.user.preferences.username;
Ox.Request.clearCache('getLists');
oml.api.getLists(function(result) {
@@ -16,8 +18,10 @@ oml.addList = function() {
if (!isDuplicate) {
isSmart = args[0];
isFrom = args[1];
+ name = args[2] || Ox._('Untitled');
+ callback = args[3];
data = {
- name: oml.validateName(Ox._('Untitled'), listNames),
+ name: oml.getValidName(name, listNames),
type: !isSmart ? 'static' : 'smart'
};
if (!isSmart) {
@@ -36,7 +40,7 @@ oml.addList = function() {
list = args[0];
listData = Ox.getObjectById(Ox.flatten(Ox.values(lists)), list);
data = Ox.extend({
- name: oml.validateName(listData.name, listNames),
+ name: oml.getValidName(listData.name, listNames),
type: listData.type
}, listData.query ? {
query: listData.query
@@ -74,26 +78,32 @@ oml.addList = function() {
$folderList = oml.$ui.folderList[0];
oml.$ui.folder[0].options({collapsed: false}); // FIXME: SET UI!
// FIXME: DOESN'T WORK
- $folderList
- .bindEventOnce({
- load: function() {
- $folderList
- .gainFocus()
- .options({selected: [list]});
- oml.UI.set({
- find: {
- conditions: [{
- key: 'list',
- operator: '==',
- value: list
- }],
- operator: '&'
- }
- });
- oml.$ui.listDialog = oml.ui.listDialog().open();
- }
- });
+ if (
+ !oml.$ui.importExportDialog
+ || !oml.$ui.importExportDialog.is(':visible')
+ ) {
+ $folderList
+ .bindEventOnce({
+ load: function() {
+ $folderList
+ .gainFocus()
+ .options({selected: [list]});
+ oml.UI.set({
+ find: {
+ conditions: [{
+ key: 'list',
+ operator: '==',
+ value: list
+ }],
+ operator: '&'
+ }
+ });
+ oml.$ui.listDialog = oml.ui.listDialog().open();
+ }
+ });
+ }
oml.$ui.folders.updateOwnLists();
+ callback && callback();
});
}
};
@@ -267,7 +277,6 @@ oml.enableDragAndDrop = function($list, canMove) {
$list.bindEvent({
draganddropstart: function(data) {
- Ox.print('DND START', data);
var $lists = oml.$ui.libraryList.concat(oml.$ui.folderList);
drag.action = 'copy';
drag.ids = $list.options('selected');
@@ -732,7 +741,6 @@ oml.getListData = function(list) {
oml.getListFoldersHeight = function() {
var ui = oml.user.ui;
return Object.keys(ui.showFolder).reduce(function(value, id, index) {
- Ox.print('WTF WTF', index)
var items = oml.$ui.folderList[index].options('items').length;
return value + 16 + ui.showFolder[id] * (1 + items) * 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) {
var page = Ox.getObjectById(
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) {
var lists = [{
id: '',
@@ -811,12 +864,30 @@ oml.getUsersAndLists = function(callback) {
oml.$ui.mainMenu.updateElement();
}
ui._lists = lists;
- Ox.print('UI._LISTS', JSON.stringify(ui._lists));
+ Ox.print('GOT LISTS ::::', 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() {
return !!$('.OxDialog:visible').length
|| !!$('.OxFullscreen').length
@@ -834,7 +905,6 @@ oml.resizeFilters = function() {
oml.resizeListFolders = function() {
// FIXME: does this have to be here?
- Ox.print('RESIZING LIST FOLDERS', 'WIDTH', oml.getListFoldersWidth(), 'HEIGHT', oml.getListFoldersHeight())
var width = oml.getListFoldersWidth(),
columnWidth = width - 58;
oml.$ui.librariesList
@@ -842,7 +912,6 @@ oml.resizeListFolders = function() {
.resizeColumn('name', columnWidth);
Ox.forEach(oml.$ui.folder, function($folder, index) {
$folder.css({width: width + 'px'});
- Ox.print('SHOULD BE:', width);
oml.$ui.libraryList[index]
.css({width: width + 'px'})
.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) {
return /^[A-Za-z0-9+\/]{43}$/.test(value);
};