This commit is contained in:
j 2014-05-19 17:00:33 +02:00
parent 1436b14003
commit 75a14fed1e
18 changed files with 251 additions and 121 deletions

View file

@ -110,7 +110,8 @@
"title": "Extension",
"type": "string",
"columnWidth": 80,
"sort": true
"sort": true,
"find": true
},
{
"id": "size",
@ -319,6 +320,7 @@
"username": ""
},
"ui": {
"coverSize": 256,
"fileInfo": "extension",
"filters": [
{"id": "author", "sort": [{"key": "items", "operator": "-"}]},

4
ctl
View file

@ -90,8 +90,8 @@ if [ "$1" == "update" ]; then
cd $BASE/$NAME
git pull
find . -name '*.pyc' -exec rm "{}" \;
$0 setup
$0 update_static > /dev/null
python2 oml setup
python2 oml update_static > /dev/null
exit
fi

View file

@ -151,7 +151,8 @@ class Changelog(db.Model):
if not i:
i = Item.get_or_create(itemid, info)
i.modified = datetime.fromtimestamp(float(timestamp))
i.users.append(user)
if user not in i.users:
i.users.append(user)
i.update()
return True

View file

@ -70,7 +70,6 @@ def api_acceptPeering(app, user_id, username, message):
user.info['username'] = username
user.info['message'] = message
user.update_peering(True, username)
trigger_event('peering.accept', user.json())
state.nodes.queue('add', user.id)
return True
return False

View file

@ -11,6 +11,7 @@ import gzip
import urllib2
from datetime import datetime
import os
import time
import ox
import ed25519
@ -30,7 +31,8 @@ logger = logging.getLogger('oml.nodes')
ENCODING='base64'
class Node(object):
class Node(Thread):
_running = True
_cert = None
online = False
download_speed = 0
@ -42,10 +44,36 @@ class Node(object):
self.user_id = user.id
key = str(user.id)
self.vk = ed25519.VerifyingKey(key, encoding=ENCODING)
self.go_online()
logger.debug('new Node %s online=%s', self.user_id, self.online)
self._q = Queue()
Thread.__init__(self)
self.daemon = True
self.start()
self._ping = PeriodicCallback(self.ping, 120000)
self._ping.start()
self.ping()
def run(self):
with self._app.app_context():
while self._running:
action = self._q.get()
if not self._running:
break
if action == 'go_online' or not self.online:
self._go_online()
else:
self.online = self.can_connect()
def join(self):
self._running = False
self.ping()
return Thread.join(self)
def ping(self):
self._q.put('')
def go_online(self):
self._q.put('go_online')
@property
def url(self):
@ -148,30 +176,26 @@ class Node(object):
def can_connect(self):
try:
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s.settimeout(1)
s.connect((self.host, self.port))
s.close()
logger.debug('try to connect to %s', self.url)
r = self._opener.open(self.url, timeout=1)
c = r.read()
logger.debug('ok')
return True
except:
pass
logger.debug('failed')
return False
def ping(self):
with self._app.app_context():
if self.online:
self.online = self.can_connect()
else:
self.go_online()
def go_online(self):
def _go_online(self):
self.resolve()
u = self.user
logger.debug('go_online peer=%s queued=%s (%s)', u.peered, u.queued, u.id)
if u.peered or u.queued:
try:
self.online = False
logger.debug('type to connect to %s at [%s]:%s', self.user_id, self.host, self.port)
logger.debug('try to connect to %s at [%s]:%s', self.user_id, self.host, self.port)
if self.can_connect():
logger.debug('connected to [%s]:%s', self.host, self.port)
self.online = True
if u.queued:
logger.debug('queued peering event pending=%s peered=%s', u.pending, u.peered)
@ -184,12 +208,14 @@ class Node(object):
self.peering('removePeering')
if self.online:
self.pullChanges()
logger.debug('connected to %s', self.user_id)
except:
logger.debug('failed to connect to %s', self.user_id, exc_info=1)
self.online = False
else:
self.online = False
self.trigger_status()
def trigger_status(self):
trigger_event('status', {
'id': self.user_id,
'online': self.online
@ -210,10 +236,7 @@ class Node(object):
r = self.request('pushChanges', changes)
except:
self.online = False
trigger_event('status', {
'id': self.user_id,
'online': self.online
})
self.trigger_status()
r = False
logger.debug('pushedChanges %s %s', r, self.user_id)
@ -230,9 +253,11 @@ class Node(object):
u.save()
else:
logger.debug('peering failed? %s %s', action, r)
if action in ('cancelPeering', 'rejectPeering', 'removePeering'):
self.online = False
else:
self.go_online()
trigger_event('peering.%s'%action.replace('Peering', ''), u.json())
return True
def download(self, item):
@ -336,7 +361,7 @@ class Nodes(Thread):
self._nodes[user_id] = Node(self, User.get_or_create(user_id))
else:
if not self._nodes[user_id].online:
self._nodes[user_id].go_online()
self._nodes[user_id].ping()
def run(self):
with self._app.app_context():
@ -351,4 +376,6 @@ class Nodes(Thread):
def join(self):
self._running = False
self._q.put(None)
for node in self._nodes.values():
node.join()
return Thread.join(self)

View file

@ -42,7 +42,7 @@ class CertValidatingHTTPSConnection(httplib.HTTPConnection):
if not self._ValidateCertificateFingerprint(cert):
raise InvalidCertificateException(self.fingerprint, cert,
'fingerprint mismatch')
logger.debug('CIPHER %s VERSION %s', self.sock.cipher(), self.sock.ssl_version)
#logger.debug('CIPHER %s VERSION %s', self.sock.cipher(), self.sock.ssl_version)
class VerifiedHTTPSHandler(urllib2.HTTPSHandler):
def __init__(self, **kwargs):

View file

@ -76,7 +76,6 @@ class User(db.Model):
def update_peering(self, peered, username=None):
was_peering = self.peered
self.queued = True
if peered:
self.pending = ''
if username:
@ -90,6 +89,7 @@ class User(db.Model):
if not was_peering:
Changelog.record(state.user(), 'addpeer', self.id, self.nickname)
self.peered = True
self.queued = True
self.save()
else:
self.pending = ''

View file

@ -86,18 +86,19 @@ oml.ui.findElement = function() {
}
},
submit: function(data) {
var scope = oml.$ui.findInSelect.value(),
// FIXME: oml.$ui.findInSelect.value() is undefined
var scope = oml.$ui.findInSelect.options('value'),
key = oml.$ui.findSelect.value(),
conditions = [].concat(
scope == 'list' ? [{
key: 'list',
value: ui._list,
operator: '=='
operator: '==',
value: ui._list
}] : [],
scope == 'user' ? [{
key: 'list',
value: ui._list.split(':')[0],
operator: '=='
operator: '==',
value: ui._list.split(':')[0] + ':'
}] : [],
data.value ? [{
key: key,
@ -158,22 +159,29 @@ oml.ui.findElement = function() {
}
function renderFindInSelect() {
var scope = !ui._list ? 'all'
: Ox.endsWith(ui._list, ':') ? 'user'
: 'list';
var $select = Ox.Select({
var items = [
{id: 'all', title: Ox._('Find In: All Libraries')},
{id: 'user', title: Ox._('Find In: This Library')},
{id: 'list', title: Ox._('Find In: This List')}
],
scope = !ui._list ? 'all'
: Ox.endsWith(ui._list, ':') ? 'user'
: 'list',
$select = Ox.Select({
items: [
{id: 'all', title: Ox._('Find In: All Libraries')},
items[0],
].concat(scope != 'all' ? [
{id: 'user', title: Ox._('Find In: This Library')},
items[1],
] : []).concat(scope == 'list' ? [
{id: 'list', title: Ox._('Find In: This List')}
items[2]
] : []),
max: 1,
min: 1,
overlap: 'right',
style: 'squared',
title: scope == 'all' ? 'data' : scope,
type: 'image',
tooltip: Ox._('Find: FIXME'),
tooltip: Ox.getObjectById(items, scope).title,
value: scope
})
.bindEvent({

View file

@ -16,7 +16,10 @@ oml.ui.folders = function() {
//overflowY: 'auto',
})
.bindEvent({
oml_find: selectList
oml_find: selectList,
oml_showfolder: function() {
oml.resizeListFolders();
}
});
function getFind(list) {
@ -120,7 +123,7 @@ oml.ui.folders = function() {
userIndex[user.nickname] = index;
oml.$ui.folder[index] = Ox.CollapsePanel({
collapsed: false,
collapsed: !ui.showFolder[user.nickname],
extras: [
oml.ui.statusIcon(user, index),
{},
@ -263,6 +266,7 @@ oml.ui.folders = function() {
});
oml.resizeListFolders(); // FIXME: DOESN'T WORK
selectList();
});
@ -272,7 +276,6 @@ oml.ui.folders = function() {
};
that.updateItems = function(items) {
Ox.print('UPDATE ITEMS', items);
var $list;
if (arguments.length == 0) {
oml.getLists(function(lists) {
@ -306,6 +309,7 @@ oml.ui.folders = function() {
})
.css({height: items.length * 16 + 'px'})
.size();
oml.resizeFolders();
callback && callback();
});
};
@ -316,10 +320,17 @@ oml.ui.folders = function() {
that.updateItems();
}
},
change: function(data) {
Ox.print('got change event')
},
'peering.accept': function(data) {
Ox.print('peering.accept reload list')
Ox.Request.clearCache('getUsers');
that.updateElement();
},
'peering.remove': function(data) {
Ox.print('peering.remove reload list')
Ox.Request.clearCache('getUsers');
that.updateElement();
}
});

View file

@ -4,6 +4,8 @@ oml.ui.infoView = function(identifyData) {
var ui = oml.user.ui,
css = getCSS(ui.coverSize),
that = Ox.Element()
.addClass('OxTextPage')
.css({overflowY: 'auto'})
@ -25,7 +27,7 @@ oml.ui.infoView = function(identifyData) {
position: 'absolute',
left: '16px',
top: '16px',
width: '256px'
width: css.cover.width
})
.appendTo(that),
@ -33,7 +35,7 @@ oml.ui.infoView = function(identifyData) {
.addClass('OxSelectable')
.css({
position: 'absolute',
left: '288px',
left: css.info.left,
right: !identifyData ? '176px' : 16 + Ox.UI.SCROLLBAR_SIZE + 'px',
top: '16px'
})
@ -55,6 +57,28 @@ oml.ui.infoView = function(identifyData) {
.appendTo(that);
}
function getCSS(size, ratio) {
var width = Math.round(ratio >= 1 ? size : size * ratio),
height = Math.round(ratio <= 1 ? size : size / ratio),
left = size == 256 ? Math.floor((size - width) / 2) : 0;
return {
cover: {
width: size + 'px'
},
info: {
left: (size == 256 ? size + 32 : width + 48) + 'px'
},
image: {
left: left + 'px',
width: width + 'px',
height: height + 'px'
},
reflection: {
top: height + 'px'
}
};
}
function getImageSize(size, ratio) {
var width = Math.round(ratio >= 1 ? size : size * ratio),
height = Math.round(ratio <= 1 ? size : size / ratio),
@ -82,19 +106,6 @@ oml.ui.infoView = function(identifyData) {
function identify(data) {
oml.ui.identifyDialog(data).open();
return;
$identifyPanel.select('id');
$identifyDialog.open();
identify(data);
function identify(data) {
oml.api.identify(data, function(result) {
$identifyList.options({
items: result.data.items.map(function(item, index) {
return Ox.extend(item, {index: index});
})
});
});
}
}
function renderMediaButton(data) {
@ -213,23 +224,29 @@ oml.ui.infoView = function(identifyData) {
return $element;
}
function updateCover(size, ratio) {
var width = Math.round(ratio >= 1 ? size : size * ratio),
height = Math.round(ratio <= 1 ? size : size / ratio),
left = Math.floor((size - width) / 2);
$image.css({
left: left + 'px',
width: width + 'px',
height: height + 'px'
}).show();
$reflectionImage.css({
left: left + 'px',
width: width + 'px',
height: height + 'px'
});
$reflection.css({
top: height + 'px'
}).show();
function toggleCoverSize(ratio) {
var coverSize = ui.coverSize == 256 ? 512 : 256,
css = getCSS(coverSize, ratio);
//$cover.animate(css.cover, 250);
$info.animate(css.info, 250);
$image.animate(css.image, 250);
$reflectionImage.animate(css.image, 250);
$reflection.animate(css.reflection, 250);
/*
$reflectionGradient.animate({
width: iconSize + 'px',
height: iconSize / 2 + 'px'
}, 250);
*/
oml.UI.set({coverSize: coverSize});
}
function updateCover(ratio) {
var css = getCSS(ui.coverSize, ratio);
$image.css(css.image).show();
$reflectionImage.css(css.image);
$reflection.css(css.reflection).show();
}
that.updateElement = function(idOrData, $elements) {
@ -257,10 +274,10 @@ oml.ui.infoView = function(identifyData) {
var $mediaButton,
isEditable = !data.mainid && data.mediastate == 'available',
src = !identifyData
? '/' + data.id + '/cover256.jpg?' + data.modified
? '/' + data.id + '/cover512.jpg?' + data.modified
: data.cover,
ratio = data.coverRatio || oml.config.coverRatio,
size = 256,
size = ui.coverSize,
reflectionSize = Math.round(size / 2);
$elements.forEach(function($element) {
@ -269,11 +286,14 @@ oml.ui.infoView = function(identifyData) {
if ($element == $cover) {
$image = $('<img>')
$image = Ox.Element({
element: '<img>',
tooltip: '' // TODO
})
.on({
load: function() {
var ratio = $image[0].width / $image[0].height;
updateCover(size, ratio);
ratio = $image[0].width / $image[0].height;
updateCover(ratio);
}
})
.attr({src: src})
@ -281,6 +301,11 @@ oml.ui.infoView = function(identifyData) {
position: 'absolute'
})
.hide()
.bindEvent({
singleclick: function() {
toggleCoverSize(ratio);
}
})
.appendTo($cover);
$reflection = $('<div>')

View file

@ -17,7 +17,7 @@ oml.ui.leftPanel = function() {
collapsed: !oml.user.ui.showInfo,
collapsible: true,
element: oml.$ui.info = oml.ui.info(),
size: ui.sidebarSize,
size: oml.getInfoHeight(),
tooltip: Ox._('info') + ' <span class="OxBright">'
+ Ox.SYMBOLS.SHIFT + 'I</span>'
}

View file

@ -12,15 +12,15 @@ oml.ui.list = function() {
delete oml.$ui.previewDialog;
},
copy: function(data) {
oml.clipboard.copy(data.ids, 'item');
oml.clipboard.copy(data.ids, 'book');
},
copyadd: function(data) {
oml.clipboard.copy(data.ids, 'item');
oml.clipboard.copy(data.ids, 'book');
},
cut: function(data) {
var listData = oml.getListData();
if (listData.editable && listData.type == 'static') {
oml.clipboard.copy(data.ids, 'item');
oml.clipboard.copy(data.ids, 'book');
oml.doHistory('cut', data.ids, ui._list, function() {
oml.UI.set({listSelection: []});
oml.$ui.folders.updateElement();
@ -31,7 +31,7 @@ oml.ui.list = function() {
cutadd: function(data) {
var listData = oml.getListData();
if (listData.editable && listData.type == 'static') {
oml.clipboard.add(data.ids, 'item');
oml.clipboard.add(data.ids, 'book');
oml.doHistory('cut', data.ids, ui._list, function() {
oml.UI.set({listSelection: []});
oml.$ui.folders.updateElement();
@ -96,6 +96,15 @@ oml.ui.list = function() {
oml.$ui.previewDialog.updateElement();
}
},
paste: function(data) {
var items = oml.clipboard.paste();
if (items.length && oml.clipboard.type() == 'book' && oml.getListData().editable) {
oml.doHistory('paste', items, ui._list, function() {
oml.UI.set({listSelection: items});
oml.reloadList();
});
}
},
resize: function(data) {
// this is the resize event of the split panel
that.size();

View file

@ -76,7 +76,7 @@ oml.ui.listDialog = function() {
// FIXME: UGLY
listNames[listNames.indexOf(listData.name)] = value;
listData.name = value;
//
// ...
oml.api.editList({
id: ui._list,
name: value

View file

@ -379,6 +379,24 @@ oml.ui.mainMenu = function() {
oml.UI.set({listSelection: []});
} else if (id == 'invertselection') {
oml.$ui.list.invertSelection();
} else if (Ox.contains(['cut', 'cutadd'], id)) {
var action = data.id == 'cut' ? 'copy' : 'add';
fromMenu = true;
oml.clipboard[action](ui.listSelection, 'item');
oml.doHistory('cut', ui.listSelection, ui._list, function() {
oml.UI.set({listSelection: []});
oml.reloadList();
});
} else if (Ox.contains(['copy', 'copyadd'], id)) {
var action = data.id == 'copy' ? 'copy' : 'add';
fromMenu = true;
oml.clipboard[action](ui.listSelection, 'item');
} else if (id == 'paste') {
var items = oml.clipboard.paste();
oml.doHistory('paste', items, ui._list, function() {
oml.UI.set({listSelection: items});
oml.reloadList();
});
} else if (data.id == 'clearclipboard') {
oml.clipboard.clear();
} else if (data.id == 'delete') {
@ -542,7 +560,7 @@ oml.ui.mainMenu = function() {
canCopy = canSelect && selectionItems,
canCut = canCopy && listData.editable,
canPaste = listData.editable && clipboardItems,
canAdd = canCopy && clipboardItems && clipboardItemType == ui.section,
canAdd = canCopy && clipboardItems && clipboardType == 'book',
canDelete = listData.user == username && selectionItems,
historyItems = oml.history.items(),
undoText = oml.history.undoText(),
@ -783,6 +801,24 @@ oml.ui.mainMenu = function() {
};
}
oml.clipboard.bindEvent(function(data, event) {
if (Ox.contains(['add', 'copy', 'clear'], event)) {
that.replaceMenu('editMenu', getEditMenu());
}
if (Ox.contains(['add', 'copy', 'paste'], event) && !fromMenu) {
that.highlightMenu('editMenu');
}
fromMenu = false;
});
oml.history.bindEvent(function(data, event) {
that.replaceMenu('editMenu', getEditMenu());
if (Ox.contains(['undo', 'redo'], event) && !fromMenu) {
that.highlightMenu('editMenu');
}
fromMenu = false;
});
that.updateElement = function(menu) {
(
menu ? Ox.makeArray(menu) : ['listMenu', 'editMenu', 'findMenu']

View file

@ -90,6 +90,9 @@
oml.URL.init().parse(function() {
oml.clipboard = Ox.Clipboard();
oml.history = Ox.History();
Ox.$window.on({
resize: oml.resizeWindow
});
oml.$ui.appPanel = oml.ui.appPanel().appendTo(Ox.$body);
oml.$ui.loadingIcon.updateElement(Ox.Request.requests());
Ox.Request.bindEvent({
@ -100,7 +103,7 @@
oml.$ui.loadingIcon.updateElement(data.requests);
}
});
if (oml.user.preferences.extensions) {
if (oml.user.preferences.extensions) {
try {
eval(oml.user.preferences.extensions)
} catch(e) {}

View file

@ -17,22 +17,30 @@ oml.ui.rightPanel = function() {
],
orientation: 'horizontal',
selected: !ui.item ? 'list' : 'item',
size: window.innerWidth - ui.showSidebar * ui.sidebarSize - 1
size: getSize()
})
.bindEvent({
resize: function(data) {
Ox.print('::RESIZING', data.size);
that.options({size: data.size});
oml.$ui.filtersOuterPanel.updateElement();
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'});
}
}
});
function getSize() {
return window.innerWidth - ui.showSidebar * ui.sidebarSize - 1;
}
that.updateElement = function() {
return that.triggerEvent('resize', {size: getSize()});
};
return that;
};

View file

@ -44,21 +44,20 @@ oml.ui.statusIcon = function(user, index) {
}
function getStatus(data) {
return !oml.user.online && index ? 'unknown'
return !oml.user.online ? '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(', ');
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',
@ -68,7 +67,6 @@ oml.ui.statusIcon = function(user, index) {
}).css({
background: '-webkit-linear-gradient(bottom, ' + color + ')',
});
that.find('div').css({
background: '-webkit-linear-gradient(top, ' + color + ')',
});
@ -85,4 +83,5 @@ oml.ui.statusIcon = function(user, index) {
}
return that;
};

View file

@ -740,9 +740,9 @@ oml.getListData = function(list) {
oml.getListFoldersHeight = function() {
var ui = oml.user.ui;
return Object.keys(ui.showFolder).reduce(function(value, id, index) {
return oml.$ui.folder.reduce(function(value, $folder, index) {
var items = oml.$ui.folderList[index].options('items').length;
return value + 16 + ui.showFolder[id] * (1 + items) * 16;
return value + 16 + !$folder.options('collapsed') * (1 + items) * 16;
}, 16);
};
@ -895,8 +895,7 @@ oml.hasDialogOrScreen = function() {
};
oml.reloadList = function() {
Ox.print('RELOAD LIST NOT IMPLEMENTED')
// ...
oml.$ui.list.updateElement();
};
oml.resizeFilters = function() {
@ -905,6 +904,16 @@ oml.resizeFilters = function() {
oml.resizeListFolders = function() {
// FIXME: does this have to be here?
/*
Ox.print(
'RESIZE LIST FOLDERS',
'SIDEBAR', oml.user.ui.sidebarSize,
'WIDTH', oml.getListFoldersWidth(),
'HEIGHT', oml.getListFoldersHeight(),
'INFO HEIGHT', oml.getInfoHeight(),
'AVAILABLE HEIGHT', window.innerHeight - 20 - 24 - 1 - oml.user.ui.showInfo * oml.getInfoHeight()
)
*/
var width = oml.getListFoldersWidth(),
columnWidth = width - 58;
oml.$ui.librariesList
@ -919,19 +928,12 @@ oml.resizeListFolders = function() {
.css({width: width + 'px'})
.resizeColumn('name', columnWidth);
});
/*
oml.$ui.librariesList
.$body.find('.OxContent')
.css({width: width + 'px'});
Ox.forEach(oml.$ui.folder, function($folder, index) {
oml.$ui.libraryList[index]
.$body.find('.OxContent')
.css({width: width + 'px'});
oml.$ui.folderList[index]
.$body.find('.OxContent')
.css({width: width + 'px'});
})
*/
};
oml.resizeWindow = function() {
oml.$ui.leftPanel && oml.$ui.leftPanel.size(2, oml.getInfoHeight());
oml.resizeListFolders();
oml.$ui.rightPanel && oml.$ui.rightPanel.updateElement();
};
oml.updateFilterMenus = function() {