prepare public api

This commit is contained in:
j 2019-01-17 16:00:22 +05:30
parent 507b6fed3e
commit dd0e22a979
18 changed files with 237 additions and 94 deletions

View file

@ -318,6 +318,7 @@
"receivedRequests": "notify", "receivedRequests": "notify",
"rejectMessage": "", "rejectMessage": "",
"sendDiagnostics": false, "sendDiagnostics": false,
"enableReadOnlyService": false,
"sendRequests": "manually", "sendRequests": "manually",
"uploadRate": null, "uploadRate": null,
"username": "" "username": ""

View file

@ -113,6 +113,7 @@ def find(data):
response['size'] = sum(size) response['size'] = sum(size)
return response return response
actions.register(find) actions.register(find)
actions.register(find, version='public')
def get(data): def get(data):
@ -128,6 +129,7 @@ def get(data):
response = item.json(data['keys'] if 'keys' in data else None) response = item.json(data['keys'] if 'keys' in data else None)
return response return response
actions.register(get) actions.register(get)
actions.register(get, version='public')
def edit(data): def edit(data):

View file

@ -71,8 +71,9 @@ def defaultcontext():
class ApiHandler(tornado.web.RequestHandler): class ApiHandler(tornado.web.RequestHandler):
executor = ThreadPoolExecutor(max_workers=MAX_WORKERS) executor = ThreadPoolExecutor(max_workers=MAX_WORKERS)
def initialize(self, context=None): def initialize(self, context=None, public=False):
self._context = context self._context = context
self._public = public
def get(self): def get(self):
self.write('use POST') self.write('use POST')
@ -81,7 +82,7 @@ class ApiHandler(tornado.web.RequestHandler):
def api_task(self, request): def api_task(self, request):
import settings import settings
context = self._context context = self._context
if context == None: if context is None:
context = defaultcontext context = defaultcontext
action = request.arguments.get('action', [None])[0].decode('utf-8') action = request.arguments.get('action', [None])[0].decode('utf-8')
data = request.arguments.get('data', [b'{}'])[0] data = request.arguments.get('data', [b'{}'])[0]
@ -96,7 +97,10 @@ class ApiHandler(tornado.web.RequestHandler):
else: else:
if settings.DEBUG_API: if settings.DEBUG_API:
logger.debug('API %s %s', action, data) logger.debug('API %s %s', action, data)
f = actions.get(action) if self._public:
f = actions.versions['public'].get(action)
else:
f = actions.get(action)
if f: if f:
with context(): with context():
try: try:
@ -126,50 +130,60 @@ class ApiHandler(tornado.web.RequestHandler):
class ApiActions(dict): class ApiActions(dict):
properties = {} properties = {}
versions = {} versions = {}
def __init__(self):
def api(data): def _api(self, data, version=None):
''' '''
returns list of all known api actions returns list of all known api actions
takes { takes {
docs: bool docs: bool
} }
if docs is true, action properties contain docstrings if docs is true, action properties contain docstrings
returns { returns {
actions: { actions: {
'api': { 'api': {
cache: true, cache: true,
doc: 'recursion' doc: 'recursion'
}, },
'hello': { 'hello': {
cache: true, cache: true,
.. ..
}
...
} }
...
} }
''' }
data = data or {} '''
docs = data.get('docs', False) data = data or {}
code = data.get('code', False) docs = data.get('docs', False)
code = data.get('code', False)
if version:
_actions = list(self.versions[version].keys())
else:
_actions = list(self.keys()) _actions = list(self.keys())
_actions.sort() _actions.sort()
actions = {} actions = {}
for a in _actions: for a in _actions:
actions[a] = self.properties[a] actions[a] = self.properties[a]
if docs: if docs:
actions[a]['doc'] = self.doc(a) actions[a]['doc'] = self.doc(a, version)
if code: if code:
actions[a]['code'] = self.code(a) actions[a]['code'] = self.code(a, version)
return {'actions': actions} return {'actions': actions}
self.register(api)
def doc(self, name): def __init__(self):
f = self[name] self.register(self._api, 'api')
def doc(self, name, version=None):
if version:
f = self.versions[version][name]
else:
f = self[name]
return trim(f.__doc__) return trim(f.__doc__)
def code(self, name, version=None): def code(self, name, version=None):
f = self[name] if version:
f = self.versions[version][name]
else:
f = self[name]
if name != 'api' and hasattr(f, 'func_closure') and f.__closure__: if name != 'api' and hasattr(f, 'func_closure') and f.__closure__:
fc = [c for c in f.__closure__ if hasattr(c.cell_contents, '__call__')] fc = [c for c in f.__closure__ if hasattr(c.cell_contents, '__call__')]
f = fc[len(fc)-1].cell_contents f = fc[len(fc)-1].cell_contents
@ -181,8 +195,9 @@ class ApiActions(dict):
if not action: if not action:
action = method.__name__ action = method.__name__
if version: if version:
if not version in self.versions: if version not in self.versions:
self.versions[version] = {} self.versions[version] = {}
self.register(lambda data: self._api(data, version), action='api', version=version)
self.versions[version][action] = method self.versions[version][action] = method
else: else:
self[action] = method self[action] = method
@ -192,4 +207,5 @@ class ApiActions(dict):
if action in self: if action in self:
del self[action] del self[action]
actions = ApiActions() actions = ApiActions()

View file

@ -95,7 +95,7 @@ def run():
else: else:
debug = False debug = False
log_format='%(asctime)s:%(levelname)s:%(name)s:%(message)s' log_format = '%(asctime)s:%(levelname)s:%(name)s:%(message)s'
if debug: if debug:
logging.basicConfig(level=logging.DEBUG, format=log_format) logging.basicConfig(level=logging.DEBUG, format=log_format)
else: else:
@ -109,7 +109,7 @@ def run():
'gzip': True 'gzip': True
} }
handlers = [ common_handlers = [
(r'/(favicon.ico)', StaticFileHandler, {'path': settings.static_path}), (r'/(favicon.ico)', StaticFileHandler, {'path': settings.static_path}),
(r'/static/oxjs/(.*)', StaticFileHandler, {'path': os.path.join(settings.base_dir, '..', 'oxjs')}), (r'/static/oxjs/(.*)', StaticFileHandler, {'path': os.path.join(settings.base_dir, '..', 'oxjs')}),
(r'/static/cbr.js/(.*)', StaticFileHandler, {'path': os.path.join(settings.base_dir, '..', 'reader', 'cbr.js')}), (r'/static/cbr.js/(.*)', StaticFileHandler, {'path': os.path.join(settings.base_dir, '..', 'reader', 'cbr.js')}),
@ -126,17 +126,33 @@ def run():
'attachment': True 'attachment': True
}), }),
(r'/(.*)/(cover|preview)(\d*).jpg', IconHandler), (r'/(.*)/(cover|preview)(\d*).jpg', IconHandler),
]
handlers = common_handlers + [
(r'/api/upload/', UploadHandler, dict(context=db.session)), (r'/api/upload/', UploadHandler, dict(context=db.session)),
(r'/api/', oxtornado.ApiHandler, dict(context=db.session)), (r'/api/', oxtornado.ApiHandler, dict(context=db.session)),
(r'/ws', websocket.Handler), (r'/ws', websocket.Handler),
(r"(.*)", MainHandler), (r"(.*)", MainHandler),
] ]
public_handlers = common_handlers + [
(r'/api/', oxtornado.ApiHandler, dict(context=db.session, public=True)),
(r'/ws', websocket.Handler, dict(public=True)),
(r"(.*)", MainHandler),
]
setup.create_db() setup.create_db()
http_server = Application(handlers, **options) http_server = Application(handlers, **options)
max_buffer_size = 2*1024*1024*1024 max_buffer_size = 2*1024*1024*1024
http_server.listen(settings.server['port'], settings.server['address'], max_buffer_size=max_buffer_size) http_server.listen(settings.server['port'], settings.server['address'], max_buffer_size=max_buffer_size)
# public server
'''
public_port = settings.server.get('public_port')
public_address = settings.server['public_address']
if public_port:
public_server = Application(public_handlers, **options)
public_server.listen(public_port, public_address)
'''
if PID: if PID:
with open(PID, 'w') as pid: with open(PID, 'w') as pid:
pid.write('%s' % os.getpid()) pid.write('%s' % os.getpid())

View file

@ -44,6 +44,8 @@ server_defaults = {
'port': 9842, 'port': 9842,
'address': '127.0.0.1', 'address': '127.0.0.1',
'node_port': 9851, 'node_port': 9851,
'public_address': '127.0.0.1',
'public_port': 9852,
'node_address': '', 'node_address': '',
'extract_text': True, 'extract_text': True,
'localnode_discovery': True, 'localnode_discovery': True,

View file

@ -200,6 +200,8 @@ class Tor(object):
key_content = RSA.importKey(private_key).exportKey().decode() key_content = RSA.importKey(private_key).exportKey().decode()
key_content = ''.join(key_content.strip().split('\n')[1:-1]) key_content = ''.join(key_content.strip().split('\n')[1:-1])
ports = {9851: settings.server['node_port']} ports = {9851: settings.server['node_port']}
if settings.preferences.get('enableReadOnlyService'):
ports[80] = settings.server['public_port']
controller.remove_ephemeral_hidden_service(settings.USER_ID) controller.remove_ephemeral_hidden_service(settings.USER_ID)
response = controller.create_ephemeral_hidden_service(ports, response = controller.create_ephemeral_hidden_service(ports,
key_type='RSA1024', key_content=key_content, key_type='RSA1024', key_content=key_content,
@ -207,6 +209,8 @@ class Tor(object):
if response.is_ok(): if response.is_ok():
logger.debug('published node as https://%s.onion:%s', logger.debug('published node as https://%s.onion:%s',
settings.USER_ID, settings.server_defaults['node_port']) settings.USER_ID, settings.server_defaults['node_port'])
if settings.preferences.get('enableReadOnlyService'):
logger.debug('published readonly version as hidden servers: http://%s.onion', settings.USER_ID)
else: else:
logger.debug('failed to publish node to tor') logger.debug('failed to publish node to tor')
else: else:
@ -217,6 +221,9 @@ class Tor(object):
target_port=settings.server['node_port'] target_port=settings.server['node_port']
) )
logger.debug('published node as https://%s:%s', result.hostname, settings.server_defaults['node_port']) logger.debug('published node as https://%s:%s', result.hostname, settings.server_defaults['node_port'])
if settings.preferences.get('enableReadOnlyService'):
logger.error('can not publish read-only version, please update TOR')
def depublish(self): def depublish(self):
if not self.connected: if not self.connected:

View file

@ -40,8 +40,8 @@ def verify(release):
value = '\n'.join(value) value = '\n'.join(value)
value = value.encode() value = value.encode()
for digest in ('sha512', 'sha256', 'sha1'): for digest in ('sha512', 'sha256', 'sha1'):
if 'signature_%s'%digest in release: if 'signature_%s' % digest in release:
tls_sig = base64.b64decode(release['signature_%s'%digest].encode()) tls_sig = base64.b64decode(release['signature_%s' % digest].encode())
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, settings.OML_UPDATE_CERT) cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, settings.OML_UPDATE_CERT)
try: try:
OpenSSL.crypto.verify(cert, tls_sig, value, digest) OpenSSL.crypto.verify(cert, tls_sig, value, digest)
@ -301,6 +301,7 @@ def getVersion(data):
response['update'] = current < new response['update'] = current < new
return response return response
actions.register(getVersion, cache=False) actions.register(getVersion, cache=False)
actions.register(getVersion, cache=False, version='public')
def restart(data): def restart(data):
''' '''

View file

@ -55,6 +55,21 @@ def init(data):
return response return response
actions.register(init) actions.register(init)
def public_init(data):
response = init(data)
name = response['user']['preferences']['username']
response['user'] = response['config']['user']
response['user']['preferences']['username'] = name
response['user']['ui']['page'] = ''
response['user']['ui']['showFolder'] = {'': True}
response['readOnly'] = True
for page in response['config']['pages']:
if page['id'] == 'preferences':
#page['parts'] = [p for p in page['parts'] if p['id'] in ('appearance', 'extensions')]
page['parts'] = [p for p in page['parts'] if p['id'] in ('appearance',)]
return response
actions.register(public_init, action='init', version='public')
def setPreferences(data): def setPreferences(data):
''' '''
@ -149,6 +164,12 @@ def getUsers(data):
} }
actions.register(getUsers) actions.register(getUsers)
def getUsersPublic(data):
return {
'users': []
}
actions.register(getUsersPublic, 'getUsers', version='public')
def getLists(data): def getLists(data):
''' '''
@ -172,6 +193,29 @@ def getLists(data):
} }
actions.register(getLists) actions.register(getLists)
def getListsPublic(data):
'''
returns {
lists: []
}
'''
from item.models import Item
from sqlalchemy.sql import operators
user = state.user()
lists = []
lists.append({
'id': '',
'items': user.items.count(),
'name': 'Libraries',
'type': 'libraries',
'user': None,
})
lists += user.lists_json()
return {
'lists': lists
}
actions.register(getListsPublic, 'getLists', version='public')
def validate_query(query): def validate_query(query):
validate_conditions(query['conditions']) validate_conditions(query['conditions'])

View file

@ -17,6 +17,9 @@ logger = logging.getLogger(__name__)
class Handler(WebSocketHandler): class Handler(WebSocketHandler):
def initialize(self, public=False):
self._public = public
def check_origin(self, origin): def check_origin(self, origin):
# allow access to websocket from site, installer and loader (local file) # allow access to websocket from site, installer and loader (local file)
return self.request.host in origin or \ return self.request.host in origin or \

View file

@ -15,7 +15,7 @@
</div> </div>
<div id="development"> <div id="development">
<p> <p>
The latest code is in our <a href="https://git.0x2620.org/openmedialibrary.git">git repository</a>. The latest code is in our <a href="https://code.0x2620.org/0x2620/openmedialibrary">git repository</a>.
</p> </p>
<p> <p>
For everything else, there's IRC, and our development mailing list. Your feedback is welcome. For everything else, there's IRC, and our development mailing list. Your feedback is welcome.

View file

@ -15,14 +15,21 @@ oml.UI = (function() {
that.reset = function() { that.reset = function() {
var ui = oml.user.ui; var ui = oml.user.ui;
oml.api.resetUI({}, function() {
function reload() {
ui = oml.config.user.ui; ui = oml.config.user.ui;
ui._list = oml.getListState(ui.find); ui._list = oml.getListState(ui.find);
ui._filterState = oml.getFilterState(ui.find); ui._filterState = oml.getFilterState(ui.find);
ui._findState = oml.getFindState(ui.find); ui._findState = oml.getFindState(ui.find);
Ox.Theme(ui.theme); Ox.Theme(ui.theme);
oml.$ui.appPanel.reload(); oml.$ui.appPanel.reload();
}); }
if (oml.readOnly) {
oml.localStorage('ui', oml.config.user.ui);
reload();
} else {
oml.api.resetUI({}, reload);
}
}; };
// sets oml.user.ui.key to value // sets oml.user.ui.key to value
@ -161,7 +168,11 @@ oml.UI = (function() {
}); });
}); });
if (Ox.len(set)) { if (Ox.len(set)) {
oml.api.setUI(set); if (oml.readOnly) {
oml.localStorage('ui', oml.user.ui);
} else {
oml.api.setUI(set);
}
} }
if (triggerEvents) { if (triggerEvents) {
Ox.forEach(trigger, function(value, key) { Ox.forEach(trigger, function(value, key) {
@ -182,4 +193,4 @@ oml.UI = (function() {
return that; return that;
}()); }());

View file

@ -25,12 +25,13 @@ oml.ui.appDialog = function() {
title: Ox._('Software Development'), title: Ox._('Software Development'),
selected: ui.page == 'development' selected: ui.page == 'development'
}, },
].concat(oml.readOnly ? [] : [
{ {
id: 'contact', id: 'contact',
title: Ox._('Send Feedback'), title: Ox._('Send Feedback'),
selected: ui.page == 'contact' selected: ui.page == 'contact'
} }
], ]),
$panel = Ox.TabPanel({ $panel = Ox.TabPanel({
content: function(id) { content: function(id) {
@ -95,7 +96,7 @@ oml.ui.appDialog = function() {
}), }),
that = Ox.Dialog({ that = Ox.Dialog({
buttons: [ buttons: (oml.readOnly ? [] : [
Ox.Button({ Ox.Button({
id: 'update', id: 'update',
style: 'squared', style: 'squared',
@ -106,6 +107,7 @@ oml.ui.appDialog = function() {
oml.UI.set({page: 'update'}); oml.UI.set({page: 'update'});
} }
}), }),
]).concat([
{}, {},
Ox.Button({ Ox.Button({
id: 'close', id: 'close',
@ -116,7 +118,7 @@ oml.ui.appDialog = function() {
that.close(); that.close();
} }
}) })
], ]),
closeButton: true, closeButton: true,
content: $panel, content: $panel,
fixedSize: true, fixedSize: true,

View file

@ -270,9 +270,10 @@ oml.ui.infoView = function(externalData, isMixed) {
items: [ items: [
{id: 'read', title: Ox._('Read in Open Media Libary')}, {id: 'read', title: Ox._('Read in Open Media Libary')},
{id: 'open', title: Ox._('Open in External Reader')}, {id: 'open', title: Ox._('Open in External Reader')},
].concat(oml.readOnly ? [] : [
{}, {},
{id: 'show', title: Ox._('Show File')} {id: 'show', title: Ox._('Show File')}
], ]),
overlap: 'left', overlap: 'left',
style: 'squared', style: 'squared',
title: 'select', title: 'select',
@ -284,7 +285,11 @@ oml.ui.infoView = function(externalData, isMixed) {
if (data_.id == 'read') { if (data_.id == 'read') {
oml.UI.set({itemView: 'book'}); oml.UI.set({itemView: 'book'});
} else if (data_.id == 'open') { } else if (data_.id == 'open') {
oml.api.openFile({id: oml.user.ui.item}); if (oml.readOnly) {
document.location.href = '/' + oml.user.ui.item + '/get/'
} else {
oml.api.openFile({id: oml.user.ui.item});
}
} else { } else {
oml.api.openFolder({id: oml.user.ui.item}); oml.api.openFolder({id: oml.user.ui.item});
} }
@ -382,9 +387,9 @@ oml.ui.infoView = function(externalData, isMixed) {
Ox.print('BOOK DATA', data) Ox.print('BOOK DATA', data)
var $div, var $div,
isEditable = isMultiple || ( isEditable = !oml.readOnly && (isMultiple || (
data.mediastate == 'available' && !externalData data.mediastate == 'available' && !externalData
), )),
src = !externalData src = !externalData
? '/' + data.id + '/' + ui.icons + '512.jpg?' + data.modified ? '/' + data.id + '/' + ui.icons + '512.jpg?' + data.modified
: data.cover, : data.cover,
@ -751,28 +756,29 @@ oml.ui.infoView = function(externalData, isMixed) {
].join(', ') ].join(', ')
) )
.appendTo($data); .appendTo($data);
if (!oml.readOnly) {
renderIdentifyButton(data).appendTo($data);
renderIdentifyButton(data).appendTo($data); ['accessed', 'modified', 'added', 'created'].forEach(function(id) {
var title;
if (data[id]) {
title = Ox.getObjectById(oml.config.itemKeys, id).title;
$('<div>')
.css({
marginTop: '8px',
fontWeight: 'bold'
})
.text(title)
.appendTo($data);
$('<div>')
.text(Ox.formatDate(data[id], '%B %e, %Y'))
.appendTo($data);
}
});
['accessed', 'modified', 'added', 'created'].forEach(function(id) { if (data.mediastate == 'available') {
var title; renderShareButton(data).appendTo($data);
if (data[id]) {
title = Ox.getObjectById(oml.config.itemKeys, id).title;
$('<div>')
.css({
marginTop: '8px',
fontWeight: 'bold'
})
.text(title)
.appendTo($data);
$('<div>')
.text(Ox.formatDate(data[id], '%B %e, %Y'))
.appendTo($data);
} }
});
if (data.mediastate == 'available') {
renderShareButton(data).appendTo($data);
} }
$('<div>').css({height: '16px'}).appendTo($data); $('<div>').css({height: '16px'}).appendTo($data);

View file

@ -7,7 +7,9 @@ oml.ui.mainMenu = function() {
fromMenu = false, fromMenu = false,
that = Ox.MainMenu({ that = Ox.MainMenu({
extras: [ extras: oml.readOnly ? [
oml.$ui.loadingIcon = oml.ui.loadingIcon()
] : [
oml.$ui.updateButton = oml.ui.updateButton(), oml.$ui.updateButton = oml.ui.updateButton(),
oml.$ui.transferButton = oml.ui.transferButton(), oml.$ui.transferButton = oml.ui.transferButton(),
oml.$ui.peersButton = oml.ui.peersButton(), oml.$ui.peersButton = oml.ui.peersButton(),
@ -39,6 +41,7 @@ oml.ui.mainMenu = function() {
id: 'contact', id: 'contact',
title: Ox._('Send Feedback') title: Ox._('Send Feedback')
}, },
].concat(oml.readOnly ? [] : [
{}, {},
{ {
id: 'update', id: 'update',
@ -50,7 +53,7 @@ oml.ui.mainMenu = function() {
title: Ox._('Quit Open Media Library'), title: Ox._('Quit Open Media Library'),
keyboard: 'control q' keyboard: 'control q'
} }
] ])
}, },
{ {
id: 'userMenu', id: 'userMenu',
@ -61,6 +64,7 @@ oml.ui.mainMenu = function() {
title: Ox._('Preferences...'), title: Ox._('Preferences...'),
keyboard: 'control ,' keyboard: 'control ,'
}, },
].concat(oml.readOnly ? [] : [
{}, {},
{ {
id: 'peers', id: 'peers',
@ -70,7 +74,7 @@ oml.ui.mainMenu = function() {
id: 'transfers', id: 'transfers',
title: Ox._('Transfers...') title: Ox._('Transfers...')
} }
] ])
}, },
getListMenu(), getListMenu(),
getEditMenu(), getEditMenu(),
@ -295,6 +299,7 @@ oml.ui.mainMenu = function() {
keyboard: 'shift control s', keyboard: 'shift control s',
disabled: true disabled: true
}, },
].concat(oml.readOnly ? [] : [
{}, {},
{ {
id: 'sorttitles', id: 'sorttitles',
@ -304,7 +309,7 @@ oml.ui.mainMenu = function() {
id: 'sortnames', id: 'sortnames',
title: Ox._('Sort Names...') title: Ox._('Sort Names...')
} }
] ])
}, },
getFindMenu(), getFindMenu(),
{ {
@ -477,15 +482,19 @@ oml.ui.mainMenu = function() {
} else if (id == 'invertselection') { } else if (id == 'invertselection') {
oml.$ui.list.invertSelection(); oml.$ui.list.invertSelection();
} else if (id == 'download') { } else if (id == 'download') {
oml.api.addListItems({ if (oml.readOnly) {
items: ui.listSelection.filter(function(id) { document.location.href = '/' + ui.listSelection[0] + '/get/'
return oml.$ui.list.value(id, 'mediastate') == 'unavailable'; } else {
}), oml.api.addListItems({
list: ':' items: ui.listSelection.filter(function(id) {
}, function(result) { return oml.$ui.list.value(id, 'mediastate') == 'unavailable';
Ox.Request.clearCache(); }),
// FIXME: reload? list: ':'
}); }, function(result) {
Ox.Request.clearCache();
// FIXME: reload?
});
}
} else if (Ox.contains(['cut', 'cutadd'], id)) { } else if (Ox.contains(['cut', 'cutadd'], id)) {
var action = data.id == 'cut' ? 'copy' : 'add'; var action = data.id == 'cut' ? 'copy' : 'add';
fromMenu = true; fromMenu = true;
@ -741,7 +750,7 @@ oml.ui.mainMenu = function() {
clipboardItems > 1 ? Ox.formatNumber(clipboardItems) + ' ' : '' clipboardItems > 1 ? Ox.formatNumber(clipboardItems) + ' ' : ''
) + Ox._(clipboardItems == 1 ? 'Book' : 'Books'), ) + Ox._(clipboardItems == 1 ? 'Book' : 'Books'),
canSelect = !ui.item, canSelect = !ui.item,
canDownload = !!unavailableItems, canDownload = !!unavailableItems || (oml.readOnly && selectionItems == 1),
canCopy = canSelect && selectionItems, canCopy = canSelect && selectionItems,
canCut = canCopy && listData.editable, canCut = canCopy && listData.editable,
canPaste = listData.editable && clipboardItems, canPaste = listData.editable && clipboardItems,
@ -757,7 +766,7 @@ oml.ui.mainMenu = function() {
return { return {
id: 'editMenu', id: 'editMenu',
title: Ox._('Edit'), title: Ox._('Edit'),
items: [ items: [].concat(oml.readOnly ? [] : [
{ {
id: 'import', id: 'import',
title: Ox._(oml.user.importing ? 'Importing Books...' : 'Import Books...') // FIXME title: Ox._(oml.user.importing ? 'Importing Books...' : 'Import Books...') // FIXME
@ -767,6 +776,7 @@ oml.ui.mainMenu = function() {
title: Ox._(oml.user.exporting ? 'Exporting Books...' : 'Export Books...') // FIXME title: Ox._(oml.user.exporting ? 'Exporting Books...' : 'Export Books...') // FIXME
}, },
{}, {},
]).concat([
{ {
id: 'selectall', id: 'selectall',
title: Ox._('Select All'), title: Ox._('Select All'),
@ -792,6 +802,7 @@ oml.ui.mainMenu = function() {
disabled: !canDownload, disabled: !canDownload,
keyboard: 'control d' keyboard: 'control d'
}, },
]).concat(oml.readOnly ? [] : [
{ {
id: 'cut', id: 'cut',
title: Ox._('Cut {0}', [selectionItemName]), title: Ox._('Cut {0}', [selectionItemName]),
@ -865,7 +876,7 @@ oml.ui.mainMenu = function() {
title: Ox._('Clear History'), title: Ox._('Clear History'),
disabled: !historyItems, disabled: !historyItems,
} }
] ])
}; };
} }
@ -930,6 +941,14 @@ oml.ui.mainMenu = function() {
isLibrary = Ox.endsWith(ui._list, ':'), isLibrary = Ox.endsWith(ui._list, ':'),
isList = !isLibraries && !isLibrary, isList = !isLibraries && !isLibrary,
isOwnList = ui._list[0] == ':'; isOwnList = ui._list[0] == ':';
if (oml.readOnly) {
return {
id: 'listMenu',
title: Ox._('List'),
items: []
};
}
return { return {
id: 'listMenu', id: 'listMenu',
title: Ox._('List'), title: Ox._('List'),

View file

@ -50,8 +50,12 @@
$ui: {}, $ui: {},
config: data.config, config: data.config,
user: data.user, user: data.user,
readOnly: data.readOnly,
version: data.version version: data.version
}); });
if (oml.readOnly) {
oml.user.ui = oml.localStorage('ui') || oml.user.ui;
}
['preferences', 'ui'].forEach(function(key) { ['preferences', 'ui'].forEach(function(key) {
// make sure all valid settings are present // make sure all valid settings are present
oml.user[key] = Ox.extend( oml.user[key] = Ox.extend(

View file

@ -142,6 +142,14 @@ oml.ui.preferencesPanel = function() {
value: preferences.autostart, value: preferences.autostart,
help: 'Launch Open Media Library in the background once you login to your computer.' help: 'Launch Open Media Library in the background once you login to your computer.'
}, },
/*
{
id: 'enableReadOnlyService',
title: 'Read-Only Hidden Service',
value: preferences.enableReadOnlyService,
help: 'Make a read-only version of your library available as a TOR Hidden service.<br>\n Your library will be available at http://' + oml.user.id + '.onion/'
},
*/
{ {
id: 'showDebugMenu', id: 'showDebugMenu',
title: 'Show Debug Menu', title: 'Show Debug Menu',

View file

@ -16,6 +16,7 @@ oml.ui.userDialog = function() {
title: Ox._('Preferences'), title: Ox._('Preferences'),
selected: ui.page == 'preferences' selected: ui.page == 'preferences'
}, },
].concat(oml.readOnly ? [] : [
{ {
id: 'peers', id: 'peers',
title: Ox._('Peers'), title: Ox._('Peers'),
@ -26,7 +27,7 @@ oml.ui.userDialog = function() {
title: Ox._('Transfers'), title: Ox._('Transfers'),
selected: ui.page == 'transfers' selected: ui.page == 'transfers'
} }
] ])
}) })
.bindEvent({ .bindEvent({
change: function(data) { change: function(data) {
@ -70,4 +71,4 @@ oml.ui.userDialog = function() {
return that; return that;
}; };

View file

@ -862,7 +862,7 @@ oml.getUsers = function(callback) {
}].concat( }].concat(
Ox.sortBy(result.data.users.filter(function(user) { Ox.sortBy(result.data.users.filter(function(user) {
return user.peered; return user.peered;
}), 'index') }), 'index')
); );
ui._users = users; ui._users = users;
callback(users); callback(users);