From c7cd72787fcbae9431042dcd5061df4a5413cb52 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 2 Feb 2013 11:49:12 +0100 Subject: [PATCH 01/10] Ignore extracted folder. fix #1369 --- couchpotato/core/plugins/scanner/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/plugins/scanner/main.py b/couchpotato/core/plugins/scanner/main.py index 684f682..b822bc0 100644 --- a/couchpotato/core/plugins/scanner/main.py +++ b/couchpotato/core/plugins/scanner/main.py @@ -23,7 +23,7 @@ class Scanner(Plugin): 'media': 314572800, # 300MB 'trailer': 1048576, # 1MB } - ignored_in_path = ['extracting', '_unpack', '_failed_', '_unknown_', '_exists_', '_failed_remove_', '_failed_rename_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo'] #unpacking, smb-crap, hidden files + ignored_in_path = [os.path.sep + 'extracted' + os.path.sep, 'extracting', '_unpack', '_failed_', '_unknown_', '_exists_', '_failed_remove_', '_failed_rename_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo'] #unpacking, smb-crap, hidden files ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts', 'bdmv', 'certificate'] extensions = { 'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts', 'm4v'], From 629bead919dc9e4138ac0ba2e2833efcebdc841f Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 2 Feb 2013 12:02:54 +0100 Subject: [PATCH 02/10] Raise current exception --- couchpotato/core/plugins/renamer/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index 91dfb33..62358dd 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -468,7 +468,7 @@ class Renamer(Plugin): except: log.error('Couldn\'t move file "%s" to "%s": %s', (old, dest, traceback.format_exc())) - raise Exception + raise return True From 52371b77053f055ff7a776c6a37d10c8af5d8efe Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 2 Feb 2013 23:16:02 +0100 Subject: [PATCH 03/10] Daemonize cleanup --- couchpotato/core/_base/_core/main.py | 2 +- couchpotato/environment.py | 2 +- libs/daemon.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/couchpotato/core/_base/_core/main.py b/couchpotato/core/_base/_core/main.py index 0423e66..c91140f 100644 --- a/couchpotato/core/_base/_core/main.py +++ b/couchpotato/core/_base/_core/main.py @@ -179,7 +179,7 @@ class Core(Plugin): if Env.get('daemonized'): return def signal_handler(signal, frame): - fireEvent('app.shutdown') + fireEvent('app.shutdown', single = True) signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) diff --git a/couchpotato/environment.py b/couchpotato/environment.py index d8c03c7..bd637ad 100644 --- a/couchpotato/environment.py +++ b/couchpotato/environment.py @@ -20,7 +20,7 @@ class Env(object): _options = None _args = None _quiet = False - _deamonize = False + _daemonized = False _desktop = None _session = None diff --git a/libs/daemon.py b/libs/daemon.py index 0e3d0d6..805cfa2 100644 --- a/libs/daemon.py +++ b/libs/daemon.py @@ -92,6 +92,7 @@ class Daemon(): """ Stop the daemon """ + # Get the pid from the pidfile try: pf = file(self.pidfile, 'r') @@ -115,7 +116,6 @@ class Daemon(): if err.find("No such process") > 0: self.delpid() else: - print str(err) sys.exit(1) def restart(self): From 4b54113f08f7f4f644dd4193545d3f6fb9500c79 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 3 Feb 2013 18:20:11 +0100 Subject: [PATCH 04/10] Use CP api for movie check --- couchpotato/core/plugins/movie/main.py | 5 ++--- couchpotato/core/providers/movie/couchpotatoapi/main.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py index 8b0761c..0e942f0 100644 --- a/couchpotato/core/plugins/movie/main.py +++ b/couchpotato/core/plugins/movie/main.py @@ -292,9 +292,8 @@ class MoviePlugin(Plugin): return False else: try: - url = 'http://thetvdb.com/api/GetSeriesByRemoteID.php?imdbid=%s' % params.get('identifier') - tvdb = self.getCache('thetvdb.%s' % params.get('identifier'), url = url, show_error = False) - if tvdb and 'series' in tvdb.lower(): + is_movie = fireEvent('movie.is_movie', identifier = params.get('identifier'), single = True) + if not is_movie: msg = 'Can\'t add movie, seems to be a TV show.' log.error(msg) fireEvent('notify.frontend', type = 'movie.is_tvshow', message = msg) diff --git a/couchpotato/core/providers/movie/couchpotatoapi/main.py b/couchpotato/core/providers/movie/couchpotatoapi/main.py index 88883d7..391af03 100644 --- a/couchpotato/core/providers/movie/couchpotatoapi/main.py +++ b/couchpotato/core/providers/movie/couchpotatoapi/main.py @@ -17,6 +17,7 @@ class CouchPotatoApi(MovieProvider): urls = { 'search': 'https://couchpota.to/api/search/%s/', 'info': 'https://couchpota.to/api/info/%s/', + 'is_movie': 'https://couchpota.to/api/ismovie/%s/', 'eta': 'https://couchpota.to/api/eta/%s/', 'suggest': 'https://couchpota.to/api/suggest/%s/%s/', } @@ -29,6 +30,7 @@ class CouchPotatoApi(MovieProvider): addEvent('movie.info', self.getInfo, priority = 1) addEvent('movie.search', self.search, priority = 1) addEvent('movie.release_date', self.getReleaseDate) + addEvent('movie.is_movie', self.isMovie) def search(self, q, limit = 12): @@ -44,6 +46,17 @@ class CouchPotatoApi(MovieProvider): return [] + def isMovie(self, identifier = None): + + if not identifier: + return + + data = self.getJsonData(self.urls['is_movie'] % identifier, headers = self.getRequestHeaders()) + if data: + return data.get('is_movie', True) + + return True + def getInfo(self, identifier = None): if not identifier: From a56bbf0b3b719b5081198c7d25254c7e5b3623f2 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 3 Feb 2013 21:50:29 +0100 Subject: [PATCH 05/10] CP API cleanup --- .../core/providers/movie/couchpotatoapi/main.py | 48 +++++----------------- 1 file changed, 10 insertions(+), 38 deletions(-) diff --git a/couchpotato/core/providers/movie/couchpotatoapi/main.py b/couchpotato/core/providers/movie/couchpotatoapi/main.py index 391af03..8758684 100644 --- a/couchpotato/core/providers/movie/couchpotatoapi/main.py +++ b/couchpotato/core/providers/movie/couchpotatoapi/main.py @@ -5,9 +5,7 @@ from couchpotato.core.helpers.request import jsonified, getParams from couchpotato.core.logger import CPLog from couchpotato.core.providers.movie.base import MovieProvider from couchpotato.core.settings.model import Movie -from flask.helpers import json import time -import traceback log = CPLog(__name__) @@ -33,18 +31,7 @@ class CouchPotatoApi(MovieProvider): addEvent('movie.is_movie', self.isMovie) def search(self, q, limit = 12): - - cache_key = 'cpapi.cache.%s' % q - cached = self.getCache(cache_key, self.urls['search'] % tryUrlencode(q), headers = self.getRequestHeaders()) - - if cached: - try: - movies = json.loads(cached) - return movies - except: - log.error('Failed parsing search results: %s', traceback.format_exc()) - - return [] + return self.getJsonData(self.urls['search'] % tryUrlencode(q), headers = self.getRequestHeaders()) def isMovie(self, identifier = None): @@ -62,38 +49,23 @@ class CouchPotatoApi(MovieProvider): if not identifier: return - cache_key = 'cpapi.cache.info.%s' % identifier - cached = self.getCache(cache_key, self.urls['info'] % identifier, headers = self.getRequestHeaders()) - - if cached: - try: - movie = json.loads(cached) - return movie - except: - log.error('Failed parsing info results: %s', traceback.format_exc()) + result = self.getJsonData(self.urls['info'] % identifier, headers = self.getRequestHeaders()) + if result: return result return {} def getReleaseDate(self, identifier = None): - if identifier is None: return {} - try: - data = self.urlopen(self.urls['eta'] % identifier, headers = self.getRequestHeaders()) - dates = json.loads(data) - log.debug('Found ETA for %s: %s', (identifier, dates)) - return dates - except Exception, e: - log.error('Error getting ETA for %s: %s', (identifier, e)) - return {} + dates = self.getJsonData(self.urls['eta'] % identifier, headers = self.getRequestHeaders()) + log.debug('Found ETA for %s: %s', (identifier, dates)) + + return dates def suggest(self, movies = [], ignore = []): - try: - data = self.urlopen(self.urls['suggest'] % (','.join(movies), ','.join(ignore))) - suggestions = json.loads(data) - log.info('Found Suggestions for %s', (suggestions)) - except Exception, e: - log.error('Error getting suggestions for %s: %s', (movies, e)) + + suggestions = self.getJsonData(self.urls['suggest'] % (','.join(movies), ','.join(ignore))) + log.info('Found Suggestions for %s', (suggestions)) return suggestions From 856b495995be279888eb7844d78e1931a85cfc09 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 4 Feb 2013 21:48:02 +0100 Subject: [PATCH 06/10] Minifier --- couchpotato/core/_base/clientscript/main.py | 128 ++++- couchpotato/core/plugins/base.py | 2 +- couchpotato/core/plugins/file/main.py | 2 +- couchpotato/static/scripts/library/prefix_free.js | 22 +- couchpotato/static/style/page/settings.css | 651 ---------------------- couchpotato/static/style/settings.css | 651 ++++++++++++++++++++++ couchpotato/templates/_desktop.html | 43 +- libs/minify/__init__.py | 0 libs/minify/cssmin.py | 223 ++++++++ libs/minify/jsmin.py | 218 ++++++++ 10 files changed, 1237 insertions(+), 703 deletions(-) delete mode 100644 couchpotato/static/style/page/settings.css create mode 100644 couchpotato/static/style/settings.css create mode 100644 libs/minify/__init__.py create mode 100644 libs/minify/cssmin.py create mode 100644 libs/minify/jsmin.py diff --git a/couchpotato/core/_base/clientscript/main.py b/couchpotato/core/_base/clientscript/main.py index bb380be..e8624b0 100644 --- a/couchpotato/core/_base/clientscript/main.py +++ b/couchpotato/core/_base/clientscript/main.py @@ -1,15 +1,58 @@ from couchpotato.core.event import addEvent +from couchpotato.core.helpers.variable import tryInt from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin +from couchpotato.environment import Env +from minify.cssmin import cssmin +from minify.jsmin import jsmin +import os log = CPLog(__name__) class ClientScript(Plugin): - urls = { - 'style': {}, - 'script': {}, + core_static = { + 'style': [ + 'style/main.css', + 'style/uniform.generic.css', + 'style/uniform.css', + 'style/settings.css', + ], + 'script': [ + 'scripts/library/mootools.js', + 'scripts/library/mootools_more.js', + 'scripts/library/prefix_free.js', + 'scripts/library/uniform.js', + 'scripts/library/form_replacement/form_check.js', + 'scripts/library/form_replacement/form_radio.js', + 'scripts/library/form_replacement/form_dropdown.js', + 'scripts/library/form_replacement/form_selectoption.js', + 'scripts/library/question.js', + 'scripts/library/scrollspy.js', + 'scripts/library/spin.js', + 'scripts/couchpotato.js', + 'scripts/api.js', + 'scripts/library/history.js', + 'scripts/page.js', + 'scripts/block.js', + 'scripts/block/navigation.js', + 'scripts/block/footer.js', + 'scripts/block/menu.js', + 'scripts/page/wanted.js', + 'scripts/page/settings.js', + 'scripts/page/about.js', + 'scripts/page/manage.js', + ], + } + + + urls = {'style': {}, 'script': {}, } + minified = {'style': {}, 'script': {}, } + paths = {'style': {}, 'script': {}, } + comment = { + 'style': '/*** %s:%d ***/\n', + 'script': '// %s:%d\n' } html = { @@ -24,6 +67,66 @@ class ClientScript(Plugin): addEvent('clientscript.get_styles', self.getStyles) addEvent('clientscript.get_scripts', self.getScripts) + addEvent('app.load', self.minify) + + self.addCore() + + def addCore(self): + + for static_type in self.core_static: + for rel_path in self.core_static.get(static_type): + file_path = os.path.join(Env.get('app_dir'), 'couchpotato', 'static', rel_path) + core_url = 'api/%s/static/%s?%s' % (Env.setting('api_key'), rel_path, tryInt(os.path.getmtime(file_path))) + + if static_type == 'script': + self.registerScript(core_url, file_path, position = 'front') + else: + self.registerStyle(core_url, file_path, position = 'front') + + + def minify(self): + + for file_type in ['style', 'script']: + ext = 'js' if file_type is 'script' else 'css' + positions = self.paths.get(file_type, {}) + for position in positions: + files = positions.get(position) + self._minify(file_type, files, position, position + '.' + ext) + + def _minify(self, file_type, files, position, out): + + cache = Env.get('cache_dir') + out_name = 'minified_' + out + out = os.path.join(cache, out_name) + + raw = [] + for file_path in files: + f = open(file_path, 'r').read() + + if file_type == 'script': + data = jsmin(f) + else: + data = cssmin(f) + data = data.replace('../images/', '../static/images/') + + raw.append({'file': file_path, 'date': int(os.path.getmtime(file_path)), 'data': data}) + + # Combine all files together with some comments + data = '' + for r in raw: + data += self.comment.get(file_type) % (r.get('file'), r.get('date')) + data += r.get('data') + '\n\n' + + self.createFile(out, data.strip()) + + if not self.minified.get(file_type): + self.minified[file_type] = {} + if not self.minified[file_type].get(position): + self.minified[file_type][position] = [] + + minified_url = 'api/%s/file.cache/%s?%s' % (Env.setting('api_key'), out_name, tryInt(os.path.getmtime(out))) + self.minified[file_type][position].append(minified_url) + def getStyles(self, *args, **kwargs): return self.get('style', *args, **kwargs) @@ -35,22 +138,27 @@ class ClientScript(Plugin): data = '' if as_html else [] try: + if Env.get('dev'): + return self.minified[type][location] + return self.urls[type][location] except Exception, e: log.error(e) return data - def registerStyle(self, path, position = 'head'): - self.register(path, 'style', position) + def registerStyle(self, api_path, file_path, position = 'head'): + self.register(api_path, file_path, 'style', position) - def registerScript(self, path, position = 'head'): - self.register(path, 'script', position) + def registerScript(self, api_path, file_path, position = 'head'): + self.register(api_path, file_path, 'script', position) - def register(self, filepath, type, location): + def register(self, api_path, file_path, type, location): if not self.urls[type].get(location): self.urls[type][location] = [] + self.urls[type][location].append(api_path) - filePath = filepath - self.urls[type][location].append(filePath) + if not self.paths[type].get(location): + self.paths[type][location] = [] + self.paths[type][location].append(file_path) diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py index 73a2c30..edecaa9 100644 --- a/couchpotato/core/plugins/base.py +++ b/couchpotato/core/plugins/base.py @@ -64,7 +64,7 @@ class Plugin(object): for f in glob.glob(os.path.join(self.plugin_path, 'static', '*')): ext = getExt(f) if ext in ['js', 'css']: - fireEvent('register_%s' % ('script' if ext in 'js' else 'style'), path + os.path.basename(f)) + fireEvent('register_%s' % ('script' if ext in 'js' else 'style'), path + os.path.basename(f), f) def showStatic(self, filename): d = os.path.join(self.plugin_path, 'static') diff --git a/couchpotato/core/plugins/file/main.py b/couchpotato/core/plugins/file/main.py index a9eab33..0dc0178 100644 --- a/couchpotato/core/plugins/file/main.py +++ b/couchpotato/core/plugins/file/main.py @@ -71,7 +71,7 @@ class FileManager(Plugin): db = get_session() for root, dirs, walk_files in os.walk(Env.get('cache_dir')): for filename in walk_files: - if root == python_cache: continue + if root == python_cache or 'minified' in filename: continue file_path = os.path.join(root, filename) f = db.query(File).filter(File.path == toUnicode(file_path)).first() if not f: diff --git a/couchpotato/static/scripts/library/prefix_free.js b/couchpotato/static/scripts/library/prefix_free.js index 8dd99e2..b6d9812 100644 --- a/couchpotato/static/scripts/library/prefix_free.js +++ b/couchpotato/static/scripts/library/prefix_free.js @@ -24,6 +24,9 @@ var self = window.StyleFix = { var url = link.href || link.getAttribute('data-href'), base = url.replace(/[^\/]+$/, ''), + base_scheme = (/^[a-z]{3,10}:/.exec(base) || [''])[0], + base_domain = (/^[a-z]{3,10}:\/\/[^\/]+/.exec(base) || [''])[0], + base_query = /^([^?]*)\??/.exec(url)[1], parent = link.parentNode, xhr = new XMLHttpRequest(), process; @@ -43,12 +46,23 @@ var self = window.StyleFix = { // Convert relative URLs to absolute, if needed if(base) { css = css.replace(/url\(\s*?((?:"|')?)(.+?)\1\s*?\)/gi, function($0, quote, url) { - if(!/^([a-z]{3,10}:|\/|#)/i.test(url)) { // If url not absolute & not a hash + if(/^([a-z]{3,10}:|#)/i.test(url)) { // Absolute & or hash-relative + return $0; + } + else if(/^\/\//.test(url)) { // Scheme-relative // May contain sequences like /../ and /./ but those DO work + return 'url("' + base_scheme + url + '")'; + } + else if(/^\//.test(url)) { // Domain-relative + return 'url("' + base_domain + url + '")'; + } + else if(/^\?/.test(url)) { // Query-relative + return 'url("' + base_query + url + '")'; + } + else { + // Path-relative return 'url("' + base + url + '")'; } - - return $0; }); // behavior URLs shoudn’t be converted (Issue #19) @@ -470,4 +484,4 @@ root.className += ' ' + self.prefix; StyleFix.register(self.prefixCSS); -})(document.documentElement); +})(document.documentElement); \ No newline at end of file diff --git a/couchpotato/static/style/page/settings.css b/couchpotato/static/style/page/settings.css deleted file mode 100644 index bcd0b77..0000000 --- a/couchpotato/static/style/page/settings.css +++ /dev/null @@ -1,651 +0,0 @@ -.page.settings:after { - content: "."; - display: block; - clear: both; - visibility: hidden; - line-height: 0; - height: 0; -} - - .page.settings .tabs { - float: left; - width: 20%; - font-size: 20px; - text-align: right; - list-style: none; - padding: 40px 0; - margin: 0; - min-height: 470px; - background-image: -*-linear-gradient( - 20deg, - rgba(0,0,0,0) 50%, - rgba(0,0,0,0.3) 100% - ); - } - .page.settings .tabs a { - display: block; - padding: 7px 15px; - font-weight: normal; - transition: all 0.3s ease-in-out; - color: rgba(255, 255, 255, 0.8); - text-shadow: none; - } - .page.settings .tabs a:hover, - .page.settings .tabs .active a { - background: rgb(78, 89, 105); - color: #fff; - } - .page.settings .tabs > li { - border-bottom: 1px solid rgb(78, 89, 105); - } - - .page.settings .tabs .subtabs { - list-style: none; - padding: 0; - margin: -5px 0 10px; - } - - .page.settings .tabs .subtabs a { - font-size: 13px; - padding: 0 15px; - font-weight: normal; - transition: all .3s ease-in-out; - color: rgba(255, 255, 255, 0.7); - } - - .page.settings .tabs .subtabs .active a { - color: #fff; - background: rgb(78, 89, 105); - } - - - .page.settings .containers { - width: 80%; - float: left; - padding: 20px 2%; - min-height: 300px; - } - - .page .advanced { - display: none; - color: #edc07f; - } - .page.show_advanced .advanced { display: block; } - - .page.settings .tab_content { - display: none; - } - .page.settings .tab_content.active { display: block; } - - .page fieldset { - padding: 10px 0; - } - .page fieldset h2 { - font-weight: normal; - font-size: 25px; - padding: 0 9px 10px 30px; - margin: 0; - border-bottom: 1px solid #333; - box-shadow: 0 1px 0px rgba(255,255,255, 0.15); - } - .page fieldset h2 .hint { - font-size: 12px; - margin-left: 10px; - } - .page fieldset h2 .hint a { - margin: 0 !important; - padding: 0; - } - - .page fieldset.disabled .ctrlHolder { - display: none; - } - .page fieldset > .ctrlHolder:first-child { - display: block; - padding: 0; - width: auto; - margin: 0; - position: relative; - margin-bottom: -23px; - border: none; - width: 20px; - } - - .page .ctrlHolder { - line-height: 25px; - padding: 10px 10px 10px 30px; - font-size: 14px; - border: 0; - } - .page .ctrlHolder.save_success:not(:first-child) { - background: url('../../images/icon.check.png') no-repeat 7px center; - } - .page .ctrlHolder:last-child { border: none; } - .page .ctrlHolder:hover { background-color: rgba(255,255,255,0.05); } - .page .ctrlHolder.focused { background-color: rgba(255,255,255,0.2); } - .page .ctrlHolder.focused:first-child, .page .ctrlHolder:first-child{ background-color: transparent; } - - .page .ctrlHolder .formHint { - width: 47%; - margin: -18px 0; - padding: 0; - color: #fff !important; - display: inline-block; - vertical-align: middle; - padding-left: 2%; - } - - .page .check + .formHint { - float: none; - width: auto; - display: inline-block; - padding-left: 1% !important; - height: 24px; - vertical-align: middle; - } - - .page .ctrlHolder label { - font-weight: bold; - width: 20%; - margin: 0; - padding: 6px 0 0; - } - - .page .xsmall { width: 20px !important; text-align: center; } - - .page .enabler { - display: block; - } - - .page .option_list { - margin-bottom: 20px; - } - - .page .option_list .enabler { - padding: 0; - margin-left: 5px !important; - } - - .page .option_list .enabler:not(.disabled) { - margin: 0 0 0 30px; - } - - .page .option_list .enabler:not(.disabled) .ctrlHolder:first-child { - margin: 10px 0 -33px 0; - } - - .page .option_list h3 { - padding: 0; - margin: 10px 5px 0; - text-align: center; - font-weight: normal; - text-shadow: none; - text-transform: uppercase; - font-size: 12px; - background: rgba(255,255,255,0.03); - } - - .page .option_list .enabler.disabled { - display: inline-block; - margin: 3px 3px 3px 20px; - padding: 4px 0; - width: 173px; - vertical-align: top; - } - - .page .option_list .enabler.disabled h2 { - border: none; - box-shadow: none; - padding: 0 10px 0 25px; - font-size: 16px; - } - - .page .option_list .enabler:not(.disabled) h2 { - font-size: 16px; - font-weight: bold; - border: none; - border-top: 1px solid rgba(255,255,255, 0.15); - box-shadow: 0 -1px 0px #333; - margin: 0; - padding: 10px 0 5px 25px; - } - .page .option_list .enabler:not(.disabled):first-child h2 { - border: none; - box-shadow: none; - } - - .page .option_list .enabler.disabled h2 .hint { - display: none; - } - .page .option_list .enabler h2 .hint { - font-weight: normal; - } - - .page input[type=text], .page input[type=password] { - padding: 5px 3px; - margin: 0; - width: 30%; - border-radius: 3px; - } - .page .input.xsmall { width: 5% } - .page .input.small { width: 10% } - .page .input.medium { width: 15% } - .page .input.large { width: 25% } - .page .input.xlarge { width: 30% } - - .page .advanced_toggle { - clear: both; - display: block; - text-align: right; - height: 20px; - margin: 0; - } - .page .advanced_toggle span { padding: 0 5px; } - .page.show_advanced .advanced_toggle { - color: #edc07f; - } - - .page .directory { - display: inline-block; - padding: 0 4% 0 4px; - font-size: 13px; - width: 30%; - background-image: url('../../images/icon.folder.gif'); - background-repeat: no-repeat; - background-position: 97% center; - overflow: hidden; - vertical-align: top; - } - .page .directory > span { - height: 25px; - display: inline-block; - float: right; - text-align: right; - white-space: nowrap; - cursor: pointer; - } - - .page .directory_list { - z-index: 2; - position: absolute; - width: 450px; - margin: 28px 0 20px 18%; - background: #5c697b; - border-radius: 3px; - box-shadow: 0 0 50px rgba(0,0,0,0.55); - } - - .page .directory_list .pointer { - border-right: 6px solid transparent; - border-left: 6px solid transparent; - border-bottom: 6px solid #5c697b; - display: block; - position: absolute; - width: 0px; - margin: -6px 0 0 22%; - } - - .page .directory_list ul { - width: 92%; - height: 300px; - overflow: auto; - margin: 0 4%; - font-size: 16px; - } - - .page .directory_list li { - padding: 4px 10px; - cursor: pointer; - margin: 0 !important; - border-top: 1px solid rgba(255,255,255,0.1); - background: url('../../images/right.arrow.png') no-repeat 98% center; - } - .page .directory_list li:last-child { - border-bottom: 1px solid rgba(255,255,255,0.1); - } - - .page .directory_list li:hover { - background-color: #515c68; - } - - .page .directory_list li.empty { - background: none; - height: 100px; - text-align: center; - font-style: italic; - border: none; - line-height: 100px; - cursor: default; - color: #BBB; - text-shadow: none; - font-size: 12px; - } - - .page .directory_list .actions { - clear: both; - padding: 4% 4% 2%; - min-height: 25px; - } - - .page .directory_list .actions label { - float: right; - width: auto; - padding: 0; - } - .page .directory_list .actions .inlay { - margin: -2px 0 0 7px; - } - - .page .directory_list .actions .back { - font-weight: bold; - width: 160px; - display: inline-block; - padding: 0; - line-height: 120%; - vertical-align: top; - } - - .page .directory_list .actions:last-child { - float: right; - padding: 4%; - } - - .page .directory_list .actions:last-child > span { - padding: 0 5px; - text-shadow: none; - } - - .page .directory_list .actions:last-child > .clear { - left: -90%; - position: relative; - background-color: #af3128; -} - - .page .directory_list .actions:last-child > .cancel { - font-weight: bold; - color: #ddd; - } - - .page .directory_list .actions:last-child > .save { - background: #9dc156; - } - - - .page .multi_directory.is_empty .delete { - visibility: hidden; - } - - .page .multi_directory .delete { - display: none; - } - .page .multi_directory:hover .delete { - display: inline-block; - width: 22px; - height: 24px; - vertical-align: top; - background-position: center; - margin-left: 5px; - } - - - .page .tag_input select { - width: 20%; - display: inline-block; - } - - .page .tag_input .selection { - border-radius: 0 10px 10px 0; - height: 26px; - } - - .page .tag_input > input { - display: none; - } - - .page .tag_input > ul { - list-style: none; - border-radius: 3px; - cursor: text; - width: 30%; - margin: 0 !important; - min-height: 27px; - line-height: 0; - display: inline-block; - } - .page .tag_input:hover > ul { - border-radius: 3px 0 0 3px; - } - .page .tag_input:hover .formHint { display: none; } - - .page .tag_input > ul > li { - display: inline-block; - min-height: 20px; - min-width: 2px; - font-size: 12px; - padding: 0; - margin: 4px 0 0 !important; - border-width: 0; - background: 0; - line-height: 20px; - } - .page .tag_input > ul > li:first-child { min-width: 4px; } - .page .tag_input li.choice { - cursor: -moz-grab; - cursor: -webkit-grab; - cursor: grab; - padding: 0; - border-radius: 2px; - } - .page .tag_input > ul:hover > li.choice { - background: -*-linear-gradient( - 270deg, - rgba(255,255,255,0.3) 0%, - rgba(255,255,255,0.1) 100% - ); - } - .page .tag_input > ul > li.choice:hover, - .page .tag_input > ul > li.choice.selected { - background: -*-linear-gradient( - 270deg, - #5b9bd1 0%, - #406db8 100% - ); - } - - .page .tag_input .select { - display: none; - } - .page .tag_input:hover .select { display: inline-block; } - - .page .tag_input li input { - background: 0; - border: 0; - color: #fff; - outline-width: 0; - padding: 0; - min-width: 2px; - } - .page .tag_input li:first-child input { - padding-left: 2px; - min-width: 0; - } - - .page .tag_input li:not(.choice) span { - white-space: pre; - position: absolute; - top: -9999px; - } - - .page .tag_input .delete { - display: none; - height: 10px; - width: 16px; - position: absolute; - margin: -9px 0 0 -16px; - border-radius: 30px 30px 0 0; - cursor: pointer; - background: url('../../images/icon.delete.png') no-repeat center 2px, -*-linear-gradient( - 270deg, - #5b9bd1 0%, - #5b9bd1 100% - ); - background-size: 65%; - } - .page .tag_input .choice:hover .delete, - .page .tag_input .choice.selected .delete { display: inline-block; } - .page .tag_input .choice .delete:hover { - height: 14px; - margin-top: -13px; - } - - .page .combined_table .head { - margin: 0 0 0 60px; - } - .page .disabled .head { display: none; } - .page .combined_table .head abbr { - display: inline-block; - font-weight: bold; - border-bottom: 1px dotted #fff; - line-height: 140%; - cursor: help; - } - .page .combined_table .head abbr.use, .page .combined_table .head abbr.automation_urls_use { - display: none; - } - .page .combined_table .head abbr.host { - margin-right: 197px; - } - - .page .combined_table .ctrlHolder { - padding-top: 2px; - padding-bottom: 3px; - } - .page .combined_table .ctrlHolder.hide { display: none; } - - .page .combined_table .ctrlHolder > * { - margin: 0 10px 0 0; - } - - .page .combined_table .ctrlHolder .delete { - display: none; - width: 22px; - height: 22px; - vertical-align: middle; - background-position: left center; - } - .page .combined_table .ctrlHolder:hover .delete { - display: inline-block; - } - - .page .combined_table .ctrlHolder.is_empty .delete, .page.settings .combined_table .ctrlHolder.is_empty .check { - visibility: hidden; -} - - .page .tab_about .usenet { - padding: 20px 30px 0; - font-size: 17px; - } - - .page .tab_about .usenet a { - padding: 0 5px; - } - - .page .tab_about .usenet ul { - float: left; - width: 50%; - margin: 10px 0; - padding: 0; - } - - .page .tab_about .usenet li { - background: url('../../images/icon.check.png') no-repeat left center; - padding: 0 0 0 25px; - } - - .page .tab_about .donate { - float: left; - width: 42%; - text-align: center; - font-size: 17px; - padding: 0 0 0 4%; - margin: 20px 0 0; - border-left: 1px solid #333; - box-shadow: -1px 0 0 rgba(255,255,255, 0.15); - } - .page .tab_about .donate form { - padding: 10px 0 0; - } - - .page .tab_about .info { - padding: 20px 30px; - margin: 0; - overflow: hidden; - } - - .page .tab_about .info dt { - clear: both; - float: left; - width: 17%; - font-weight: bold; - } - - .page .tab_about .info dd { - float: right; - width: 80%; - padding: 0; - margin: 0; - font-style: italic; - } - .page .tab_about .info dd.version { cursor: pointer; } - - .page .tab_about .group_actions > div { - padding: 30px; - text-align: center; - } - - .page .tab_about .group_actions a { - margin: 0 10px; - font-size: 20px; - } - -.group_userscript { - background: center bottom no-repeat; - min-height: 360px; - font-size: 20px; - font-weight: normal; -} - - .group_userscript h2 .hint { - display: block; - margin: 0 !important; - } - - .group_userscript .userscript { - float: left; - margin: 14px 0 0 25px; - height: 36px; - line-height: 25px; - } - - .group_userscript .or { - float: left; - margin: 20px -10px 0 10px; - } - - .group_userscript .bookmarklet { - display: block; - display: block; - float: left; - padding: 20px 15px 0 25px; - border-radius: 5px; - } - - .group_userscript .bookmarklet span { - margin-left: 10px; - display: inline-block; - } - -.active .group_imdb_automation:not(.disabled) { - background: url('../../images/imdb_watchlist.png') no-repeat right 50px; - min-height: 210px; -} \ No newline at end of file diff --git a/couchpotato/static/style/settings.css b/couchpotato/static/style/settings.css new file mode 100644 index 0000000..bcd0b77 --- /dev/null +++ b/couchpotato/static/style/settings.css @@ -0,0 +1,651 @@ +.page.settings:after { + content: "."; + display: block; + clear: both; + visibility: hidden; + line-height: 0; + height: 0; +} + + .page.settings .tabs { + float: left; + width: 20%; + font-size: 20px; + text-align: right; + list-style: none; + padding: 40px 0; + margin: 0; + min-height: 470px; + background-image: -*-linear-gradient( + 20deg, + rgba(0,0,0,0) 50%, + rgba(0,0,0,0.3) 100% + ); + } + .page.settings .tabs a { + display: block; + padding: 7px 15px; + font-weight: normal; + transition: all 0.3s ease-in-out; + color: rgba(255, 255, 255, 0.8); + text-shadow: none; + } + .page.settings .tabs a:hover, + .page.settings .tabs .active a { + background: rgb(78, 89, 105); + color: #fff; + } + .page.settings .tabs > li { + border-bottom: 1px solid rgb(78, 89, 105); + } + + .page.settings .tabs .subtabs { + list-style: none; + padding: 0; + margin: -5px 0 10px; + } + + .page.settings .tabs .subtabs a { + font-size: 13px; + padding: 0 15px; + font-weight: normal; + transition: all .3s ease-in-out; + color: rgba(255, 255, 255, 0.7); + } + + .page.settings .tabs .subtabs .active a { + color: #fff; + background: rgb(78, 89, 105); + } + + + .page.settings .containers { + width: 80%; + float: left; + padding: 20px 2%; + min-height: 300px; + } + + .page .advanced { + display: none; + color: #edc07f; + } + .page.show_advanced .advanced { display: block; } + + .page.settings .tab_content { + display: none; + } + .page.settings .tab_content.active { display: block; } + + .page fieldset { + padding: 10px 0; + } + .page fieldset h2 { + font-weight: normal; + font-size: 25px; + padding: 0 9px 10px 30px; + margin: 0; + border-bottom: 1px solid #333; + box-shadow: 0 1px 0px rgba(255,255,255, 0.15); + } + .page fieldset h2 .hint { + font-size: 12px; + margin-left: 10px; + } + .page fieldset h2 .hint a { + margin: 0 !important; + padding: 0; + } + + .page fieldset.disabled .ctrlHolder { + display: none; + } + .page fieldset > .ctrlHolder:first-child { + display: block; + padding: 0; + width: auto; + margin: 0; + position: relative; + margin-bottom: -23px; + border: none; + width: 20px; + } + + .page .ctrlHolder { + line-height: 25px; + padding: 10px 10px 10px 30px; + font-size: 14px; + border: 0; + } + .page .ctrlHolder.save_success:not(:first-child) { + background: url('../../images/icon.check.png') no-repeat 7px center; + } + .page .ctrlHolder:last-child { border: none; } + .page .ctrlHolder:hover { background-color: rgba(255,255,255,0.05); } + .page .ctrlHolder.focused { background-color: rgba(255,255,255,0.2); } + .page .ctrlHolder.focused:first-child, .page .ctrlHolder:first-child{ background-color: transparent; } + + .page .ctrlHolder .formHint { + width: 47%; + margin: -18px 0; + padding: 0; + color: #fff !important; + display: inline-block; + vertical-align: middle; + padding-left: 2%; + } + + .page .check + .formHint { + float: none; + width: auto; + display: inline-block; + padding-left: 1% !important; + height: 24px; + vertical-align: middle; + } + + .page .ctrlHolder label { + font-weight: bold; + width: 20%; + margin: 0; + padding: 6px 0 0; + } + + .page .xsmall { width: 20px !important; text-align: center; } + + .page .enabler { + display: block; + } + + .page .option_list { + margin-bottom: 20px; + } + + .page .option_list .enabler { + padding: 0; + margin-left: 5px !important; + } + + .page .option_list .enabler:not(.disabled) { + margin: 0 0 0 30px; + } + + .page .option_list .enabler:not(.disabled) .ctrlHolder:first-child { + margin: 10px 0 -33px 0; + } + + .page .option_list h3 { + padding: 0; + margin: 10px 5px 0; + text-align: center; + font-weight: normal; + text-shadow: none; + text-transform: uppercase; + font-size: 12px; + background: rgba(255,255,255,0.03); + } + + .page .option_list .enabler.disabled { + display: inline-block; + margin: 3px 3px 3px 20px; + padding: 4px 0; + width: 173px; + vertical-align: top; + } + + .page .option_list .enabler.disabled h2 { + border: none; + box-shadow: none; + padding: 0 10px 0 25px; + font-size: 16px; + } + + .page .option_list .enabler:not(.disabled) h2 { + font-size: 16px; + font-weight: bold; + border: none; + border-top: 1px solid rgba(255,255,255, 0.15); + box-shadow: 0 -1px 0px #333; + margin: 0; + padding: 10px 0 5px 25px; + } + .page .option_list .enabler:not(.disabled):first-child h2 { + border: none; + box-shadow: none; + } + + .page .option_list .enabler.disabled h2 .hint { + display: none; + } + .page .option_list .enabler h2 .hint { + font-weight: normal; + } + + .page input[type=text], .page input[type=password] { + padding: 5px 3px; + margin: 0; + width: 30%; + border-radius: 3px; + } + .page .input.xsmall { width: 5% } + .page .input.small { width: 10% } + .page .input.medium { width: 15% } + .page .input.large { width: 25% } + .page .input.xlarge { width: 30% } + + .page .advanced_toggle { + clear: both; + display: block; + text-align: right; + height: 20px; + margin: 0; + } + .page .advanced_toggle span { padding: 0 5px; } + .page.show_advanced .advanced_toggle { + color: #edc07f; + } + + .page .directory { + display: inline-block; + padding: 0 4% 0 4px; + font-size: 13px; + width: 30%; + background-image: url('../../images/icon.folder.gif'); + background-repeat: no-repeat; + background-position: 97% center; + overflow: hidden; + vertical-align: top; + } + .page .directory > span { + height: 25px; + display: inline-block; + float: right; + text-align: right; + white-space: nowrap; + cursor: pointer; + } + + .page .directory_list { + z-index: 2; + position: absolute; + width: 450px; + margin: 28px 0 20px 18%; + background: #5c697b; + border-radius: 3px; + box-shadow: 0 0 50px rgba(0,0,0,0.55); + } + + .page .directory_list .pointer { + border-right: 6px solid transparent; + border-left: 6px solid transparent; + border-bottom: 6px solid #5c697b; + display: block; + position: absolute; + width: 0px; + margin: -6px 0 0 22%; + } + + .page .directory_list ul { + width: 92%; + height: 300px; + overflow: auto; + margin: 0 4%; + font-size: 16px; + } + + .page .directory_list li { + padding: 4px 10px; + cursor: pointer; + margin: 0 !important; + border-top: 1px solid rgba(255,255,255,0.1); + background: url('../../images/right.arrow.png') no-repeat 98% center; + } + .page .directory_list li:last-child { + border-bottom: 1px solid rgba(255,255,255,0.1); + } + + .page .directory_list li:hover { + background-color: #515c68; + } + + .page .directory_list li.empty { + background: none; + height: 100px; + text-align: center; + font-style: italic; + border: none; + line-height: 100px; + cursor: default; + color: #BBB; + text-shadow: none; + font-size: 12px; + } + + .page .directory_list .actions { + clear: both; + padding: 4% 4% 2%; + min-height: 25px; + } + + .page .directory_list .actions label { + float: right; + width: auto; + padding: 0; + } + .page .directory_list .actions .inlay { + margin: -2px 0 0 7px; + } + + .page .directory_list .actions .back { + font-weight: bold; + width: 160px; + display: inline-block; + padding: 0; + line-height: 120%; + vertical-align: top; + } + + .page .directory_list .actions:last-child { + float: right; + padding: 4%; + } + + .page .directory_list .actions:last-child > span { + padding: 0 5px; + text-shadow: none; + } + + .page .directory_list .actions:last-child > .clear { + left: -90%; + position: relative; + background-color: #af3128; +} + + .page .directory_list .actions:last-child > .cancel { + font-weight: bold; + color: #ddd; + } + + .page .directory_list .actions:last-child > .save { + background: #9dc156; + } + + + .page .multi_directory.is_empty .delete { + visibility: hidden; + } + + .page .multi_directory .delete { + display: none; + } + .page .multi_directory:hover .delete { + display: inline-block; + width: 22px; + height: 24px; + vertical-align: top; + background-position: center; + margin-left: 5px; + } + + + .page .tag_input select { + width: 20%; + display: inline-block; + } + + .page .tag_input .selection { + border-radius: 0 10px 10px 0; + height: 26px; + } + + .page .tag_input > input { + display: none; + } + + .page .tag_input > ul { + list-style: none; + border-radius: 3px; + cursor: text; + width: 30%; + margin: 0 !important; + min-height: 27px; + line-height: 0; + display: inline-block; + } + .page .tag_input:hover > ul { + border-radius: 3px 0 0 3px; + } + .page .tag_input:hover .formHint { display: none; } + + .page .tag_input > ul > li { + display: inline-block; + min-height: 20px; + min-width: 2px; + font-size: 12px; + padding: 0; + margin: 4px 0 0 !important; + border-width: 0; + background: 0; + line-height: 20px; + } + .page .tag_input > ul > li:first-child { min-width: 4px; } + .page .tag_input li.choice { + cursor: -moz-grab; + cursor: -webkit-grab; + cursor: grab; + padding: 0; + border-radius: 2px; + } + .page .tag_input > ul:hover > li.choice { + background: -*-linear-gradient( + 270deg, + rgba(255,255,255,0.3) 0%, + rgba(255,255,255,0.1) 100% + ); + } + .page .tag_input > ul > li.choice:hover, + .page .tag_input > ul > li.choice.selected { + background: -*-linear-gradient( + 270deg, + #5b9bd1 0%, + #406db8 100% + ); + } + + .page .tag_input .select { + display: none; + } + .page .tag_input:hover .select { display: inline-block; } + + .page .tag_input li input { + background: 0; + border: 0; + color: #fff; + outline-width: 0; + padding: 0; + min-width: 2px; + } + .page .tag_input li:first-child input { + padding-left: 2px; + min-width: 0; + } + + .page .tag_input li:not(.choice) span { + white-space: pre; + position: absolute; + top: -9999px; + } + + .page .tag_input .delete { + display: none; + height: 10px; + width: 16px; + position: absolute; + margin: -9px 0 0 -16px; + border-radius: 30px 30px 0 0; + cursor: pointer; + background: url('../../images/icon.delete.png') no-repeat center 2px, -*-linear-gradient( + 270deg, + #5b9bd1 0%, + #5b9bd1 100% + ); + background-size: 65%; + } + .page .tag_input .choice:hover .delete, + .page .tag_input .choice.selected .delete { display: inline-block; } + .page .tag_input .choice .delete:hover { + height: 14px; + margin-top: -13px; + } + + .page .combined_table .head { + margin: 0 0 0 60px; + } + .page .disabled .head { display: none; } + .page .combined_table .head abbr { + display: inline-block; + font-weight: bold; + border-bottom: 1px dotted #fff; + line-height: 140%; + cursor: help; + } + .page .combined_table .head abbr.use, .page .combined_table .head abbr.automation_urls_use { + display: none; + } + .page .combined_table .head abbr.host { + margin-right: 197px; + } + + .page .combined_table .ctrlHolder { + padding-top: 2px; + padding-bottom: 3px; + } + .page .combined_table .ctrlHolder.hide { display: none; } + + .page .combined_table .ctrlHolder > * { + margin: 0 10px 0 0; + } + + .page .combined_table .ctrlHolder .delete { + display: none; + width: 22px; + height: 22px; + vertical-align: middle; + background-position: left center; + } + .page .combined_table .ctrlHolder:hover .delete { + display: inline-block; + } + + .page .combined_table .ctrlHolder.is_empty .delete, .page.settings .combined_table .ctrlHolder.is_empty .check { + visibility: hidden; +} + + .page .tab_about .usenet { + padding: 20px 30px 0; + font-size: 17px; + } + + .page .tab_about .usenet a { + padding: 0 5px; + } + + .page .tab_about .usenet ul { + float: left; + width: 50%; + margin: 10px 0; + padding: 0; + } + + .page .tab_about .usenet li { + background: url('../../images/icon.check.png') no-repeat left center; + padding: 0 0 0 25px; + } + + .page .tab_about .donate { + float: left; + width: 42%; + text-align: center; + font-size: 17px; + padding: 0 0 0 4%; + margin: 20px 0 0; + border-left: 1px solid #333; + box-shadow: -1px 0 0 rgba(255,255,255, 0.15); + } + .page .tab_about .donate form { + padding: 10px 0 0; + } + + .page .tab_about .info { + padding: 20px 30px; + margin: 0; + overflow: hidden; + } + + .page .tab_about .info dt { + clear: both; + float: left; + width: 17%; + font-weight: bold; + } + + .page .tab_about .info dd { + float: right; + width: 80%; + padding: 0; + margin: 0; + font-style: italic; + } + .page .tab_about .info dd.version { cursor: pointer; } + + .page .tab_about .group_actions > div { + padding: 30px; + text-align: center; + } + + .page .tab_about .group_actions a { + margin: 0 10px; + font-size: 20px; + } + +.group_userscript { + background: center bottom no-repeat; + min-height: 360px; + font-size: 20px; + font-weight: normal; +} + + .group_userscript h2 .hint { + display: block; + margin: 0 !important; + } + + .group_userscript .userscript { + float: left; + margin: 14px 0 0 25px; + height: 36px; + line-height: 25px; + } + + .group_userscript .or { + float: left; + margin: 20px -10px 0 10px; + } + + .group_userscript .bookmarklet { + display: block; + display: block; + float: left; + padding: 20px 15px 0 25px; + border-radius: 5px; + } + + .group_userscript .bookmarklet span { + margin-left: 10px; + display: inline-block; + } + +.active .group_imdb_automation:not(.disabled) { + background: url('../../images/imdb_watchlist.png') no-repeat right 50px; + min-height: 210px; +} \ No newline at end of file diff --git a/couchpotato/templates/_desktop.html b/couchpotato/templates/_desktop.html index 8689b66..1d61806 100644 --- a/couchpotato/templates/_desktop.html +++ b/couchpotato/templates/_desktop.html @@ -1,43 +1,14 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {% for url in fireEvent('clientscript.get_scripts', as_html = True, single = True) %} + {% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'front', single = True) %} + {% endfor %} + {% for url in fireEvent('clientscript.get_scripts', as_html = True, location = 'front', single = True) %} + {% endfor %} + + {% for url in fireEvent('clientscript.get_scripts', as_html = True, location = 'head', single = True) %} {% endfor %} - {% for url in fireEvent('clientscript.get_styles', as_html = True, single = True) %} + {% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'head', single = True) %} {% endfor %} diff --git a/libs/minify/__init__.py b/libs/minify/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/minify/cssmin.py b/libs/minify/cssmin.py new file mode 100644 index 0000000..c29cb83 --- /dev/null +++ b/libs/minify/cssmin.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# `cssmin.py` - A Python port of the YUI CSS compressor. + + +from StringIO import StringIO # The pure-Python StringIO supports unicode. +import re + + +__version__ = '0.1.1' + + +def remove_comments(css): + """Remove all CSS comment blocks.""" + + iemac = False + preserve = False + comment_start = css.find("/*") + while comment_start >= 0: + # Preserve comments that look like `/*!...*/`. + # Slicing is used to make sure we don"t get an IndexError. + preserve = css[comment_start + 2:comment_start + 3] == "!" + + comment_end = css.find("*/", comment_start + 2) + if comment_end < 0: + if not preserve: + css = css[:comment_start] + break + elif comment_end >= (comment_start + 2): + if css[comment_end - 1] == "\\": + # This is an IE Mac-specific comment; leave this one and the + # following one alone. + comment_start = comment_end + 2 + iemac = True + elif iemac: + comment_start = comment_end + 2 + iemac = False + elif not preserve: + css = css[:comment_start] + css[comment_end + 2:] + else: + comment_start = comment_end + 2 + comment_start = css.find("/*", comment_start) + + return css + + +def remove_unnecessary_whitespace(css): + """Remove unnecessary whitespace characters.""" + + def pseudoclasscolon(css): + + """ + Prevents 'p :link' from becoming 'p:link'. + + Translates 'p :link' into 'p ___PSEUDOCLASSCOLON___link'; this is + translated back again later. + """ + + regex = re.compile(r"(^|\})(([^\{\:])+\:)+([^\{]*\{)") + match = regex.search(css) + while match: + css = ''.join([ + css[:match.start()], + match.group().replace(":", "___PSEUDOCLASSCOLON___"), + css[match.end():]]) + match = regex.search(css) + return css + + css = pseudoclasscolon(css) + # Remove spaces from before things. + css = re.sub(r"\s+([!{};:>+\(\)\],])", r"\1", css) + + # If there is a `@charset`, then only allow one, and move to the beginning. + css = re.sub(r"^(.*)(@charset \"[^\"]*\";)", r"\2\1", css) + css = re.sub(r"^(\s*@charset [^;]+;\s*)+", r"\1", css) + + # Put the space back in for a few cases, such as `@media screen` and + # `(-webkit-min-device-pixel-ratio:0)`. + css = re.sub(r"\band\(", "and (", css) + + # Put the colons back. + css = css.replace('___PSEUDOCLASSCOLON___', ':') + + # Remove spaces from after things. + css = re.sub(r"([!{}:;>+\(\[,])\s+", r"\1", css) + + return css + + +def remove_unnecessary_semicolons(css): + """Remove unnecessary semicolons.""" + + return re.sub(r";+\}", "}", css) + + +def remove_empty_rules(css): + """Remove empty rules.""" + + return re.sub(r"[^\}\{]+\{\}", "", css) + + +def normalize_rgb_colors_to_hex(css): + """Convert `rgb(51,102,153)` to `#336699`.""" + + regex = re.compile(r"rgb\s*\(\s*([0-9,\s]+)\s*\)") + match = regex.search(css) + while match: + colors = match.group(1).split(",") + hexcolor = '#%.2x%.2x%.2x' % tuple(map(int, colors)) + css = css.replace(match.group(), hexcolor) + match = regex.search(css) + return css + + +def condense_zero_units(css): + """Replace `0(px, em, %, etc)` with `0`.""" + + return re.sub(r"([\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", r"\1\2", css) + + +def condense_multidimensional_zeros(css): + """Replace `:0 0 0 0;`, `:0 0 0;` etc. with `:0;`.""" + + css = css.replace(":0 0 0 0;", ":0;") + css = css.replace(":0 0 0;", ":0;") + css = css.replace(":0 0;", ":0;") + + # Revert `background-position:0;` to the valid `background-position:0 0;`. + css = css.replace("background-position:0;", "background-position:0 0;") + + return css + + +def condense_floating_points(css): + """Replace `0.6` with `.6` where possible.""" + + return re.sub(r"(:|\s)0+\.(\d+)", r"\1.\2", css) + + +def condense_hex_colors(css): + """Shorten colors from #AABBCC to #ABC where possible.""" + + regex = re.compile(r"([^\"'=\s])(\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])") + match = regex.search(css) + while match: + first = match.group(3) + match.group(5) + match.group(7) + second = match.group(4) + match.group(6) + match.group(8) + if first.lower() == second.lower(): + css = css.replace(match.group(), match.group(1) + match.group(2) + '#' + first) + match = regex.search(css, match.end() - 3) + else: + match = regex.search(css, match.end()) + return css + + +def condense_whitespace(css): + """Condense multiple adjacent whitespace characters into one.""" + + return re.sub(r"\s+", " ", css) + + +def condense_semicolons(css): + """Condense multiple adjacent semicolon characters into one.""" + + return re.sub(r";;+", ";", css) + + +def wrap_css_lines(css, line_length): + """Wrap the lines of the given CSS to an approximate length.""" + + lines = [] + line_start = 0 + for i, char in enumerate(css): + # It's safe to break after `}` characters. + if char == '}' and (i - line_start >= line_length): + lines.append(css[line_start:i + 1]) + line_start = i + 1 + + if line_start < len(css): + lines.append(css[line_start:]) + return '\n'.join(lines) + + +def cssmin(css, wrap = None): + css = remove_comments(css) + css = condense_whitespace(css) + # A pseudo class for the Box Model Hack + # (see http://tantek.com/CSS/Examples/boxmodelhack.html) + css = css.replace('"\\"}\\""', "___PSEUDOCLASSBMH___") + #css = remove_unnecessary_whitespace(css) + css = remove_unnecessary_semicolons(css) + css = condense_zero_units(css) + css = condense_multidimensional_zeros(css) + css = condense_floating_points(css) + css = normalize_rgb_colors_to_hex(css) + css = condense_hex_colors(css) + if wrap is not None: + css = wrap_css_lines(css, wrap) + css = css.replace("___PSEUDOCLASSBMH___", '"\\"}\\""') + css = condense_semicolons(css) + return css.strip() + + +def main(): + import optparse + import sys + + p = optparse.OptionParser( + prog = "cssmin", version = __version__, + usage = "%prog [--wrap N]", + description = """Reads raw CSS from stdin, and writes compressed CSS to stdout.""") + + p.add_option( + '-w', '--wrap', type = 'int', default = None, metavar = 'N', + help = "Wrap output to approximately N chars per line.") + + options, args = p.parse_args() + sys.stdout.write(cssmin(sys.stdin.read(), wrap = options.wrap)) + + +if __name__ == '__main__': + main() diff --git a/libs/minify/jsmin.py b/libs/minify/jsmin.py new file mode 100644 index 0000000..a1b81f9 --- /dev/null +++ b/libs/minify/jsmin.py @@ -0,0 +1,218 @@ +#!/usr/bin/python + +# This code is original from jsmin by Douglas Crockford, it was translated to +# Python by Baruch Even. The original code had the following copyright and +# license. +# +# /* jsmin.c +# 2007-05-22 +# +# Copyright (c) 2002 Douglas Crockford (www.crockford.com) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is furnished to do +# so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# The Software shall be used for Good, not Evil. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# */ + +from StringIO import StringIO + +def jsmin(js): + ins = StringIO(js) + outs = StringIO() + JavascriptMinify().minify(ins, outs) + str = outs.getvalue() + if len(str) > 0 and str[0] == '\n': + str = str[1:] + return str + +def isAlphanum(c): + """return true if the character is a letter, digit, underscore, + dollar sign, or non-ASCII character. + """ + return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or + (c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or (c is not None and ord(c) > 126)); + +class UnterminatedComment(Exception): + pass + +class UnterminatedStringLiteral(Exception): + pass + +class UnterminatedRegularExpression(Exception): + pass + +class JavascriptMinify(object): + + def _outA(self): + self.outstream.write(self.theA) + def _outB(self): + self.outstream.write(self.theB) + + def _get(self): + """return the next character from stdin. Watch out for lookahead. If + the character is a control character, translate it to a space or + linefeed. + """ + c = self.theLookahead + self.theLookahead = None + if c == None: + c = self.instream.read(1) + if c >= ' ' or c == '\n': + return c + if c == '': # EOF + return '\000' + if c == '\r': + return '\n' + return ' ' + + def _peek(self): + self.theLookahead = self._get() + return self.theLookahead + + def _next(self): + """get the next character, excluding comments. peek() is used to see + if a '/' is followed by a '/' or '*'. + """ + c = self._get() + if c == '/': + p = self._peek() + if p == '/': + c = self._get() + while c > '\n': + c = self._get() + return c + if p == '*': + c = self._get() + while 1: + c = self._get() + if c == '*': + if self._peek() == '/': + self._get() + return ' ' + if c == '\000': + raise UnterminatedComment() + + return c + + def _action(self, action): + """do something! What you do is determined by the argument: + 1 Output A. Copy B to A. Get the next B. + 2 Copy B to A. Get the next B. (Delete A). + 3 Get the next B. (Delete B). + action treats a string as a single character. Wow! + action recognizes a regular expression if it is preceded by ( or , or =. + """ + if action <= 1: + self._outA() + + if action <= 2: + self.theA = self.theB + if self.theA == "'" or self.theA == '"': + while 1: + self._outA() + self.theA = self._get() + if self.theA == self.theB: + break + if self.theA <= '\n': + raise UnterminatedStringLiteral() + if self.theA == '\\': + self._outA() + self.theA = self._get() + + + if action <= 3: + self.theB = self._next() + if self.theB == '/' and (self.theA == '(' or self.theA == ',' or + self.theA == '=' or self.theA == ':' or + self.theA == '[' or self.theA == '?' or + self.theA == '!' or self.theA == '&' or + self.theA == '|' or self.theA == ';' or + self.theA == '{' or self.theA == '}' or + self.theA == '\n'): + self._outA() + self._outB() + while 1: + self.theA = self._get() + if self.theA == '/': + break + elif self.theA == '\\': + self._outA() + self.theA = self._get() + elif self.theA <= '\n': + raise UnterminatedRegularExpression() + self._outA() + self.theB = self._next() + + + def _jsmin(self): + """Copy the input to the output, deleting the characters which are + insignificant to JavaScript. Comments will be removed. Tabs will be + replaced with spaces. Carriage returns will be replaced with linefeeds. + Most spaces and linefeeds will be removed. + """ + self.theA = '\n' + self._action(3) + + while self.theA != '\000': + if self.theA == ' ': + if isAlphanum(self.theB): + self._action(1) + else: + self._action(2) + elif self.theA == '\n': + if self.theB in ['{', '[', '(', '+', '-']: + self._action(1) + elif self.theB == ' ': + self._action(3) + else: + if isAlphanum(self.theB): + self._action(1) + else: + self._action(2) + else: + if self.theB == ' ': + if isAlphanum(self.theA): + self._action(1) + else: + self._action(3) + elif self.theB == '\n': + if self.theA in ['}', ']', ')', '+', '-', '"', '\'']: + self._action(1) + else: + if isAlphanum(self.theA): + self._action(1) + else: + self._action(3) + else: + self._action(1) + + def minify(self, instream, outstream): + self.instream = instream + self.outstream = outstream + self.theA = '\n' + self.theB = None + self.theLookahead = None + + self._jsmin() + self.instream.close() + +if __name__ == '__main__': + import sys + jsm = JavascriptMinify() + jsm.minify(sys.stdin, sys.stdout) From af113c0ffd0ce1d22f48dd09daf44b9ec69ea718 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 4 Feb 2013 21:59:12 +0100 Subject: [PATCH 07/10] Minifier 2 --- couchpotato/core/_base/clientscript/main.py | 2 +- couchpotato/static/style/settings.css | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/couchpotato/core/_base/clientscript/main.py b/couchpotato/core/_base/clientscript/main.py index e8624b0..7c95367 100644 --- a/couchpotato/core/_base/clientscript/main.py +++ b/couchpotato/core/_base/clientscript/main.py @@ -138,7 +138,7 @@ class ClientScript(Plugin): data = '' if as_html else [] try: - if Env.get('dev'): + if not Env.get('dev'): return self.minified[type][location] return self.urls[type][location] diff --git a/couchpotato/static/style/settings.css b/couchpotato/static/style/settings.css index bcd0b77..3af7dba 100644 --- a/couchpotato/static/style/settings.css +++ b/couchpotato/static/style/settings.css @@ -118,7 +118,7 @@ border: 0; } .page .ctrlHolder.save_success:not(:first-child) { - background: url('../../images/icon.check.png') no-repeat 7px center; + background: url('../images/icon.check.png') no-repeat 7px center; } .page .ctrlHolder:last-child { border: none; } .page .ctrlHolder:hover { background-color: rgba(255,255,255,0.05); } @@ -250,7 +250,7 @@ padding: 0 4% 0 4px; font-size: 13px; width: 30%; - background-image: url('../../images/icon.folder.gif'); + background-image: url('../images/icon.folder.gif'); background-repeat: no-repeat; background-position: 97% center; overflow: hidden; @@ -298,7 +298,7 @@ cursor: pointer; margin: 0 !important; border-top: 1px solid rgba(255,255,255,0.1); - background: url('../../images/right.arrow.png') no-repeat 98% center; + background: url('../images/right.arrow.png') no-repeat 98% center; } .page .directory_list li:last-child { border-bottom: 1px solid rgba(255,255,255,0.1); @@ -484,7 +484,7 @@ margin: -9px 0 0 -16px; border-radius: 30px 30px 0 0; cursor: pointer; - background: url('../../images/icon.delete.png') no-repeat center 2px, -*-linear-gradient( + background: url('../images/icon.delete.png') no-repeat center 2px, -*-linear-gradient( 270deg, #5b9bd1 0%, #5b9bd1 100% @@ -558,7 +558,7 @@ } .page .tab_about .usenet li { - background: url('../../images/icon.check.png') no-repeat left center; + background: url('../images/icon.check.png') no-repeat left center; padding: 0 0 0 25px; } @@ -646,6 +646,6 @@ } .active .group_imdb_automation:not(.disabled) { - background: url('../../images/imdb_watchlist.png') no-repeat right 50px; + background: url('../images/imdb_watchlist.png') no-repeat right 50px; min-height: 210px; } \ No newline at end of file From edb232df606db6e13b570659bb3bffe83cf0cc60 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 4 Feb 2013 22:35:25 +0100 Subject: [PATCH 08/10] Don't fire progress untill other request ended --- couchpotato/static/scripts/page/wanted.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/couchpotato/static/scripts/page/wanted.js b/couchpotato/static/scripts/page/wanted.js index 3f6e065..a9e0bc7 100644 --- a/couchpotato/static/scripts/page/wanted.js +++ b/couchpotato/static/scripts/page/wanted.js @@ -52,7 +52,8 @@ Page.Wanted = new Class({ var start_text = self.manual_search.get('text'); self.progress_interval = setInterval(function(){ - Api.request('searcher.progress', { + if(self.search_progress && self.search_progress.running) return; + self.search_progress = Api.request('searcher.progress', { 'onComplete': function(json){ self.search_in_progress = true; if(!json.progress){ @@ -65,7 +66,7 @@ Page.Wanted = new Class({ self.manual_search.set('text', 'Searching.. (' + (((progress.total-progress.to_go)/progress.total)*100).round() + '%)'); } } - }) + }); }, 1000); } From 2ca2cc95972ceab6a0d5be77e274e8146ea0af97 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 4 Feb 2013 22:36:22 +0100 Subject: [PATCH 09/10] Don't fire openpage twice on start --- couchpotato/static/scripts/couchpotato.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index a94f5d4..8f4e665 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -24,8 +24,8 @@ var CouchPotato = new Class({ if(window.location.hash) History.handleInitialState(); - - self.openPage(window.location.pathname); + else + self.openPage(window.location.pathname); History.addEvent('change', self.openPage.bind(self)); self.c.addEvent('click:relay(a[href^=/]:not([target]))', self.pushState.bind(self)); From 87cdf9222ddf349a15790708abd4f05e8802acce Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 4 Feb 2013 23:05:21 +0100 Subject: [PATCH 10/10] Hide test notification button --- couchpotato/core/notifications/core/static/notification.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/couchpotato/core/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js index db9db84..52062e9 100644 --- a/couchpotato/core/notifications/core/static/notification.js +++ b/couchpotato/core/notifications/core/static/notification.js @@ -178,11 +178,14 @@ var NotificationBase = new Class({ }, addTestButton: function(fieldset, plugin_name){ - var self = this; + var self = this, + button_name = self.testButtonName(fieldset); + + if(button_name.contains('Notifications')) return; new Element('.ctrlHolder.test_button').adopt( new Element('a.button', { - 'text': self.testButtonName(fieldset), + 'text': button_name, 'events': { 'click': function(){ var button = fieldset.getElement('.test_button .button'); @@ -191,7 +194,7 @@ var NotificationBase = new Class({ Api.request('notify.'+plugin_name+'.test', { 'onComplete': function(json){ - button.set('text', self.testButtonName(fieldset)); + button.set('text', button_name); if(json.success){ var message = new Element('span.success', {