From 9a6ea22616cf3cc67e292c908521b79764400faf Mon Sep 17 00:00:00 2001 From: RaNaN Date: Sun, 18 Aug 2013 17:01:17 +0200 Subject: new linkgrabber --- pyload/api/CollectorApi.py | 37 ----------- pyload/api/DownloadApi.py | 11 ---- pyload/api/DownloadPreparingApi.py | 72 +++++++++----------- pyload/api/__init__.py | 2 +- pyload/database/FileDatabase.py | 18 ----- pyload/interaction/EventManager.py | 12 ++-- pyload/plugins/Account.py | 4 +- pyload/plugins/Crypter.py | 8 ++- pyload/remote/apitypes.py | 27 ++------ pyload/remote/apitypes_debug.py | 17 ++--- pyload/remote/pyload.thrift | 29 ++------ pyload/remote/wsbackend/AsyncHandler.py | 13 +++- pyload/threads/DecrypterThread.py | 2 +- pyload/threads/InfoThread.py | 14 ++-- pyload/threads/ThreadManager.py | 3 +- pyload/utils/packagetools.py | 52 +++++++-------- pyload/web/app/scripts/collections/LinkList.js | 14 ++++ pyload/web/app/scripts/config.js | 6 +- pyload/web/app/scripts/helpers/linkStatus.js | 18 +++++ pyload/web/app/scripts/models/CollectorPackage.js | 77 ++++++++++++++++++++++ pyload/web/app/scripts/models/LinkStatus.js | 22 +++++++ pyload/web/app/scripts/views/abstract/modalView.js | 6 +- pyload/web/app/scripts/views/headerView.js | 2 +- pyload/web/app/scripts/views/linkGrabberModal.js | 49 -------------- .../app/scripts/views/linkgrabber/collectorView.js | 21 ++++++ .../web/app/scripts/views/linkgrabber/modalView.js | 61 +++++++++++++++++ .../app/scripts/views/linkgrabber/packageView.js | 58 ++++++++++++++++ pyload/web/app/styles/default/linkgrabber.less | 18 +++++ pyload/web/app/styles/default/main.less | 1 + .../app/templates/default/dialogs/linkgrabber.html | 49 -------------- .../app/templates/default/linkgrabber/modal.html | 41 ++++++++++++ .../app/templates/default/linkgrabber/package.html | 30 +++++++++ .../app/templates/default/settings/actionbar.html | 10 +-- .../web/app/templates/default/settings/layout.html | 4 +- pyload/web/bower.json | 2 +- 35 files changed, 487 insertions(+), 323 deletions(-) delete mode 100644 pyload/api/CollectorApi.py create mode 100644 pyload/web/app/scripts/collections/LinkList.js create mode 100644 pyload/web/app/scripts/helpers/linkStatus.js create mode 100644 pyload/web/app/scripts/models/CollectorPackage.js create mode 100644 pyload/web/app/scripts/models/LinkStatus.js delete mode 100644 pyload/web/app/scripts/views/linkGrabberModal.js create mode 100644 pyload/web/app/scripts/views/linkgrabber/collectorView.js create mode 100644 pyload/web/app/scripts/views/linkgrabber/modalView.js create mode 100644 pyload/web/app/scripts/views/linkgrabber/packageView.js create mode 100644 pyload/web/app/styles/default/linkgrabber.less delete mode 100755 pyload/web/app/templates/default/dialogs/linkgrabber.html create mode 100755 pyload/web/app/templates/default/linkgrabber/modal.html create mode 100644 pyload/web/app/templates/default/linkgrabber/package.html diff --git a/pyload/api/CollectorApi.py b/pyload/api/CollectorApi.py deleted file mode 100644 index 49340285e..000000000 --- a/pyload/api/CollectorApi.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from pyload.Api import Api, RequirePerm, Permission - -from ApiComponent import ApiComponent - -class CollectorApi(ApiComponent): - """ Link collector """ - - @RequirePerm(Permission.All) - def getCollector(self): - pass - - @RequirePerm(Permission.Add) - def addToCollector(self, links): - pass - - @RequirePerm(Permission.Add) - def addFromCollector(self, name, new_name): - pass - - @RequirePerm(Permission.Delete) - def deleteCollPack(self, name): - pass - - @RequirePerm(Permission.Add) - def renameCollPack(self, name, new_name): - pass - - @RequirePerm(Permission.Delete) - def deleteCollLink(self, url): - pass - - -if Api.extend(CollectorApi): - del CollectorApi \ No newline at end of file diff --git a/pyload/api/DownloadApi.py b/pyload/api/DownloadApi.py index 0a01007b5..d855dd882 100644 --- a/pyload/api/DownloadApi.py +++ b/pyload/api/DownloadApi.py @@ -11,17 +11,6 @@ from ApiComponent import ApiComponent class DownloadApi(ApiComponent): """ Component to create, add, delete or modify downloads.""" - @RequirePerm(Permission.Add) - def generateAndAddPackages(self, links, paused=False): - """Generates and add packages - - :param links: list of urls - :param paused: paused package - :return: list of package ids - """ - return [self.addPackageP(name, urls, "", paused) for name, urls - in self.generatePackages(links).iteritems()] - @RequirePerm(Permission.Add) def createPackage(self, name, folder, root, password="", site="", comment="", paused=False): """Create a new package. diff --git a/pyload/api/DownloadPreparingApi.py b/pyload/api/DownloadPreparingApi.py index 0a47fe5ab..d74f0aee2 100644 --- a/pyload/api/DownloadPreparingApi.py +++ b/pyload/api/DownloadPreparingApi.py @@ -3,7 +3,9 @@ from itertools import chain -from pyload.Api import Api, RequirePerm, Permission, OnlineCheck, LinkStatus, urlmatcher +from pyload.Api import Api, DownloadStatus as DS,\ + RequirePerm, Permission, OnlineCheck, LinkStatus, urlmatcher +from pyload.utils import uniqify from pyload.utils.fs import join from pyload.utils.packagetools import parseNames from pyload.network.RequestFactory import getURL @@ -14,33 +16,13 @@ class DownloadPreparingApi(ApiComponent): """ All kind of methods to parse links or retrieve online status """ @RequirePerm(Permission.Add) - def parseURLs(self, html=None, url=None): - """Parses html content or any arbitrary text for links and returns result of `checkURLs` - - :param html: html source - :return: - """ - urls = [] - - if html: - urls += [x[0] for x in urlmatcher.findall(html)] - - if url: - page = getURL(url) - urls += [x[0] for x in urlmatcher.findall(page)] - - # remove duplicates - return self.checkURLs(set(urls)) - - - @RequirePerm(Permission.Add) - def checkURLs(self, urls): + def parseLinks(self, links): """ Gets urls and returns pluginname mapped to list of matching urls. - :param urls: + :param links: :return: {plugin: urls} """ - data, crypter = self.core.pluginManager.parseUrls(urls) + data, crypter = self.core.pluginManager.parseUrls(links) plugins = {} for url, plugin in chain(data, crypter): @@ -52,31 +34,23 @@ class DownloadPreparingApi(ApiComponent): return plugins @RequirePerm(Permission.Add) - def checkOnlineStatus(self, urls): + def checkLinks(self, links): """ initiates online status check, will also decrypt files. :param urls: :return: initial set of data as :class:`OnlineCheck` instance containing the result id """ - data, crypter = self.core.pluginManager.parseUrls(urls) + hoster, crypter = self.core.pluginManager.parseUrls(links) # initial result does not contain the crypter links - tmp = [(url, (url, LinkStatus(url, pluginname, "unknown", 3, 0))) for url, pluginname in data] + tmp = [(url, LinkStatus(url, url, pluginname, -1, DS.Queued)) for url, pluginname in hoster + crypter] data = parseNames(tmp) - result = {} - - for k, v in data.iteritems(): - for url, status in v: - status.packagename = k - result[url] = status - - data.update(crypter) # hoster and crypter will be processed - rid = self.core.threadManager.createResultThread(data, False) + rid = self.core.threadManager.createResultThread(data) - return OnlineCheck(rid, result) + return OnlineCheck(rid, data) @RequirePerm(Permission.Add) - def checkOnlineStatusContainer(self, urls, container, data): + def checkContainer(self, filename, data): """ checks online status of urls and a submitted container file :param urls: list of urls @@ -84,11 +58,27 @@ class DownloadPreparingApi(ApiComponent): :param data: file content :return: :class:`OnlineCheck` """ - th = open(join(self.core.config["general"]["download_folder"], "tmp_" + container), "wb") + th = open(join(self.core.config["general"]["download_folder"], "tmp_" + filename), "wb") th.write(str(data)) th.close() - urls.append(th.name) - return self.checkOnlineStatus(urls) + return self.checkLinks([th.name]) + + @RequirePerm(Permission.Add) + def checkHTML(self, html, url): + """Parses html content or any arbitrary text for links and returns result of `checkURLs` + + :param html: html source + :return: + """ + urls = [] + if html: + urls += [x[0] for x in urlmatcher.findall(html)] + if url: + page = getURL(url) + urls += [x[0] for x in urlmatcher.findall(page)] + + # remove duplicates + return self.checkLinks(uniqify(urls)) @RequirePerm(Permission.Add) def pollResults(self, rid): diff --git a/pyload/api/__init__.py b/pyload/api/__init__.py index 1348fd26f..bea46011d 100644 --- a/pyload/api/__init__.py +++ b/pyload/api/__init__.py @@ -1,5 +1,5 @@ __all__ = ["CoreApi", "ConfigApi", "DownloadApi", "DownloadPreparingApi", "FileApi", - "CollectorApi", "UserInteractionApi", "AccountApi", "AddonApi"] + "UserInteractionApi", "AccountApi", "AddonApi"] # Import all components # from .import * diff --git a/pyload/database/FileDatabase.py b/pyload/database/FileDatabase.py index 649c1f1ef..a07486443 100644 --- a/pyload/database/FileDatabase.py +++ b/pyload/database/FileDatabase.py @@ -128,24 +128,6 @@ class FileMethods(DatabaseMethods): self.c.execute('UPDATE files SET fileorder=fileorder-1 WHERE fileorder > ? AND package=? AND owner=?', (order, package, owner)) - @async - def saveCollector(self, owner, data): - """ simply save the json string to database """ - self.c.execute("INSERT INTO collector(owner, data) VALUES (?,?)", (owner, data)) - - @queue - def retrieveCollector(self, owner): - """ retrieve the saved string """ - self.c.execute('SELECT data FROM collector WHERE owner=?', (owner,)) - r = self.c.fetchone() - if not r: return None - return r[0] - - @async - def deleteCollector(self, owner): - """ drop saved user collector """ - self.c.execute('DELETE FROM collector WHERE owner=?', (owner,)) - @queue def getAllFiles(self, package=None, search=None, state=None, owner=None): """ Return dict with file information diff --git a/pyload/interaction/EventManager.py b/pyload/interaction/EventManager.py index 329961d93..8cc1e81d2 100644 --- a/pyload/interaction/EventManager.py +++ b/pyload/interaction/EventManager.py @@ -55,23 +55,21 @@ class EventManager: if func in events: events.remove(func) - def dispatchEvent(self, event, *args): + def dispatchEvent(self, event, *args, **kwargs): """dispatches event with args""" for f in self.events["event"]: try: - f(event, *args) + f(event, *args, **kwargs) except Exception, e: self.log.warning("Error calling event handler %s: %s, %s, %s" % ("event", f, args, str(e))) - if self.core.debug: - print_exc() + self.core.print_exc() if event in self.events: for f in self.events[event]: try: - f(*args) + f(*args, **kwargs) except Exception, e: self.log.warning("Error calling event handler %s: %s, %s, %s" % (event, f, args, str(e))) - if self.core.debug: - print_exc() \ No newline at end of file + self.core.print_exc() \ No newline at end of file diff --git a/pyload/plugins/Account.py b/pyload/plugins/Account.py index 26a6124b6..ed0769fb4 100644 --- a/pyload/plugins/Account.py +++ b/pyload/plugins/Account.py @@ -95,9 +95,9 @@ class Account(Base): return self.config_data[option].input.default_value def setConfig(self, option, value): - """ Sets a config value for this account instance or global plugin config """ + """ Sets a config value for this account instance. Modifying the global values is not allowed. """ if option not in self.config_data: - return Base.setConfig(self, option, value) + return value = from_string(value, self.config_data[option].input.type) # given value is the default value and does not need to be saved at all diff --git a/pyload/plugins/Crypter.py b/pyload/plugins/Crypter.py index d14960308..d780ad805 100644 --- a/pyload/plugins/Crypter.py +++ b/pyload/plugins/Crypter.py @@ -2,6 +2,7 @@ from traceback import print_exc +from pyload.Api import LinkStatus from pyload.utils import to_list, has_method, uniqify from pyload.utils.fs import exists, remove, fs_encode from pyload.utils.packagetools import parseNames @@ -12,10 +13,9 @@ from Base import Base, Retry class Package: """ Container that indicates that a new package should be created """ - def __init__(self, name, urls=None, folder=None): + def __init__(self, name=None, urls=None): self.name = name self.urls = urls if urls else [] - self.folder = folder # nested packages self.packs = [] @@ -23,6 +23,10 @@ class Package: def addURL(self, url): self.urls.append(url) + def addLink(self, url, name, status, size): + # TODO: allow to add urls with known information + pass + def addPackage(self, pack): self.packs.append(pack) diff --git a/pyload/remote/apitypes.py b/pyload/remote/apitypes.py index 385f4ca07..d186717d4 100644 --- a/pyload/remote/apitypes.py +++ b/pyload/remote/apitypes.py @@ -241,15 +241,14 @@ class InvalidConfigSection(ExceptionObject): self.section = section class LinkStatus(BaseObject): - __slots__ = ['url', 'name', 'plugin', 'size', 'status', 'packagename'] + __slots__ = ['url', 'name', 'plugin', 'size', 'status'] - def __init__(self, url=None, name=None, plugin=None, size=None, status=None, packagename=None): + def __init__(self, url=None, name=None, plugin=None, size=None, status=None): self.url = url self.name = name self.plugin = plugin self.size = size self.status = status - self.packagename = packagename class OnlineCheck(BaseObject): __slots__ = ['rid', 'data'] @@ -368,8 +367,6 @@ class UserDoesNotExists(ExceptionObject): self.user = user class Iface(object): - def addFromCollector(self, name, paused): - pass def addLinks(self, pid, links): pass def addLocalFile(self, pid, name, path): @@ -380,26 +377,20 @@ class Iface(object): pass def addPackageP(self, name, links, password, paused): pass - def addToCollector(self, links): - pass def addUser(self, username, password): pass def callAddon(self, plugin, func, arguments): pass def callAddonHandler(self, plugin, func, pid_or_fid): pass - def checkOnlineStatus(self, urls): + def checkContainer(self, filename, data): pass - def checkOnlineStatusContainer(self, urls, filename, data): + def checkHTML(self, html, url): pass - def checkURLs(self, urls): + def checkLinks(self, links): pass def createPackage(self, name, folder, root, password, site, comment, paused): pass - def deleteCollLink(self, url): - pass - def deleteCollPack(self, name): - pass def deleteConfig(self, plugin): pass def deleteFiles(self, fids): @@ -412,8 +403,6 @@ class Iface(object): pass def freeSpace(self): pass - def generateAndAddPackages(self, links, paused): - pass def generateDownloadLink(self, fid, timeout): pass def generatePackages(self, links): @@ -432,8 +421,6 @@ class Iface(object): pass def getAvailablePlugins(self): pass - def getCollector(self): - pass def getConfig(self): pass def getConfigValue(self, section, option): @@ -484,7 +471,7 @@ class Iface(object): pass def orderPackage(self, pids, position): pass - def parseURLs(self, html, url): + def parseLinks(self, links): pass def pauseServer(self): pass @@ -498,8 +485,6 @@ class Iface(object): pass def removeUser(self, uid): pass - def renameCollPack(self, name, new_name): - pass def restart(self): pass def restartFailed(self): diff --git a/pyload/remote/apitypes_debug.py b/pyload/remote/apitypes_debug.py index 177029054..1c34b702e 100644 --- a/pyload/remote/apitypes_debug.py +++ b/pyload/remote/apitypes_debug.py @@ -32,7 +32,7 @@ classes = { 'Input' : [int, (None, basestring), (None, basestring)], 'InteractionTask' : [int, int, Input, basestring, basestring, basestring], 'InvalidConfigSection' : [basestring], - 'LinkStatus' : [basestring, basestring, basestring, int, int, basestring], + 'LinkStatus' : [basestring, basestring, basestring, int, int], 'OnlineCheck' : [int, (dict, basestring, LinkStatus)], 'PackageDoesNotExists' : [int], 'PackageInfo' : [int, basestring, basestring, int, int, basestring, basestring, basestring, int, (list, basestring), int, bool, int, PackageStats, (list, int), (list, int)], @@ -47,29 +47,24 @@ classes = { } methods = { - 'addFromCollector': int, 'addLinks': None, 'addLocalFile': None, 'addPackage': int, 'addPackageChild': int, 'addPackageP': int, - 'addToCollector': None, 'addUser': UserData, 'callAddon': None, 'callAddonHandler': None, - 'checkOnlineStatus': OnlineCheck, - 'checkOnlineStatusContainer': OnlineCheck, - 'checkURLs': (dict, basestring, list), + 'checkContainer': OnlineCheck, + 'checkHTML': OnlineCheck, + 'checkLinks': OnlineCheck, 'createPackage': int, - 'deleteCollLink': None, - 'deleteCollPack': None, 'deleteConfig': None, 'deleteFiles': None, 'deletePackages': None, 'findFiles': TreeCollection, 'findPackages': TreeCollection, 'freeSpace': int, - 'generateAndAddPackages': (list, int), 'generateDownloadLink': basestring, 'generatePackages': (dict, basestring, list), 'getAccountInfo': AccountInfo, @@ -79,7 +74,6 @@ methods = { 'getAllFiles': TreeCollection, 'getAllUserData': (dict, int, UserData), 'getAvailablePlugins': (list, ConfigInfo), - 'getCollector': (list, LinkStatus), 'getConfig': (dict, basestring, ConfigHolder), 'getConfigValue': basestring, 'getCoreConfig': (list, ConfigInfo), @@ -105,14 +99,13 @@ methods = { 'movePackage': bool, 'orderFiles': None, 'orderPackage': None, - 'parseURLs': (dict, basestring, list), + 'parseLinks': (dict, basestring, list), 'pauseServer': None, 'pollResults': OnlineCheck, 'quit': None, 'recheckPackage': None, 'removeAccount': None, 'removeUser': None, - 'renameCollPack': None, 'restart': None, 'restartFailed': None, 'restartFile': None, diff --git a/pyload/remote/pyload.thrift b/pyload/remote/pyload.thrift index 702bd9b94..d41b6d10b 100644 --- a/pyload/remote/pyload.thrift +++ b/pyload/remote/pyload.thrift @@ -193,14 +193,13 @@ struct TreeCollection { 3: map packages } -// general info about link, used for collector and online results +// general info about link, used for online results struct LinkStatus { 1: string url, 2: string name, 3: PluginName plugin, 4: ByteCount size, // size <= 0 : unknown 5: DownloadStatus status, - 6: string packagename, } struct ServerStatus { @@ -299,7 +298,7 @@ struct AccountInfo { struct OnlineCheck { 1: ResultID rid, // -1 -> nothing more to get - 2: map data, // url to result + 2: map data, // package name to result } // exceptions @@ -383,12 +382,12 @@ service Pyload { // Download Preparing /////////////////////// - map checkURLs(1: LinkList urls), - map parseURLs(1: string html, 2: string url), + map parseLinks(1: LinkList links), // parses results and generates packages - OnlineCheck checkOnlineStatus(1: LinkList urls), - OnlineCheck checkOnlineStatusContainer(1: LinkList urls, 2: string filename, 3: binary data) + OnlineCheck checkLinks(1: LinkList links), + OnlineCheck checkContainer(1: string filename, 2: binary data), + OnlineCheck checkHTML(1: string html, 2: string url), // poll results from previously started online check OnlineCheck pollResults(1: ResultID rid), @@ -400,8 +399,6 @@ service Pyload { // Download /////////////////////// - list generateAndAddPackages(1: LinkList links, 2: bool paused), - PackageID createPackage(1: string name, 2: string folder, 3: PackageID root, 4: string password, 5: string site, 6: string comment, 7: bool paused), @@ -426,22 +423,10 @@ service Pyload { void restartPackage(1: PackageID pid), void restartFile(1: FileID fid), void recheckPackage(1: PackageID pid), - void restartFailed(), + void restartFailed() void stopDownloads(1: list fids), void stopAllDownloads(), - /////////////////////// - // Collector - /////////////////////// - - list getCollector(), - - void addToCollector(1: LinkList links), - PackageID addFromCollector(1: string name, 2: bool paused), - void renameCollPack(1: string name, 2: string new_name), - void deleteCollPack(1: string name), - void deleteCollLink(1: string url), - //////////////////////////// // File Information retrieval //////////////////////////// diff --git a/pyload/remote/wsbackend/AsyncHandler.py b/pyload/remote/wsbackend/AsyncHandler.py index 7eee40707..d53d6bcea 100644 --- a/pyload/remote/wsbackend/AsyncHandler.py +++ b/pyload/remote/wsbackend/AsyncHandler.py @@ -75,10 +75,18 @@ class AsyncHandler(AbstractHandler): pass @lock - def add_event(self, event, *args): + def add_event(self, event, *args, **kwargs): # Convert arguments to json suited instance event = EventInfo(event, [x.toInfoData() if hasattr(x, 'toInfoData') else x for x in args]) + # use the user kwarg argument to determine access + user = None + if 'user' in kwargs: + user = kwargs['user'] + del kwargs['user'] + if hasattr(user, 'uid'): + user = user.uid + for req in self.clients: # Not logged in yet if not req.api: continue @@ -87,6 +95,9 @@ class AsyncHandler(AbstractHandler): # TODO: events are security critical, this should be revised later # TODO: permissions? interaction etc if not req.api.user.isAdmin(): + if user is not None and req.api.primaryUID != user: + break + skip = False for arg in args: if hasattr(arg, 'owner') and arg.owner != req.api.primaryUID: diff --git a/pyload/threads/DecrypterThread.py b/pyload/threads/DecrypterThread.py index 77502569c..e4df2ee75 100644 --- a/pyload/threads/DecrypterThread.py +++ b/pyload/threads/DecrypterThread.py @@ -55,7 +55,7 @@ class DecrypterThread(BaseThread): plugin.logDebug("Decrypted", plugin_result) result.extend(plugin_result) - #TODO + #TODO package names are optional result = uniqify(result) pack_names = {} urls = [] diff --git a/pyload/threads/InfoThread.py b/pyload/threads/InfoThread.py index fba2a9056..6e685cdcf 100644 --- a/pyload/threads/InfoThread.py +++ b/pyload/threads/InfoThread.py @@ -4,7 +4,7 @@ from time import time from traceback import print_exc -from pyload.Api import LinkStatus +from pyload.Api import LinkStatus, DownloadStatus from pyload.utils.packagetools import parseNames from pyload.utils import has_method, accumulate @@ -100,17 +100,11 @@ class InfoThread(BaseThread): if len(self.cache) >= 20 or force: #used for package generating - tmp = [(name, (url, LinkStatus(name, plugin, "unknown", status, int(size)))) - for name, size, status, url in self.cache] + tmp = [(name, LinkStatus(url, name, plugin, int(size), status)) + for name, size, status, url in self.cache] data = parseNames(tmp) - result = {} - for k, v in data.iteritems(): - for url, status in v: - status.packagename = k - result[url] = status - - self.m.setInfoResults(self.rid, result) + self.m.setInfoResults(self.rid, data) self.cache = [] diff --git a/pyload/threads/ThreadManager.py b/pyload/threads/ThreadManager.py index 086e8ba51..a2e0aa400 100644 --- a/pyload/threads/ThreadManager.py +++ b/pyload/threads/ThreadManager.py @@ -106,7 +106,7 @@ class ThreadManager: def getInfoResult(self, rid): """returns result and clears it""" self.timestamp = time() + 5 * 60 - + # TODO: restrict user to his own results if rid in self.infoResults: data = self.infoResults[rid] self.infoResults[rid] = {} @@ -116,6 +116,7 @@ class ThreadManager: @lock def setInfoResults(self, rid, result): + self.core.evm.dispatchEvent("onlineResult:updated", rid, result) self.infoResults[rid].update(result) def getActiveDownloads(self, user=None): diff --git a/pyload/utils/packagetools.py b/pyload/utils/packagetools.py index 791a46d51..02dfa4739 100644 --- a/pyload/utils/packagetools.py +++ b/pyload/utils/packagetools.py @@ -5,6 +5,31 @@ import re from urlparse import urlparse +endings = "\\.(3gp|7zip|7z|abr|ac3|aiff|aifc|aif|ai|au|avi|bin|bz2|cbr|cbz|ccf|cue|cvd|chm|dta|deb|divx|djvu|dlc|dmg|doc|docx|dot|eps|exe|ff|flv|f4v|gsd|gif|gz|iwd|iso|ipsw|java|jar|jpg|jpeg|jdeatme|load|mws|mw|m4v|m4a|mkv|mp2|mp3|mp4|mov|movie|mpeg|mpe|mpg|msi|msu|msp|nfo|npk|oga|ogg|ogv|otrkey|pkg|png|pdf|pptx|ppt|pps|ppz|pot|psd|qt|rmvb|rm|rar|ram|ra|rev|rnd|r\\d+|rpm|run|rsdf|rtf|sh(!?tml)|srt|snd|sfv|swf|tar|tif|tiff|ts|txt|viv|vivo|vob|wav|wmv|xla|xls|xpi|zeno|zip|z\\d+|_[_a-z]{2}|\\d+$)" + +rarPats = [re.compile("(.*)(\\.|_|-)pa?r?t?\\.?[0-9]+.(rar|exe)$", re.I), + re.compile("(.*)(\\.|_|-)part\\.?[0]*[1].(rar|exe)$", re.I), + re.compile("(.*)\\.rar$", re.I), + re.compile("(.*)\\.r\\d+$", re.I), + re.compile("(.*)(\\.|_|-)\\d+$", re.I)] + +zipPats = [re.compile("(.*)\\.zip$", re.I), + re.compile("(.*)\\.z\\d+$", re.I), + re.compile("(?is).*\\.7z\\.[\\d]+$", re.I), + re.compile("(.*)\\.a.$", re.I)] + +ffsjPats = [re.compile("(.*)\\._((_[a-z])|([a-z]{2}))(\\.|$)"), + re.compile("(.*)(\\.|_|-)[\\d]+(" + endings + "$)", re.I)] + +iszPats = [re.compile("(.*)\\.isz$", re.I), + re.compile("(.*)\\.i\\d{2}$", re.I)] + +pat1 = re.compile("(\\.?CD\\d+)", re.I) +pat2 = re.compile("(\\.?part\\d+)", re.I) + +pat3 = re.compile("(.+)[\\.\\-_]+$") +pat4 = re.compile("(.+)\\.\\d+\\.xtm$") + def matchFirst(string, *args): """ matches against list of regexp and returns first match""" for patternlist in args: @@ -25,31 +50,6 @@ def parseNames(files): """ packs = {} - endings = "\\.(3gp|7zip|7z|abr|ac3|aiff|aifc|aif|ai|au|avi|bin|bz2|cbr|cbz|ccf|cue|cvd|chm|dta|deb|divx|djvu|dlc|dmg|doc|docx|dot|eps|exe|ff|flv|f4v|gsd|gif|gz|iwd|iso|ipsw|java|jar|jpg|jpeg|jdeatme|load|mws|mw|m4v|m4a|mkv|mp2|mp3|mp4|mov|movie|mpeg|mpe|mpg|msi|msu|msp|nfo|npk|oga|ogg|ogv|otrkey|pkg|png|pdf|pptx|ppt|pps|ppz|pot|psd|qt|rmvb|rm|rar|ram|ra|rev|rnd|r\\d+|rpm|run|rsdf|rtf|sh(!?tml)|srt|snd|sfv|swf|tar|tif|tiff|ts|txt|viv|vivo|vob|wav|wmv|xla|xls|xpi|zeno|zip|z\\d+|_[_a-z]{2}|\\d+$)" - - rarPats = [re.compile("(.*)(\\.|_|-)pa?r?t?\\.?[0-9]+.(rar|exe)$", re.I), - re.compile("(.*)(\\.|_|-)part\\.?[0]*[1].(rar|exe)$", re.I), - re.compile("(.*)\\.rar$", re.I), - re.compile("(.*)\\.r\\d+$", re.I), - re.compile("(.*)(\\.|_|-)\\d+$", re.I)] - - zipPats = [re.compile("(.*)\\.zip$", re.I), - re.compile("(.*)\\.z\\d+$", re.I), - re.compile("(?is).*\\.7z\\.[\\d]+$", re.I), - re.compile("(.*)\\.a.$", re.I)] - - ffsjPats = [re.compile("(.*)\\._((_[a-z])|([a-z]{2}))(\\.|$)"), - re.compile("(.*)(\\.|_|-)[\\d]+(" + endings + "$)", re.I)] - - iszPats = [re.compile("(.*)\\.isz$", re.I), - re.compile("(.*)\\.i\\d{2}$", re.I)] - - pat1 = re.compile("(\\.?CD\\d+)", re.I) - pat2 = re.compile("(\\.?part\\d+)", re.I) - - pat3 = re.compile("(.+)[\\.\\-_]+$") - pat4 = re.compile("(.+)\\.\\d+\\.xtm$") - for file, url in files: patternMatch = False @@ -127,7 +127,7 @@ def parseNames(files): # fallback : default name if not name: - name = "unknown" + name = _("Unnamed package") # build mapping if name in packs: diff --git a/pyload/web/app/scripts/collections/LinkList.js b/pyload/web/app/scripts/collections/LinkList.js new file mode 100644 index 000000000..170a2c039 --- /dev/null +++ b/pyload/web/app/scripts/collections/LinkList.js @@ -0,0 +1,14 @@ +define(['jquery', 'backbone', 'underscore', 'models/LinkStatus'], function($, Backbone, _, LinkStatus) { + 'use strict'; + + return Backbone.Collection.extend({ + + model: LinkStatus, + + comparator: function(link) { + return link.get('name'); + } + + }); + +}); \ No newline at end of file diff --git a/pyload/web/app/scripts/config.js b/pyload/web/app/scripts/config.js index ff4082ce4..51ea63285 100644 --- a/pyload/web/app/scripts/config.js +++ b/pyload/web/app/scripts/config.js @@ -59,8 +59,10 @@ require.config({ deps: ['underscore', 'jquery'], exports: 'Backbone' }, - - marionette: ['backbone'], + marionette: { + deps: ['backbone'], + exports: 'Backbone' + }, handlebars: { exports: 'Handlebars' }, diff --git a/pyload/web/app/scripts/helpers/linkStatus.js b/pyload/web/app/scripts/helpers/linkStatus.js new file mode 100644 index 000000000..2497785fb --- /dev/null +++ b/pyload/web/app/scripts/helpers/linkStatus.js @@ -0,0 +1,18 @@ +define('helpers/linkStatus', ['underscore', 'handlebars', 'utils/apitypes', 'utils/i18n'], + function(_, Handlebars, Api, i18n) { + 'use strict'; + function linkStatus(status) { + var s; + if (status === Api.DownloadStatus.Online) + s = '' + i18n.gettext('online') + ''; + else if (status === Api.DownloadState.Offline) + s = '' + i18n.gettext('offline') + ''; + else + s = '' + i18n.gettext('unknown') + ''; + + return new Handlebars.SafeString(s); + } + + Handlebars.registerHelper('linkStatus', linkStatus); + return linkStatus; + }); diff --git a/pyload/web/app/scripts/models/CollectorPackage.js b/pyload/web/app/scripts/models/CollectorPackage.js new file mode 100644 index 000000000..293342440 --- /dev/null +++ b/pyload/web/app/scripts/models/CollectorPackage.js @@ -0,0 +1,77 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes', 'collections/LinkList'], + function($, Backbone, _, App, Api, LinkList) { + 'use strict'; + return Backbone.Model.extend({ + + idAttribute: 'name', + defaults: { + name: 'Unnamed package', + new_name: null, + links: null + }, + + initialize: function() { + this.set('links', new LinkList()); + }, + + destroy: function() { + // Copied from backbones destroy method + var model = this; + model.trigger('destroy', model, model.collection); + }, + + // get the actual name + getName: function() { + var new_name = this.get('new_name'); + if (new_name) + return new_name; + + return this.get('name'); + + }, + // Add the package to pyload + add: function() { + var self = this; + var links = this.get('links').pluck('url'); + + $.ajax(App.apiRequest('addPackage', + {name: this.getName(), + links: links}, + {success: function() { + self.destroy(); + App.vent.trigger('package:added'); + }})); + + }, + + updateLinks: function(links) { + this.get('links').set(links, {remove: false}); + this.trigger('change'); + }, + + toJSON: function() { + var data = { + name: this.getName(), + links: this.get('links').toJSON() + }; + var links = this.get('links'); + data.length = links.length; + data.online = 0; + data.offline = 0; + data.unknown = 0; + + // Summary + links.each(function(link) { + if (link.get('status') === Api.DownloadStatus.Online) + data.online++; + else if (link.get('status') === Api.DownloadStatus.Offline) + data.offline++; + else + data.unknown++; + }); + + return data; + } + + }); + }); \ No newline at end of file diff --git a/pyload/web/app/scripts/models/LinkStatus.js b/pyload/web/app/scripts/models/LinkStatus.js new file mode 100644 index 000000000..2be1ce368 --- /dev/null +++ b/pyload/web/app/scripts/models/LinkStatus.js @@ -0,0 +1,22 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'], + function($, Backbone, _, App, Api) { + 'use strict'; + + return Backbone.Model.extend({ + + idAttribute: 'url', + + defaults: { + name: '', + plugin: '', + size: -1, + status: Api.DownloadStatus.Queued + }, + + destroy: function() { + var model = this; + model.trigger('destroy', model, model.collection); + } + + }); + }); \ No newline at end of file diff --git a/pyload/web/app/scripts/views/abstract/modalView.js b/pyload/web/app/scripts/views/abstract/modalView.js index b6d9f0eab..61016a9fb 100644 --- a/pyload/web/app/scripts/views/abstract/modalView.js +++ b/pyload/web/app/scripts/views/abstract/modalView.js @@ -117,10 +117,14 @@ define(['jquery', 'backbone', 'underscore', 'omniwindow'], function($, Backbone, }, destroy: function() { + this.onDestroy(); this.$el.remove(); this.dialog = null; this.remove(); - } + }, + onDestroy: function() { + + } }); }); \ No newline at end of file diff --git a/pyload/web/app/scripts/views/headerView.js b/pyload/web/app/scripts/views/headerView.js index 3fdfe32ba..60a47ad62 100644 --- a/pyload/web/app/scripts/views/headerView.js +++ b/pyload/web/app/scripts/views/headerView.js @@ -172,7 +172,7 @@ define(['jquery', 'underscore', 'backbone', 'app', 'models/ServerStatus', 'colle open_grabber: function() { var self = this; - _.requireOnce(['views/linkGrabberModal'], function(ModalView) { + _.requireOnce(['views/linkgrabber/modalView'], function(ModalView) { if (self.grabber === null) self.grabber = new ModalView(); diff --git a/pyload/web/app/scripts/views/linkGrabberModal.js b/pyload/web/app/scripts/views/linkGrabberModal.js deleted file mode 100644 index e6f59c134..000000000 --- a/pyload/web/app/scripts/views/linkGrabberModal.js +++ /dev/null @@ -1,49 +0,0 @@ -define(['jquery', 'underscore', 'app', 'views/abstract/modalView', 'hbs!tpl/dialogs/linkgrabber'], - function($, _, App, modalView, template) { - 'use strict'; - // Modal dialog for package adding - triggers package:added when package was added - return modalView.extend({ - - events: { - 'click .btn-success': 'addPackage', - 'keypress #inputPackageName': 'addOnEnter' - }, - - template: template, - - initialize: function() { - // Inherit parent events - this.events = _.extend({}, modalView.prototype.events, this.events); - }, - - addOnEnter: function(e) { - if (e.keyCode !== 13) return; - this.addPackage(e); - }, - - addPackage: function(e) { - var self = this; - var options = App.apiRequest('addPackage', - { - name: $('#inputPackageName').val(), - // TODO: better parsing / tokenization - links: $('#inputLinks').val().split('\n') - }, - { - success: function() { - App.vent.trigger('package:added'); - self.hide(); - } - }); - - $.ajax(options); - $('#inputPackageName').val(''); - $('#inputLinks').val(''); - }, - - onShow: function() { - this.$('#inputPackageName').focus(); - } - - }); - }); \ No newline at end of file diff --git a/pyload/web/app/scripts/views/linkgrabber/collectorView.js b/pyload/web/app/scripts/views/linkgrabber/collectorView.js new file mode 100644 index 000000000..d2b43f699 --- /dev/null +++ b/pyload/web/app/scripts/views/linkgrabber/collectorView.js @@ -0,0 +1,21 @@ +define(['jquery', 'underscore', 'backbone', 'app', './packageView'], + function($, _, Backbone, App, packageView) { + 'use strict'; + return Backbone.Marionette.CollectionView.extend({ + itemView: packageView, + updateData: function(result) { + var self = this; + _.each(result.data, function(links, name) { + var pack = self.collection.get(name); + if (!pack) { + pack = new self.collection.model({name: name}); + self.collection.add(pack); + } + + // TODO: remove links from all other packages than pack + pack.updateLinks(links); + }); + } + + }); + }); \ No newline at end of file diff --git a/pyload/web/app/scripts/views/linkgrabber/modalView.js b/pyload/web/app/scripts/views/linkgrabber/modalView.js new file mode 100644 index 000000000..6e4781ac2 --- /dev/null +++ b/pyload/web/app/scripts/views/linkgrabber/modalView.js @@ -0,0 +1,61 @@ +define(['jquery', 'underscore', 'backbone', 'app', 'models/CollectorPackage', 'views/abstract/modalView', './collectorView', 'hbs!tpl/linkgrabber/modal'], + function($, _, Backbone, App, CollectorPackage, modalView, CollectorView, template) { + 'use strict'; + // Modal dialog for package adding - triggers package:added when package was added + return modalView.extend({ + + events: { + 'keypress #inputLinks': 'addOnEnter' + }, + + template: template, + + // Holds the view that display the packages + collectorView: null, + + initialize: function() { + // Inherit parent events + this.events = _.extend({}, modalView.prototype.events, this.events); + }, + + addOnEnter: function(e) { + if (e.keyCode !== 13) return; + this.addPackage(e); + }, + + addPackage: function(e) { + var self = this; + // split, trim and remove empty links + var links = _.filter(_.map(this.$('#inputLinks').val().split('\n'), function(link) { + return $.trim(link); + }), function(link) { + return link.length > 0; + }); + + var options = App.apiRequest('checkLinks', + {links: links}, + { + success: function(data) { + self.collectorView.updateData(data); + } + }); + + $.ajax(options); + this.$('#inputLinks').val(''); + }, + + onRender: function() { + // anonymous collection + this.collectorView = new CollectorView({collection: new (Backbone.Collection.extend({ + model: CollectorPackage + }))()}); + this.collectorView.setElement(this.$('.prepared-packages')); + }, + + onDestroy: function() { + if (this.collectorView) + this.collectorView.close(); + } + + }); + }); \ No newline at end of file diff --git a/pyload/web/app/scripts/views/linkgrabber/packageView.js b/pyload/web/app/scripts/views/linkgrabber/packageView.js new file mode 100644 index 000000000..97d00f722 --- /dev/null +++ b/pyload/web/app/scripts/views/linkgrabber/packageView.js @@ -0,0 +1,58 @@ +define(['jquery', 'underscore', 'backbone', 'app', 'hbs!tpl/linkgrabber/package'], + function($, _, Backbone, App, template) { + 'use strict'; + return Backbone.Marionette.ItemView.extend({ + + tagName: 'div', + className: 'row-fluid package', + template: template, + + modelEvents: { + change: 'render' + }, + + ui: { + 'table': 'table' + }, + + events: { + 'click .btn-expand': 'expand', + 'click .btn-add': 'addPackage', + 'click .btn-delete': 'deletePackage', + 'click .btn-mini': 'deleteLink' + }, + + expanded: false, + + serializeData: function() { + var data = this.model.toJSON(); + data.expanded = this.expanded; + return data; + }, + + addPackage: function() { + this.model.add(); + }, + + deletePackage: function() { + this.model.destroy(); + }, + + deleteLink: function(e) { + var el = $(e.target); + var id = parseInt(el.data('index'), 10); + + var model = this.model.get('links').at(id); + if (model) + model.destroy(); + + this.render(); + }, + + expand: function() { + this.expanded ^= true; + this.ui.table.toggle(); + } + + }); + }); \ No newline at end of file diff --git a/pyload/web/app/styles/default/linkgrabber.less b/pyload/web/app/styles/default/linkgrabber.less new file mode 100644 index 000000000..364c9c420 --- /dev/null +++ b/pyload/web/app/styles/default/linkgrabber.less @@ -0,0 +1,18 @@ +.prepared-packages { + hr { + margin: 0; + } + + .package > .btn { + margin-bottom: 3px; + } + + img { + height: 22px; + } + + .table { + margin-bottom: 0; + } + +} \ No newline at end of file diff --git a/pyload/web/app/styles/default/main.less b/pyload/web/app/styles/default/main.less index 0bfa4fe2f..6bf21e80b 100644 --- a/pyload/web/app/styles/default/main.less +++ b/pyload/web/app/styles/default/main.less @@ -8,6 +8,7 @@ @import "pyload-common/styles/basic-layout"; @import "style"; +@import "linkgrabber"; @import "dashboard"; @import "settings"; @import "accounts"; diff --git a/pyload/web/app/templates/default/dialogs/linkgrabber.html b/pyload/web/app/templates/default/dialogs/linkgrabber.html deleted file mode 100755 index 8d90131ac..000000000 --- a/pyload/web/app/templates/default/dialogs/linkgrabber.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - \ No newline at end of file diff --git a/pyload/web/app/templates/default/linkgrabber/modal.html b/pyload/web/app/templates/default/linkgrabber/modal.html new file mode 100755 index 000000000..67e2e4fe3 --- /dev/null +++ b/pyload/web/app/templates/default/linkgrabber/modal.html @@ -0,0 +1,41 @@ + + + + + \ No newline at end of file diff --git a/pyload/web/app/templates/default/linkgrabber/package.html b/pyload/web/app/templates/default/linkgrabber/package.html new file mode 100644 index 000000000..54f6f4ad1 --- /dev/null +++ b/pyload/web/app/templates/default/linkgrabber/package.html @@ -0,0 +1,30 @@ +{{ name }} -
+ + + {{#each links}} + + + + + + + {{/each}} + +
{{ name }} {{ plugin }}{{ linkStatus status }}
+
+{{ ngettext "%d link" "%d links" length }}: +{{#if online}} + + {{ online }} {{_ "online" }} + +{{/if}} +{{#if offline}} + + {{ offline }} {{_ "offline" }} + +{{/if}} +{{#if unknown}} + + {{ unknown }} {{_ "unknown" }} + +{{/if}} diff --git a/pyload/web/app/templates/default/settings/actionbar.html b/pyload/web/app/templates/default/settings/actionbar.html index 25b10d463..647d0af99 100644 --- a/pyload/web/app/templates/default/settings/actionbar.html +++ b/pyload/web/app/templates/default/settings/actionbar.html @@ -1,5 +1,5 @@ -
-
- - - \ No newline at end of file + \ No newline at end of file diff --git a/pyload/web/app/templates/default/settings/layout.html b/pyload/web/app/templates/default/settings/layout.html index 39f1a2ec9..143d0caad 100644 --- a/pyload/web/app/templates/default/settings/layout.html +++ b/pyload/web/app/templates/default/settings/layout.html @@ -1,8 +1,8 @@ -
+
-
+

Please choose a config section

diff --git a/pyload/web/bower.json b/pyload/web/bower.json index 1d12378a0..b0176a891 100644 --- a/pyload/web/bower.json +++ b/pyload/web/bower.json @@ -13,7 +13,7 @@ "flot": "~0.8.1", "underscore": "~1.4.4", "backbone": "~1.0.0", - "backbone.marionette": "~1.0.3", + "backbone.marionette": "~1.1.0", "handlebars.js": "1.0.0-rc.3", "jed": "~0.5.4", "select2": "~3.4.0", -- cgit v1.2.3