diff options
-rw-r--r-- | docs/plugins/addon_plugin.rst | 4 | ||||
-rw-r--r-- | module/AddonManager.py | 18 | ||||
-rw-r--r-- | module/Api.py | 10 | ||||
-rw-r--r-- | module/FileManager.py | 9 | ||||
-rw-r--r-- | module/api/CoreApi.py | 5 | ||||
-rw-r--r-- | module/config/ConfigManager.py | 2 | ||||
-rw-r--r-- | module/interaction/EventManager.py | 80 | ||||
-rw-r--r-- | module/plugins/Hoster.py | 2 | ||||
-rw-r--r-- | module/remote/apitypes.py | 11 | ||||
-rw-r--r-- | module/remote/apitypes_debug.py | 3 | ||||
-rw-r--r-- | module/remote/pyload.thrift | 21 | ||||
-rw-r--r-- | module/remote/wsbackend/AsyncHandler.py | 27 | ||||
-rw-r--r-- | module/web/static/js/models/File.js | 7 | ||||
-rw-r--r-- | module/web/static/js/models/ServerStatus.js | 81 | ||||
-rw-r--r-- | module/web/static/js/views/dashboardView.js | 19 | ||||
-rw-r--r-- | module/web/static/js/views/headerView.js | 19 | ||||
-rw-r--r-- | module/web/static/js/views/linkGrabberModal.js | 3 | ||||
-rw-r--r-- | module/web/templates/default/base.html | 21 | ||||
-rwxr-xr-x | pyload.py | 2 |
19 files changed, 169 insertions, 175 deletions
diff --git a/docs/plugins/addon_plugin.rst b/docs/plugins/addon_plugin.rst index c2258f2aa..e18bd6b0f 100644 --- a/docs/plugins/addon_plugin.rst +++ b/docs/plugins/addon_plugin.rst @@ -59,7 +59,7 @@ What a basic excerpt would look like: :: Your Hook code here. """ - def coreReady(self): + def activate(self): print "Yay, the core is ready let's do some work." def downloadFinished(self, pyfile): @@ -147,7 +147,7 @@ Just store everything in ``self.info``. :: def setup(self): self.info = {"running": False} - def coreReady(self): + def activate(self): self.info["running"] = True Usable with: :: diff --git a/module/AddonManager.py b/module/AddonManager.py index 7d3852274..cc2181767 100644 --- a/module/AddonManager.py +++ b/module/AddonManager.py @@ -44,14 +44,14 @@ class AddonManager: self.createIndex() # manage addons on config change - self.addEvent("configChanged", self.manageAddons) + self.addEvent("config:changed", self.manageAddons) @lock - def callInHooks(self, event, *args): + def callInHooks(self, event, eventName, *args): """ Calls a method in all addons and catch / log errors""" for plugin in self.plugins.itervalues(): self.call(plugin, event, *args) - self.dispatchEvent(event, *args) + self.dispatchEvent(eventName, *args) def call(self, addon, f, *args): try: @@ -179,22 +179,22 @@ class AddonManager: self.call(plugin, "deactivate") def downloadPreparing(self, pyfile): - self.callInHooks("downloadPreparing", pyfile) + self.callInHooks("downloadPreparing", "download:preparing", pyfile) def downloadFinished(self, pyfile): - self.callInHooks("downloadFinished", pyfile) + self.callInHooks("downloadFinished", "download:finished", pyfile) def downloadFailed(self, pyfile): - self.callInHooks("downloadFailed", pyfile) + self.callInHooks("downloadFailed", "download:failed", pyfile) def packageFinished(self, package): - self.callInHooks("packageFinished", package) + self.callInHooks("packageFinished", "package:finished", package) def beforeReconnecting(self, ip): - self.callInHooks("beforeReconnecting", ip) + self.callInHooks("beforeReconnecting", "reconnecting:before", ip) def afterReconnecting(self, ip): - self.callInHooks("afterReconnecting", ip) + self.callInHooks("afterReconnecting", "reconnecting:after", ip) @lock def startThread(self, function, *args, **kwargs): diff --git a/module/Api.py b/module/Api.py index 96b10be9c..27d3e8ffe 100644 --- a/module/Api.py +++ b/module/Api.py @@ -131,16 +131,6 @@ class Api(Iface): return self.user_apis[uid] - def getEvents(self, uuid): - """Lists occurred events, may be affected to changes in future. - - :param uuid: self assigned string uuid which has to be unique - :return: list of `Events` - """ - # TODO: permissions? - # TODO - pass - ############################# # Auth+User Information ############################# diff --git a/module/FileManager.py b/module/FileManager.py index 082bdb4d4..0d8a35a64 100644 --- a/module/FileManager.py +++ b/module/FileManager.py @@ -292,7 +292,6 @@ class FileManager: return pyfile - #TODO def getDownloadStats(self, user=None): """ return number of downloads """ if user not in self.downloadstats: @@ -377,7 +376,9 @@ class FileManager: def updateFile(self, pyfile): """updates file""" self.db.updateFile(pyfile) - self.evm.dispatchEvent("file:updated", pyfile.fid, pyfile.packageid) + + # This event is thrown with pyfile or only fid + self.evm.dispatchEvent("file:updated", pyfile) def updatePackage(self, pypack): """updates a package""" @@ -395,7 +396,7 @@ class FileManager: # TODO: user context? if not self.getQueueStats(None, True)[0]: - self.core.addonManager.dispatchEvent("downloads:finished") + self.core.addonManager.dispatchEvent("download:allFinished") self.core.log.debug("All downloads finished") return True @@ -409,7 +410,7 @@ class FileManager: # TODO: user context? if not self.db.processcount(fid): - self.core.addonManager.dispatchEvent("downloads:processed") + self.core.addonManager.dispatchEvent("download:allProcessed") self.core.log.debug("All downloads processed") return True diff --git a/module/api/CoreApi.py b/module/api/CoreApi.py index 9338954d0..a86197813 100644 --- a/module/api/CoreApi.py +++ b/module/api/CoreApi.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from module.Api import Api, RequirePerm, Permission, ServerStatus, PackageStats +from module.Api import Api, RequirePerm, Permission, ServerStatus from module.utils.fs import join, free_space from module.utils import compare_time @@ -32,7 +32,8 @@ class CoreApi(ApiComponent): total = self.core.files.getDownloadStats(self.primaryUID) serverStatus = ServerStatus(0, - PackageStats(total[0], total[0] - queue[0], total[1], total[1] - queue[1]), + total[0], queue[0], + total[1], queue[1], 0, not self.core.threadManager.pause and self.isTimeDownload(), self.core.threadManager.pause, diff --git a/module/config/ConfigManager.py b/module/config/ConfigManager.py index eb96a49f6..8d908abaf 100644 --- a/module/config/ConfigManager.py +++ b/module/config/ConfigManager.py @@ -98,7 +98,7 @@ class ConfigManager(ConfigParser): self.values[user, section][option] = value self.saveValues(user, section) - if changed: self.core.evm.dispatchEvent("configChanged", section, option, value) + if changed: self.core.evm.dispatchEvent("config:changed", section, option, value) return changed def saveValues(self, user, section): diff --git a/module/interaction/EventManager.py b/module/interaction/EventManager.py index 976a92413..b25514b6a 100644 --- a/module/interaction/EventManager.py +++ b/module/interaction/EventManager.py @@ -2,9 +2,6 @@ from threading import Lock from traceback import print_exc -from time import time - -from module.utils import lock class EventManager: """ @@ -16,30 +13,25 @@ class EventManager: ===================== ================ =========================================================== Name Arguments Description ===================== ================ =========================================================== - metaEvent eventName, *args Called for every event, with eventName and original args - downloadPreparing fid A download was just queued and will be prepared now. - downloadStarts fid A plugin will immediately start the download afterwards. - linksAdded links, pid Someone just added links, you are able to modify these links. - allDownloadsProcessed All links were handled, pyLoad would idle afterwards. - allDownloadsFinished All downloads in the queue are finished. - unrarFinished folder, fname An Unrar job finished - configChanged sec, opt, value The config was changed. + event eventName, *args Called for every event, with eventName and original args + download:preparing fid A download was just queued and will be prepared now. + download:start fid A plugin will immediately start the download afterwards. + download:allProcessed All links were handled, pyLoad would idle afterwards. + download:allFinished All downloads in the queue are finished. + config:changed sec, opt, value The config was changed. ===================== ================ =========================================================== | Notes: - | allDownloadsProcessed is *always* called before allDownloadsFinished. - | configChanged is *always* called before pluginConfigChanged. + | download:allProcessed is *always* called before download:allFinished. """ - CLIENT_EVENTS = ("packageUpdated", "packageInserted", "linkUpdated", "packageDeleted") - def __init__(self, core): self.core = core self.log = core.log # uuid : list of events self.clients = {} - self.events = {"metaEvent": []} + self.events = {"event": []} self.lock = Lock() @@ -66,12 +58,12 @@ class EventManager: def dispatchEvent(self, event, *args): """dispatches event with args""" - for f in self.events["metaEvent"]: + for f in self.events["event"]: try: f(event, *args) except Exception, e: self.log.warning("Error calling event handler %s: %s, %s, %s" - % ("metaEvent", f, args, str(e))) + % ("event", f, args, str(e))) if self.core.debug: print_exc() @@ -83,54 +75,4 @@ class EventManager: self.log.warning("Error calling event handler %s: %s, %s, %s" % (event, f, args, str(e))) if self.core.debug: - print_exc() - - self.updateClients(event, args) - - @lock - def updateClients(self, event, args): - # append to client event queue - if event in self.CLIENT_EVENTS: - for uuid, client in self.clients.items(): - if client.delete(): - del self.clients[uuid] - else: - client.append(event, args) - - def removeFromEvents(self, func): - """ Removes func from all known events """ - for name, events in self.events.iteritems(): - if func in events: - events.remove(func) - - - -class Client: - - # delete clients after this time - TIMEOUT = 60 * 60 - # max events, if this value is reached you should assume that older events were dropped - MAX = 30 - - def __init__(self): - self.lastActive = time() - self.events = [] - - def delete(self): - return self.lastActive + self.TIMEOUT < time() - - def append(self, event, args): - ev = (event, args) - if ev not in self.events: - self.events.insert(0, ev) - - del self.events[self.MAX:] - - - def get(self): - self.lastActive = time() - - events = self.events - self.events = [] - - return [(ev, [str(x) for x in args]) for ev, args in events]
\ No newline at end of file + print_exc()
\ No newline at end of file diff --git a/module/plugins/Hoster.py b/module/plugins/Hoster.py index 1c53807b7..fbe1c7480 100644 --- a/module/plugins/Hoster.py +++ b/module/plugins/Hoster.py @@ -279,7 +279,7 @@ class Hoster(Base): filename = join(location, name) - self.core.addonManager.dispatchEvent("downloadStarts", self.pyfile, url, filename) + self.core.addonManager.dispatchEvent("download:start", self.pyfile, url, filename) try: newname = self.req.httpDownload(url, filename, get=get, post=post, ref=ref, cookies=cookies, diff --git a/module/remote/apitypes.py b/module/remote/apitypes.py index aaec2b3ce..83368c6de 100644 --- a/module/remote/apitypes.py +++ b/module/remote/apitypes.py @@ -297,11 +297,14 @@ class ProgressInfo(BaseObject): self.download = download class ServerStatus(BaseObject): - __slots__ = ['speed', 'files', 'notifications', 'paused', 'download', 'reconnect'] + __slots__ = ['speed', 'linkstotal', 'linksqueue', 'sizetotal', 'sizequeue', 'notifications', 'paused', 'download', 'reconnect'] - def __init__(self, speed=None, files=None, notifications=None, paused=None, download=None, reconnect=None): + def __init__(self, speed=None, linkstotal=None, linksqueue=None, sizetotal=None, sizequeue=None, notifications=None, paused=None, download=None, reconnect=None): self.speed = speed - self.files = files + self.linkstotal = linkstotal + self.linksqueue = linksqueue + self.sizetotal = sizetotal + self.sizequeue = sizequeue self.notifications = notifications self.paused = paused self.download = download @@ -427,8 +430,6 @@ class Iface(object): pass def getCoreConfig(self): pass - def getEvents(self, uuid): - pass def getFileInfo(self, fid): pass def getFileTree(self, pid, full): diff --git a/module/remote/apitypes_debug.py b/module/remote/apitypes_debug.py index 6d30f1da6..4fab11f96 100644 --- a/module/remote/apitypes_debug.py +++ b/module/remote/apitypes_debug.py @@ -37,7 +37,7 @@ classes = { 'PackageInfo' : [int, basestring, basestring, int, int, basestring, basestring, basestring, int, (list, basestring), int, bool, int, PackageStats, (list, int), (list, int)], 'PackageStats' : [int, int, int, int], 'ProgressInfo' : [basestring, basestring, basestring, int, int, int, (None, DownloadProgress)], - 'ServerStatus' : [int, PackageStats, int, bool, bool, bool], + 'ServerStatus' : [int, int, int, int, int, int, bool, bool, bool], 'ServiceDoesNotExists' : [basestring, basestring], 'ServiceException' : [basestring], 'TreeCollection' : [PackageInfo, (dict, int, FileInfo), (dict, int, PackageInfo)], @@ -82,7 +82,6 @@ methods = { 'getConfig': (dict, basestring, ConfigHolder), 'getConfigValue': basestring, 'getCoreConfig': (list, ConfigInfo), - 'getEvents': (list, EventInfo), 'getFileInfo': FileInfo, 'getFileTree': TreeCollection, 'getFilteredFileTree': TreeCollection, diff --git a/module/remote/pyload.thrift b/module/remote/pyload.thrift index dc6b1c406..06add4208 100644 --- a/module/remote/pyload.thrift +++ b/module/remote/pyload.thrift @@ -196,11 +196,14 @@ struct LinkStatus { struct ServerStatus { 1: ByteCount speed, - 2: PackageStats files, - 3: i16 notifications, - 4: bool paused, - 5: bool download, - 6: bool reconnect, + 2: i16 linkstotal, + 3: i16 linksqueue, + 4: ByteCount sizetotal, + 5: ByteCount sizequeue, + 6: i16 notifications, + 7: bool paused, + 8: bool download, + 9: bool reconnect, } struct InteractionTask { @@ -257,7 +260,7 @@ struct ConfigInfo { struct EventInfo { 1: string eventname, - 2: list<JSONString> event_args, + 2: list<JSONString> event_args, //will contain json objects } struct UserData { @@ -481,12 +484,6 @@ service Pyload { list<InteractionTask> getNotifications(), /////////////////////// - // Event Handling - /////////////////////// - - list<EventInfo> getEvents(1: string uuid), - - /////////////////////// // Account Methods /////////////////////// diff --git a/module/remote/wsbackend/AsyncHandler.py b/module/remote/wsbackend/AsyncHandler.py index 2f9b43ad2..5c08aa96d 100644 --- a/module/remote/wsbackend/AsyncHandler.py +++ b/module/remote/wsbackend/AsyncHandler.py @@ -16,11 +16,13 @@ # @author: RaNaN ############################################################################### +import re from Queue import Queue, Empty from threading import Lock from mod_pywebsocket.msgutil import receive_message +from module.Api import EventInfo from module.utils import lock from AbstractHandler import AbstractHandler @@ -41,31 +43,40 @@ class AsyncHandler(AbstractHandler): COMMAND = "start" PROGRESS_INTERVAL = 2 - STATUS_INTERVAL = 60 + EVENT_PATTERN = re.compile(r"^(package|file)", re.I) def __init__(self, api): AbstractHandler.__init__(self, api) self.clients = [] self.lock = Lock() + self.core.evm.addEvent("event", self.add_event) + @lock def on_open(self, req): req.queue = Queue() req.interval = self.PROGRESS_INTERVAL + req.events = self.EVENT_PATTERN req.mode = Mode.STANDBY self.clients.append(req) @lock def on_close(self, req): try: + del req.queue self.clients.remove(req) except ValueError: # ignore when not in list pass @lock - def add_event(self, event): + def add_event(self, event, *args): + # Convert arguments to json suited instance + event = EventInfo(event, [x.toInfoData() if hasattr(x, 'toInfoData') else x for x in args]) + for req in self.clients: - req.queue.put(event) + if req.events.search(event.eventname): + self.log.debug("Pushing event %s" % event) + req.queue.put(event) def transfer_data(self, req): while True: @@ -100,6 +111,8 @@ class AsyncHandler(AbstractHandler): if func == "setInterval": req.interval = args[0] + elif func == "setEvents": + req.events = re.compile(args[0], re.I) elif func == self.COMMAND: req.mode = Mode.RUNNING @@ -108,7 +121,13 @@ class AsyncHandler(AbstractHandler): """ Listen for events, closes socket when returning True """ try: ev = req.queue.get(True, req.interval) - self.send(req, ev) + try: + self.send(req, ev) + except TypeError: + self.log.debug("Event %s not converted" % ev) + ev.event_args = [] + # Resend the event without arguments + self.send(req, ev) except Empty: # TODO: server status is not enough diff --git a/module/web/static/js/models/File.js b/module/web/static/js/models/File.js index fa0945713..2ac6c05f5 100644 --- a/module/web/static/js/models/File.js +++ b/module/web/static/js/models/File.js @@ -31,6 +31,13 @@ define(['jquery', 'backbone', 'underscore', 'utils/apitypes'], function($, Backb }, + fetch: function(options){ + options || (options = {}); + options.url = 'api/getFileInfo/' + this.get('fid'); + + return Backbone.Model.prototype.fetch.call(this, options); + }, + destroy: function(options) { options || (options = {}); // TODO: as post data diff --git a/module/web/static/js/models/ServerStatus.js b/module/web/static/js/models/ServerStatus.js index 2430a9ffd..9242bdf95 100644 --- a/module/web/static/js/models/ServerStatus.js +++ b/module/web/static/js/models/ServerStatus.js @@ -1,47 +1,46 @@ define(['jquery', 'backbone', 'underscore'], function($, Backbone, _) { - return Backbone.Model.extend({ + return Backbone.Model.extend({ + + defaults: { + speed: 0, + linkstotal: 0, + linksqueue: 0, + sizetotal: 0, + sizequeue: 0, + notifications: -1, + paused: false, + download: false, + reconnect: false + }, + + // Model Constructor + initialize: function() { + + }, + + fetch: function() { + options || (options = {}); + options.url = 'api/getServerStatus'; + + return Backbone.Model.prototype.fetch.call(this, options); + }, + + toJSON: function(options) { + var obj = Backbone.Model.prototype.toJSON.call(this, options); + + obj.linksdone = obj.linkstotal - obj.linksqueue; + obj.sizedone = obj.sizetotal - obj.sizequeue; + if (obj.speed && obj.speed > 0) + obj.eta = Math.round(obj.sizequeue / obj.speed); + else if (obj.sizequeue > 0) + obj.eta = Infinity; + else + obj.eta = 0; - defaults: { - speed: 0, - files: null, - notifications: -1, - paused: false, - download: false, - reconnect: false - }, - - // Model Constructor - initialize: function() { - - }, - - fetch: function() { - options || (options = {}); - options.url = 'api/getServerStatus'; - - return Backbone.Model.prototype.fetch.call(this, options); - }, - - toJSON: function(options) { - var obj = Backbone.Model.prototype.toJSON.call(this, options); - - // stats are not available - if (obj.files === null) return obj; + } - obj.files.linksleft = obj.files.linkstotal - obj.files.linksdone; - obj.files.sizeleft = obj.files.sizetotal - obj.files.sizedone; - if (obj.speed && obj.speed > 0) - obj.files.eta = Math.round(obj.files.sizeleft / obj.speed); - else if (obj.files.sizeleft > 0) - obj.files.eta = Infinity; - else - obj.files.eta = 0; - - return obj; - } - - }); -});
\ No newline at end of file + }); + });
\ No newline at end of file diff --git a/module/web/static/js/views/dashboardView.js b/module/web/static/js/views/dashboardView.js index d9ff1c5fc..58a50777c 100644 --- a/module/web/static/js/views/dashboardView.js +++ b/module/web/static/js/views/dashboardView.js @@ -35,7 +35,11 @@ define(['jquery', 'backbone', 'underscore', 'app', 'models/TreeCollection', self.tree.fetch(); }); - // TODO file:added + App.vent.on('file:updated', _.bind(this.fileUpdated, this)); + + // TODO: file:added + // TODO: package:deleted + // TODO: package:updated }, init: function() { @@ -48,7 +52,7 @@ define(['jquery', 'backbone', 'underscore', 'app', 'models/TreeCollection', self.tree.get('packages').on('add', function(pack) { console.log('Package ' + pack.get('pid') + ' added to tree'); self.appendPackage(pack, 0, true); - + self.openPackage(pack); }); }}); @@ -144,6 +148,17 @@ define(['jquery', 'backbone', 'underscore', 'app', 'models/TreeCollection', //TODO: show placeholder when nothing is displayed (filtered content empty) this.fileUL.fadeIn(); App.vent.trigger('dashboard:updated'); + }, + + // Refresh the file if it is currently shown + fileUpdated: function(data) { + // this works with ids and object + var file = this.files.get(data); + if (file) + if (_.isObject(data)) // update directly + file.set(data); + else // fetch from server + file.fetch(); } }); });
\ No newline at end of file diff --git a/module/web/static/js/views/headerView.js b/module/web/static/js/views/headerView.js index c22f173c4..35df06003 100644 --- a/module/web/static/js/views/headerView.js +++ b/module/web/static/js/views/headerView.js @@ -12,6 +12,7 @@ define(['jquery', 'underscore', 'backbone', 'app', 'models/ServerStatus', 'flot' }, templateStatus: _.compile($('#template-header-status').html()), + templateProgress: _.compile($('#template-header-progress').html()), // Will hold the link grabber grabber: null, @@ -100,11 +101,15 @@ define(['jquery', 'underscore', 'backbone', 'app', 'models/ServerStatus', 'flot' }, render: function() { -// console.log('Render header'); + // TODO: what should be displayed in the header + // queue/processing size? this.$('.status-block').html( this.templateStatus(this.status.toJSON()) ); + + // TODO: render progress + this.$('.progress-list'); }, toggle_taskList: function() { @@ -132,10 +137,10 @@ define(['jquery', 'underscore', 'backbone', 'app', 'models/ServerStatus', 'flot' if (data['@class'] === "ServerStatus") { this.status.set(data); } - else if (data['@class'] === 'progress') + else if (_.isArray(data)) this.onProgressUpdate(data); - else if (data['@class'] === 'event') - this.onEvent(data); + else if (data['@class'] === 'EventInfo') + this.onEvent(data.eventname, data.event_args); else console.log('Unknown Async input'); @@ -145,8 +150,10 @@ define(['jquery', 'underscore', 'backbone', 'app', 'models/ServerStatus', 'flot' }, - onEvent: function(event) { - + onEvent: function(event, args) { + args.unshift(event); + console.log('Core send event', args); + App.vent.trigger.apply(App.vent, args); } }); diff --git a/module/web/static/js/views/linkGrabberModal.js b/module/web/static/js/views/linkGrabberModal.js index ea11aa339..9f7a5882d 100644 --- a/module/web/static/js/views/linkGrabberModal.js +++ b/module/web/static/js/views/linkGrabberModal.js @@ -30,7 +30,8 @@ define(['jquery', 'underscore', 'app', 'views/abstract/modalView', 'text!tpl/def type: 'POST', data: { name: JSON.stringify($('#inputPackageName').val()), - links: JSON.stringify(['http://download.pyload.org/random.bin', 'invalid link', 'invalid link 2', 'invalid link 3', 'inavlid link 4', + links: JSON.stringify(['http://download.pyload.org/random.bin', 'http://download.pyload.org/random100.bin', + 'invalid link', 'invalid link 2', 'invalid link 3', 'inavlid link 4', 'http://download.pyload.org/random.bin', 'http://download.pyload.org/random.bin', 'http://download.pyload.org/random.bin', 'A really really long invalid url that should exceed length of most of the urls by far and split into two lines']) }, diff --git a/module/web/templates/default/base.html b/module/web/templates/default/base.html index 71b913f9e..2046dd36d 100644 --- a/module/web/templates/default/base.html +++ b/module/web/templates/default/base.html @@ -40,10 +40,25 @@ </script>
<script type="text/template" id="template-header-status">
- <span class="pull-right eta"><% formatTime files.eta %></span><br>
- <span class="pull-right remeaning"><% formatSize files.sizeleft %></span><br>
+ <span class="pull-right eta"><% formatTime eta %></span><br>
+ <span class="pull-right remeaning"><% formatSize sizequeue %></span><br>
<span class="pull-right"><span
- style="font-weight:bold;color: #fff !important;"><% files.linksleft %></span> of <% files.linkstotal %></span>
+ style="font-weight:bold;color: #fff !important;"><% linksqueue %></span> of <% linkstotal %></span>
+ </script>
+
+ <script type="text/template" id="template-header-progress">
+ <li> {# background-image for logo #}
+ Some Download
+ <span class="pull-right">YouTube</span>
+
+ <div class="progress">
+ <div class="bar" style="width: 25%"></div>
+ </div>
+ 20 Kb of 23 MB (500 kb/s)
+ <span class="pull-right">
+ 50%
+ </span>
+ </li>
</script>
{% block head %}
@@ -447,7 +447,7 @@ class Core(object): self.addonManager.activateAddons() self.log.info(_("pyLoad is up and running")) - self.eventManager.dispatchEvent("coreReady") + self.eventManager.dispatchEvent("core:ready") #test api # from module.common.APIExerciser import startApiExerciser |