diff options
20 files changed, 413 insertions, 105 deletions
diff --git a/pyload/Core.py b/pyload/Core.py index 5e083a14e..4f20ae33e 100644 --- a/pyload/Core.py +++ b/pyload/Core.py @@ -277,10 +277,8 @@ class Core(object): self.version = CURRENT_VERSION - # TODO: Re-enable when its working again - # TODO: Don't forget it - if False and not exists("pyload.conf") and not tests: - from Setup import Setup + if not exists("pyload.conf") and not tests: + from setup.Setup import Setup print "This is your first start, running configuration assistant now." self.config = ConfigParser() diff --git a/pyload/setup/Setup.py b/pyload/setup/Setup.py index 78afb7fcc..cea960885 100644 --- a/pyload/setup/Setup.py +++ b/pyload/setup/Setup.py @@ -30,17 +30,48 @@ from pyload.utils.fs import abspath, dirname, exists, join, makedirs from pyload.utils import get_console_encoding from pyload.web.ServerThread import WebServer +from system import get_system_info +from dependencies import deps class Setup(): """ pyLoads initial setup configuration assistant """ + @staticmethod + def check_system(): + return get_system_info() + + + @staticmethod + def check_deps(): + result = { + "core": [], + "opt": [] + } + + for d in deps: + avail, v = d.check() + check = { + "name": d.name, + "avail": avail, + "v": v + } + if d.optional: + result["opt"].append(check) + else: + result["core"].append(check) + + return result + + def __init__(self, path, config): self.path = path self.config = config self.stdin_encoding = get_console_encoding(sys.stdin.encoding) self.lang = None + self.db = None + # We will create a timestamp so that the setup will be completed in a specific interval self.timestamp = time() @@ -72,9 +103,13 @@ class Setup(): cli = self.ask("Use commandline for configuration instead?", self.no, bool=True) if cli: - self.start_cli() - else: - raw_input() + print "Not implemented yet!" + print "Use web configuration or config files" + + raw_input() + + return True + def start_cli(self): @@ -93,34 +128,8 @@ class Setup(): print _("When you are ready for system check, hit enter.") raw_input() - #self.get_page_next() - - - if len(avail) < 5: - print _("Features missing: ") - print - - if not self.check_module("Crypto"): - print _("no py-crypto available") - print _("You need this if you want to decrypt container files.") - print "" - - if not ssl: - print _("no SSL available") - print _("This is needed if you want to establish a secure connection to core or webinterface.") - print _("If you only want to access locally to pyLoad ssl is not useful.") - print "" - - if not captcha: - print _("no Captcha Recognition available") - print _("Only needed for some hosters and as freeuser.") - print "" - if not js: - print _("no JavaScript engine found") - print _("You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino") - - print _("You can abort the setup now and fix some dependencies if you want.") + # TODO: new system check + deps con = self.ask(_("Continue with setup?"), self.yes, bool=True) @@ -151,12 +160,11 @@ class Setup(): if ssl: self.conf_ssl() + print "" + print _("Do you want to configure webinterface?") + web = self.ask(_("Configure webinterface?"), self.yes, bool=True) if web: - print "" - print _("Do you want to configure webinterface?") - web = self.ask(_("Configure webinterface?"), self.yes, bool=True) - if web: - self.conf_web() + self.conf_web() print "" print _("Setup finished successfully.") @@ -182,18 +190,11 @@ class Setup(): db.shutdown() print "" - print _("External clients (GUI, CLI or other) need remote access to work over the network.") - print _("However, if you only want to use the webinterface you may disable it to save ram.") - self.config["remote"]["activated"] = self.ask(_("Enable remote access"), self.yes, bool=True) - - print "" langs = self.config.getMetaData("general", "language") self.config["general"]["language"] = self.ask(_("Language"), "en", langs.type.split(";")) self.config["general"]["download_folder"] = self.ask(_("Download folder"), "Downloads") self.config["download"]["max_downloads"] = self.ask(_("Max parallel downloads"), "3") - #print _("You should disable checksum proofing, if you have low hardware requirements.") - #self.config["general"]["checksum"] = self.ask(_("Proof checksum?"), "y", bool=True) reconnect = self.ask(_("Use Reconnect?"), self.no, bool=True) self.config["reconnect"]["activated"] = reconnect @@ -247,12 +248,8 @@ class Setup(): languages=[self.config["general"]["language"], "en"], fallback=True) translation.install(True) - from pyload.database import DatabaseBackend - - db = DatabaseBackend(None) - db.setup() + self.openDB() - noaction = True try: while True: print _("Select action") @@ -267,14 +264,12 @@ class Setup(): print "" username = self.ask(_("Username"), "User") password = self.ask("", "", password=True) - db.addUser(username, password) - noaction = False + self.db.addUser(username, password) elif action == "2": print "" print _("Users") print "-----" - users = db.getAllUserData() - noaction = False + users = self.db.getAllUserData() for user in users.itervalues(): print user.name print "-----" @@ -283,14 +278,31 @@ class Setup(): print "" username = self.ask(_("Username"), "") if username: - db.removeUserByName(username) - noaction = False + self.db.removeUserByName(username) elif action == "4": - db.syncSave() + self.db.syncSave() break finally: - if not noaction: - db.shutdown() + self.closeDB() + + def addUser(self, username, password): + self.openDB() + try: + self.db.addUser(username, password) + finally: + self.closeDB() + + def openDB(self): + from pyload.database import DatabaseBackend + + if self.db is None: + self.db = DatabaseBackend(None) + self.db.setup() + + def closeDB(self): + if self.db is not None: + self.db.syncSave() + self.db.shutdown() def conf_path(self, trans=False): if trans: diff --git a/pyload/setup/dependencies.py b/pyload/setup/dependencies.py index 53457de93..f7a0e4ae7 100644 --- a/pyload/setup/dependencies.py +++ b/pyload/setup/dependencies.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +import inspect + # Provide gettext marker _ = lambda x: x @@ -50,7 +52,7 @@ class Python(Dependency): def getVersion(self): import sys - ".".join(str(v) for v in sys.version_info[:3]) + return ".".join(str(v) for v in sys.version_info[:3]) class JSON(Dependency): @@ -58,8 +60,7 @@ class JSON(Dependency): optional = False def isStatisfied(self): - # TODO - return True + return find_module("json") or find_module("simplejson") class PyCurl(Dependency): @@ -67,8 +68,7 @@ class PyCurl(Dependency): optional = False def isStatisfied(self): - # TODO - return True + return find_module("pycurl") class Sqlite(Dependency): @@ -76,9 +76,8 @@ class Sqlite(Dependency): optional = False def isStatisfied(self): - # TODO - return True + return find_module("sqlite3") or find_module("pysqlite2") # TODO: ssl, crypto, image, tesseract, js -deps = [x for x in locals().itervalues() if issubclass(x, Dependency) and x is not Dependency]
\ No newline at end of file +deps = [Python, Sqlite, PyCurl, JSON]
\ No newline at end of file diff --git a/pyload/setup/system.py b/pyload/setup/system.py index 6e7039331..dab6d1d17 100644 --- a/pyload/setup/system.py +++ b/pyload/setup/system.py @@ -3,6 +3,8 @@ import sys import os +from new_collections import OrderedDict + # gettext decorator, translated only when needed _ = lambda x: x @@ -17,12 +19,12 @@ def get_system_info(): if info is None: import platform - info = { - _("Platform"): platform.platform(), - _("Version"): sys.version, - _("Path"): os.path.abspath(""), - _("Encoding"): sys.getdefaultencoding(), - _("FS-Encoding"): sys.getfilesystemencoding() - } + info = OrderedDict([ + (_("Platform"), platform.platform()), + (_("Version"), sys.version), + (_("Path"), os.path.abspath("")), + (_("Encoding"), sys.getdefaultencoding()), + (_("FS-Encoding"), sys.getfilesystemencoding()) + ]) return info
\ No newline at end of file diff --git a/pyload/web/app/scripts/models/Setup.js b/pyload/web/app/scripts/models/Setup.js index 82a2978db..424edf452 100644 --- a/pyload/web/app/scripts/models/Setup.js +++ b/pyload/web/app/scripts/models/Setup.js @@ -4,10 +4,30 @@ define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'], return Backbone.Model.extend({ + url: App.apiUrl('setup'), defaults: { lang: 'en', + system: null, + deps: null, user: null, password: null + }, + + fetch: function(options) { + options || (options = {}); + options.url = App.apiUrl('setup'); + return Backbone.Model.prototype.fetch.call(this, options); + }, + + // will get a 409 on success + submit: function(options) { + options || (options = {}); + options.url = App.apiUrl('setup_done'); + options.data = { + user: this.get('user'), + password: this.get('password') + }; + return Backbone.Model.prototype.fetch.call(this, options); } }); diff --git a/pyload/web/app/scripts/views/setup/finishedView.js b/pyload/web/app/scripts/views/setup/finishedView.js new file mode 100644 index 000000000..9f0f8db19 --- /dev/null +++ b/pyload/web/app/scripts/views/setup/finishedView.js @@ -0,0 +1,25 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'hbs!tpl/setup/finished'], + function($, Backbone, _, App, template) { + 'use strict'; + + return Backbone.Marionette.ItemView.extend({ + + name: 'Finished', + template: template, + + events: { + 'click .btn-blue': 'confirm' + }, + + ui: { + }, + + onRender: function() { + }, + + confirm: function() { + this.model.submit(); + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/setup/setupView.js b/pyload/web/app/scripts/views/setup/setupView.js index 7636a0bc2..8ab6fba51 100644 --- a/pyload/web/app/scripts/views/setup/setupView.js +++ b/pyload/web/app/scripts/views/setup/setupView.js @@ -1,6 +1,6 @@ -define(['jquery', 'backbone', 'underscore', 'app', 'models/Setup', 'hbs!tpl/setup/layout', 'hbs!tpl/setup/actionbar', - './welcomeView', './systemView'], - function($, Backbone, _, App, Setup, template, templateBar, welcomeView, systemView) { +define(['jquery', 'backbone', 'underscore', 'app', 'models/Setup', 'hbs!tpl/setup/layout', 'hbs!tpl/setup/actionbar', 'hbs!tpl/setup/error', + './welcomeView', './systemView', './userView', './finishedView'], + function($, Backbone, _, App, Setup, template, templateBar, templateError, welcomeView, systemView, userView, finishedView) { 'use strict'; return Backbone.Marionette.ItemView.extend({ @@ -15,11 +15,14 @@ define(['jquery', 'backbone', 'underscore', 'app', 'models/Setup', 'hbs!tpl/setu pages: [ welcomeView, - systemView + systemView, + userView, + finishedView ], page: 0, view: null, + error: null, initialize: function() { var self = this; @@ -52,37 +55,66 @@ define(['jquery', 'backbone', 'underscore', 'app', 'models/Setup', 'hbs!tpl/setu }); this.listenTo(this.model, 'page:next', function() { - self.openPage(self.page++); + self.openPage(self.page + 1); }); this.listenTo(this.model, 'page:prev', function() { - self.openPage(self.page--); + self.openPage(self.page - 1); }); + + this.listenTo(this.model, 'error', this.onError); + this.model.fetch(); }, openPage: function(page) { console.log('Change page', page); // check if number is reasonable - if (!_.isNumber(page) || !_.isFinite(page)) + if (!_.isNumber(page) || !_.isFinite(page) || page < 0 || page >= this.pages.length) return; if (page === this.page) return; + // Render error directly + if (this.error) { + this.onRender(); + return; + } + this.page = page; - this.onRender(); + + var self = this; + this.ui.page.fadeOut({complete: function() { + self.onRender(); + }}); this.model.trigger('page:changed', page); }, + onError: function(model, xhr) { + console.log('Setup error', xhr); + this.error = xhr; + this.onRender(); + }, + onRender: function() { + // close old opened view if (this.view) this.view.close(); - // TODO: animation + // Render error if occurred + if (this.error) { + this.ui.page.html(templateError(this.error)); + return; + } + this.view = new this.pages[this.page]({model: this.model}); this.ui.page.empty(); - this.ui.page.append(this.view.render().$el); + + var el = this.view.render().el; + this.ui.page.append(el); + + this.ui.page.fadeIn(); } }); diff --git a/pyload/web/app/scripts/views/setup/systemView.js b/pyload/web/app/scripts/views/setup/systemView.js index 11e50213d..b4c0f7e12 100644 --- a/pyload/web/app/scripts/views/setup/systemView.js +++ b/pyload/web/app/scripts/views/setup/systemView.js @@ -8,12 +8,17 @@ define(['jquery', 'backbone', 'underscore', 'app', 'hbs!tpl/setup/system'], template: template, events: { + 'click .btn-blue': 'nextPage' }, ui: { }, onRender: function() { + }, + + nextPage: function() { + this.model.trigger('page:next'); } }); diff --git a/pyload/web/app/scripts/views/setup/userView.js b/pyload/web/app/scripts/views/setup/userView.js new file mode 100644 index 000000000..95eaa0dc2 --- /dev/null +++ b/pyload/web/app/scripts/views/setup/userView.js @@ -0,0 +1,39 @@ +define(['jquery', 'backbone', 'underscore', 'app', 'hbs!tpl/setup/user'], + function($, Backbone, _, App, template) { + 'use strict'; + + return Backbone.Marionette.ItemView.extend({ + + name: 'User', + template: template, + + events: { + 'click .btn-blue': 'submit' + }, + + ui: { + username: '#username', + password: '#password', + password2: '#password2' + }, + + onRender: function() { + }, + + submit: function() { + var pw = this.ui.password.val(); + var pw2 = this.ui.password2.val(); + + // TODO more checks and error messages + if (pw !== pw2) { + return; + } + + this.model.set('user', this.ui.username.val()); + this.model.set('password', pw); + + this.model.trigger('page:next'); + } + + }); + });
\ No newline at end of file diff --git a/pyload/web/app/scripts/views/setup/welcomeView.js b/pyload/web/app/scripts/views/setup/welcomeView.js index 4affc9075..a964e0d42 100644 --- a/pyload/web/app/scripts/views/setup/welcomeView.js +++ b/pyload/web/app/scripts/views/setup/welcomeView.js @@ -8,12 +8,17 @@ define(['jquery', 'backbone', 'underscore', 'app', 'hbs!tpl/setup/welcome'], template: template, events: { + 'click .btn-blue': 'nextPage' }, ui: { }, onRender: function() { + }, + + nextPage: function() { + this.model.trigger('page:next'); } }); diff --git a/pyload/web/app/styles/default/main.less b/pyload/web/app/styles/default/main.less index 6bf21e80b..c978a3b90 100644 --- a/pyload/web/app/styles/default/main.less +++ b/pyload/web/app/styles/default/main.less @@ -13,6 +13,7 @@ @import "settings"; @import "accounts"; @import "admin"; +@import "setup"; @ResourcePath: "../.."; @DefaultFont: 'Abel', sans-serif; diff --git a/pyload/web/app/styles/default/setup.less b/pyload/web/app/styles/default/setup.less new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/pyload/web/app/styles/default/setup.less diff --git a/pyload/web/app/templates/default/setup/error.html b/pyload/web/app/templates/default/setup/error.html new file mode 100644 index 000000000..37ce51283 --- /dev/null +++ b/pyload/web/app/templates/default/setup/error.html @@ -0,0 +1,14 @@ +{{#ifEq status 410}} + <h2 class="text-warning">{{ _ "Setup timed out" }}</h2> + <p>{{ _ "Setup was closed due to inactivity. Please restart it to continue configuration." }}</p> +{{else}} +{{#ifEq status 409}} + <h2 class="text-success">{{ _ "Setup finished" }}</h2> + <p>{{ _ "Setup was successful. You can restart pyLoad now." }}</p> +{{else}} + <h2 class="text-error"> + {{ _ "Setup failed" }} + </h2> + <p>{{ _ "Try to restart it or open a bug report." }}</p> +{{/ifEq}} +{{/ifEq}}
\ No newline at end of file diff --git a/pyload/web/app/templates/default/setup/finished.html b/pyload/web/app/templates/default/setup/finished.html new file mode 100644 index 000000000..abe93b352 --- /dev/null +++ b/pyload/web/app/templates/default/setup/finished.html @@ -0,0 +1,23 @@ +{{#if user}} + +<h2> + {{ _ "Nearly Done" }} +</h2> + +<p> + {{ _ "Please check your settings." }} +</p> + +<p> + <strong>Username:</strong> {{user}} +</p> + +<button class="btn btn-large btn-blue"> + {{ _ "Confirm" }} +</button> + +{{else}} + +<h2>{{ _ "Pleae add a user first." }}</h2> + +{{/if}}
\ No newline at end of file diff --git a/pyload/web/app/templates/default/setup/layout.html b/pyload/web/app/templates/default/setup/layout.html index 7b75e53b1..2e986173a 100644 --- a/pyload/web/app/templates/default/setup/layout.html +++ b/pyload/web/app/templates/default/setup/layout.html @@ -3,5 +3,8 @@ {{ _ "Setup" }} </h1> </div> -<div class="span8 setup-page"> +<div class="span8"> + <div class="hero-unit setup-page"> + + </div> </div>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/setup/system.html b/pyload/web/app/templates/default/setup/system.html index 84a217b19..0c5023669 100644 --- a/pyload/web/app/templates/default/setup/system.html +++ b/pyload/web/app/templates/default/setup/system.html @@ -1,5 +1,56 @@ -<h1>{{ _ "System" }} </h1> +<h3>{{ _ "System" }} </h3> -<h2>{{_ "Dependencies" }}</h2> +<dl class="dl-horizontal"> + {{#each system}} + <dt>{{ @key }}</dt> + <dd>{{ this }}</dd> + {{/each}} +</dl> -<h2>{{ _ "Optional" }}</h2>
\ No newline at end of file +<h3>{{_ "Dependencies" }}</h3> +<dl class="dl-horizontal"> + {{#each deps.core}} + <dt>{{ name }}</dt> + <dd> + {{#if avail}} + <span class="text-success"> + <i class="icon-ok"></i> + {{#if v}} + ({{v}}) + {{/if}} + </span> + {{else}} + <span class="text-error"> + <i class="icon-remove"></i> + </span> + {{/if}} + </dd> + {{/each}} +</dl> + + +<h4>{{ _ "Optional" }}</h4> +<dl class="dl-horizontal"> + {{#each deps.opt}} + <dt>{{ name }}</dt> + <dd> + {{#if avail}} + <span class="text-success"> + {{ _ "available" }} + {{#if v}} + ({{v}}) + {{/if}} + </span> + {{else}} + <span class="text-error"> + {{ _ "not available" }} + </span> + {{/if}} + </dd> + {{/each}} +</dl> + + +<button class="btn btn-blue"> + {{ _ "Next" }} +</button>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/setup/user.html b/pyload/web/app/templates/default/setup/user.html new file mode 100644 index 000000000..fe3f2de71 --- /dev/null +++ b/pyload/web/app/templates/default/setup/user.html @@ -0,0 +1,34 @@ +<form class="form-horizontal"> + <div class="control-group"> + <label class="control-label"> + Username + </label> + + <div class="controls"> + <input type="text" id="username" placeholder="User"> + </div> + </div> + <div class="control-group"> + <label class="control-label"> + Password + </label> + + <div class="controls"> + <input type="password" id="password"> + </div> + </div> + <div class="control-group"> + <label class="control-label"> + Password (again) + </label> + + <div class="controls"> + <input type="password" id="password2"> + </div> + </div> + <div class="control-group"> + <div class="controls"> + <a class="btn btn-blue">Submit</a> + </div> + </div> +</form>
\ No newline at end of file diff --git a/pyload/web/app/templates/default/setup/welcome.html b/pyload/web/app/templates/default/setup/welcome.html index f5c5af4d7..5a4f74d9f 100644 --- a/pyload/web/app/templates/default/setup/welcome.html +++ b/pyload/web/app/templates/default/setup/welcome.html @@ -1,16 +1,14 @@ -<div class="hero-unit"> - <h1>{{ _ "Welcome!" }}</h1> +<h1>{{ _ "Welcome!" }}</h1> - <p>{{ _ "pyLoad is running and ready for configuration." }}</p> +<p>{{ _ "pyLoad is running and ready for configuration." }}</p> - <p> - {{ _ "Select your language:" }} - <select> - <option>en</option> - </select> - </p> +<p> + {{ _ "Select your language:" }} + <select> + <option>en</option> + </select> +</p> - <button class="btn btn-large btn-blue"> - {{ _ "Start configuration" }} - </button> -</div>
\ No newline at end of file +<button class="btn btn-large btn-blue"> + {{ _ "Start configuration" }} +</button>
\ No newline at end of file diff --git a/pyload/web/pyload_app.py b/pyload/web/pyload_app.py index 1ec7cf4c9..1c89e2ada 100644 --- a/pyload/web/pyload_app.py +++ b/pyload/web/pyload_app.py @@ -70,7 +70,11 @@ def index(): # set variable depending on setup mode setup = 'false' if SETUP is None else 'true' ws = PYLOAD.getWSAddress() if PYLOAD else False - web = PYLOAD.getConfigValue('webinterface', 'port') if PYLOAD else False + web = None + if PYLOAD: + web = PYLOAD.getConfigValue('webinterface', 'port') + elif SETUP: + web = SETUP.config['webinterface']['port'] # Render variables into the html page if resp.status_code == 200: diff --git a/pyload/web/setup_app.py b/pyload/web/setup_app.py index 5163f9cc6..680c5ae89 100644 --- a/pyload/web/setup_app.py +++ b/pyload/web/setup_app.py @@ -3,26 +3,69 @@ from time import time +from pyload.utils import json_dumps + from bottle import route, request, response, HTTPError, redirect from webinterface import PROJECT_DIR, SETUP from utils import add_json_header +# returns http error +def error(code, msg): + return HTTPError(code, json_dumps(msg), **dict(response.headers)) + + def setup_required(func): def _view(*args, **kwargs): + global timestamp + # setup needs to be running if SETUP is None: - redirect("/nopermission") + return error(404, "Not Found") + + # setup finished + if timestamp == 0: + return error(409, "Done") + + # setup timed out due to inactivity + if timestamp + TIMEOUT * 60 < time(): + return error(410, "Timeout") + + timestamp = time() return func(*args, **kwargs) + return _view # setup will close after inactivity TIMEOUT = 15 timestamp = time() + @route("/setup") @setup_required def setup(): - pass # TODO + add_json_header(response) + + return json_dumps({ + "system": SETUP.check_system(), + "deps": SETUP.check_deps() + }) + + +@route("/setup_done") +@setup_required +def setup_done(): + global timestamp + add_json_header(response) + + SETUP.addUser( + request.params['user'], + request.params['password'] + ) + + # mark setup as finished + timestamp = 0 + + return error(409, "Done") |