diff --git a/couchpotato/core/_base/_core/main.py b/couchpotato/core/_base/_core/main.py index 0423e6666803e77e25625d3fec2ec7f81c9975b8..c91140fab3f28a00a1fd430224e18c44b08ae015 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/core/_base/clientscript/main.py b/couchpotato/core/_base/clientscript/main.py index bb380be64c78ab382bd6eabbb8831470da2b9469..7c95367ae77f14c88e7c36702734442a20c389f7 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 not 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/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js index db9db843a29f2c54560966eb6b9d5853cfb4ffe5..52062e93d122098f606324eb6095deb0789c4d7d 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', { diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py index 73a2c306253253a3548e092c0f8dc9821a404893..edecaa943d0ecb0d1754b2c25d99e013987a1c80 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 a9eab33dfaf7d1e6bf5059cfdeff5a62d4ccfb58..0dc01783406c0b0810051a237a6d3b7d7a98ed72 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/core/plugins/movie/main.py b/couchpotato/core/plugins/movie/main.py index 8b0761c118740849a6b3892d7b8585dc882bbcd5..0e942f09196c2037f25b1c6a968f6778da5d7489 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/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index 91dfb339508dfc6d5b7fdd731be95aa9d3c8ea61..62358dde8bc63ada9383cb25f3983da8f3404585 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 diff --git a/couchpotato/core/plugins/scanner/main.py b/couchpotato/core/plugins/scanner/main.py index 684f68210f05f4817b052d318df060c5955dde63..b822bc0f9adf9f99a811489226f39a7ad1734a57 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'], diff --git a/couchpotato/core/providers/movie/couchpotatoapi/main.py b/couchpotato/core/providers/movie/couchpotatoapi/main.py index 88883d79dbe6567690c9ef2cfe2fa3c13331a47c..87586847e0364f56a8a6d5efa6151d16e02cd7fd 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__) @@ -17,6 +15,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,58 +28,44 @@ 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): + return self.getJsonData(self.urls['search'] % tryUrlencode(q), headers = self.getRequestHeaders()) - cache_key = 'cpapi.cache.%s' % q - cached = self.getCache(cache_key, self.urls['search'] % tryUrlencode(q), headers = self.getRequestHeaders()) + def isMovie(self, identifier = None): - if cached: - try: - movies = json.loads(cached) - return movies - except: - log.error('Failed parsing search results: %s', traceback.format_exc()) + if not identifier: + return - 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: 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 diff --git a/couchpotato/environment.py b/couchpotato/environment.py index d8c03c7cc96ff44316d5e95c255f1f23acfdfb28..bd637ad219fdcfd856c57f2e9349c3e9096d2f2a 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/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index a94f5d4e674af7f437a9e9116db44d9a29caa5df..8f4e665f8413afec3d9ddf2391bef7a13d7c0484 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)); diff --git a/couchpotato/static/scripts/library/prefix_free.js b/couchpotato/static/scripts/library/prefix_free.js index 8dd99e2e1881cf118173dad77d906b6499ab1643..b6d9812a0eba7ac704c3a89d6bed6f143367e7de 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/scripts/page/wanted.js b/couchpotato/static/scripts/page/wanted.js index 3f6e065b57473a9de69ed8f2ed449b19134573a6..a9e0bc71f579432729450a781964a56b840a0e6f 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); } diff --git a/couchpotato/static/style/page/settings.css b/couchpotato/static/style/settings.css similarity index 96% rename from couchpotato/static/style/page/settings.css rename to couchpotato/static/style/settings.css index bcd0b774850d6805924757a22ebcbbf88d1669b8..3af7dba78587ad380cf31465f3d11a11004cc645 100644 --- a/couchpotato/static/style/page/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 diff --git a/couchpotato/templates/_desktop.html b/couchpotato/templates/_desktop.html index 8689b66698cde6715b7ac33e2480f1c48e2701e3..1d618066325bd497a2095c9a90a649f065ab96ef 100644 --- a/couchpotato/templates/_desktop.html +++ b/couchpotato/templates/_desktop.html @@ -1,43 +1,14 @@ <!doctype html> <html> <head> - <link rel="stylesheet" href="{{ url_for('web.static', filename='style/main.css') }}" type="text/css"> - <link rel="stylesheet" href="{{ url_for('web.static', filename='style/uniform.generic.css') }}" type="text/css"> - <link rel="stylesheet" href="{{ url_for('web.static', filename='style/uniform.css') }}" type="text/css"> - - <link rel="stylesheet" href="{{ url_for('web.static', filename='style/page/settings.css') }}" type="text/css"> - - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/library/mootools.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/library/mootools_more.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/library/prefix_free.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/library/uniform.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/library/form_replacement/form_check.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/library/form_replacement/form_radio.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/library/form_replacement/form_dropdown.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/library/form_replacement/form_selectoption.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/library/question.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/library/scrollspy.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/library/spin.js') }}"></script> - - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/couchpotato.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/api.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/library/history.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/page.js') }}"></script> - - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/block.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/block/navigation.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/block/footer.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/block/menu.js') }}"></script> - - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/page/wanted.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/page/settings.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/page/about.js') }}"></script> - <script type="text/javascript" src="{{ url_for('web.static', filename='scripts/page/manage.js') }}"></script> - <!--<script type="text/javascript" src="{{ url_for('web.static', filename='scripts/page/soon.js') }}"></script>--> - - {% 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) %} + <link rel="stylesheet" href="{{ url_for('web.index') }}{{ url }}" type="text/css">{% endfor %} + {% for url in fireEvent('clientscript.get_scripts', as_html = True, location = 'front', single = True) %} + <script type="text/javascript" src="{{ url_for('web.index') }}{{ url }}"></script>{% endfor %} + + {% for url in fireEvent('clientscript.get_scripts', as_html = True, location = 'head', single = True) %} <script type="text/javascript" src="{{ url_for('web.index') }}{{ url }}"></script>{% 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) %} <link rel="stylesheet" href="{{ url_for('web.index') }}{{ url }}" type="text/css">{% endfor %} <link href="{{ url_for('web.static', filename='images/favicon.ico') }}" rel="icon" type="image/x-icon" /> diff --git a/libs/daemon.py b/libs/daemon.py index 0e3d0d63b1ccb27969021d3a8634213fa0264af8..805cfa248050fa354d2321ab5e18b592008d63e2 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): diff --git a/libs/minify/__init__.py b/libs/minify/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/libs/minify/cssmin.py b/libs/minify/cssmin.py new file mode 100644 index 0000000000000000000000000000000000000000..c29cb83b7fdc825124bb1b5799d7e55ba0b600b8 --- /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 0000000000000000000000000000000000000000..a1b81f9ab7a3ae0d4cb683000eb96fbc2289427a --- /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)