Browse Source

Merge branch 'refs/heads/develop'

pull/1399/head
Ruud 12 years ago
parent
commit
811f35b028
  1. 2
      couchpotato/core/_base/_core/main.py
  2. 128
      couchpotato/core/_base/clientscript/main.py
  3. 9
      couchpotato/core/notifications/core/static/notification.js
  4. 2
      couchpotato/core/plugins/base.py
  5. 2
      couchpotato/core/plugins/file/main.py
  6. 5
      couchpotato/core/plugins/movie/main.py
  7. 2
      couchpotato/core/plugins/renamer/main.py
  8. 2
      couchpotato/core/plugins/scanner/main.py
  9. 51
      couchpotato/core/providers/movie/couchpotatoapi/main.py
  10. 2
      couchpotato/environment.py
  11. 2
      couchpotato/static/scripts/couchpotato.js
  12. 20
      couchpotato/static/scripts/library/prefix_free.js
  13. 5
      couchpotato/static/scripts/page/wanted.js
  14. 12
      couchpotato/static/style/settings.css
  15. 43
      couchpotato/templates/_desktop.html
  16. 2
      libs/daemon.py
  17. 0
      libs/minify/__init__.py
  18. 223
      libs/minify/cssmin.py
  19. 218
      libs/minify/jsmin.py

2
couchpotato/core/_base/_core/main.py

@ -179,7 +179,7 @@ class Core(Plugin):
if Env.get('daemonized'): return if Env.get('daemonized'): return
def signal_handler(signal, frame): def signal_handler(signal, frame):
fireEvent('app.shutdown') fireEvent('app.shutdown', single = True)
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGTERM, signal_handler)

128
couchpotato/core/_base/clientscript/main.py

@ -1,15 +1,58 @@
from couchpotato.core.event import addEvent from couchpotato.core.event import addEvent
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin 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__) log = CPLog(__name__)
class ClientScript(Plugin): class ClientScript(Plugin):
urls = { core_static = {
'style': {}, 'style': [
'script': {}, '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 = { html = {
@ -24,6 +67,66 @@ class ClientScript(Plugin):
addEvent('clientscript.get_styles', self.getStyles) addEvent('clientscript.get_styles', self.getStyles)
addEvent('clientscript.get_scripts', self.getScripts) 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): def getStyles(self, *args, **kwargs):
return self.get('style', *args, **kwargs) return self.get('style', *args, **kwargs)
@ -35,22 +138,27 @@ class ClientScript(Plugin):
data = '' if as_html else [] data = '' if as_html else []
try: try:
if not Env.get('dev'):
return self.minified[type][location]
return self.urls[type][location] return self.urls[type][location]
except Exception, e: except Exception, e:
log.error(e) log.error(e)
return data return data
def registerStyle(self, path, position = 'head'): def registerStyle(self, api_path, file_path, position = 'head'):
self.register(path, 'style', position) self.register(api_path, file_path, 'style', position)
def registerScript(self, path, position = 'head'): def registerScript(self, api_path, file_path, position = 'head'):
self.register(path, 'script', position) 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): if not self.urls[type].get(location):
self.urls[type][location] = [] self.urls[type][location] = []
self.urls[type][location].append(api_path)
filePath = filepath if not self.paths[type].get(location):
self.urls[type][location].append(filePath) self.paths[type][location] = []
self.paths[type][location].append(file_path)

9
couchpotato/core/notifications/core/static/notification.js

@ -178,11 +178,14 @@ var NotificationBase = new Class({
}, },
addTestButton: function(fieldset, plugin_name){ 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('.ctrlHolder.test_button').adopt(
new Element('a.button', { new Element('a.button', {
'text': self.testButtonName(fieldset), 'text': button_name,
'events': { 'events': {
'click': function(){ 'click': function(){
var button = fieldset.getElement('.test_button .button'); var button = fieldset.getElement('.test_button .button');
@ -191,7 +194,7 @@ var NotificationBase = new Class({
Api.request('notify.'+plugin_name+'.test', { Api.request('notify.'+plugin_name+'.test', {
'onComplete': function(json){ 'onComplete': function(json){
button.set('text', self.testButtonName(fieldset)); button.set('text', button_name);
if(json.success){ if(json.success){
var message = new Element('span.success', { var message = new Element('span.success', {

2
couchpotato/core/plugins/base.py

@ -64,7 +64,7 @@ class Plugin(object):
for f in glob.glob(os.path.join(self.plugin_path, 'static', '*')): for f in glob.glob(os.path.join(self.plugin_path, 'static', '*')):
ext = getExt(f) ext = getExt(f)
if ext in ['js', 'css']: 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): def showStatic(self, filename):
d = os.path.join(self.plugin_path, 'static') d = os.path.join(self.plugin_path, 'static')

2
couchpotato/core/plugins/file/main.py

@ -71,7 +71,7 @@ class FileManager(Plugin):
db = get_session() db = get_session()
for root, dirs, walk_files in os.walk(Env.get('cache_dir')): for root, dirs, walk_files in os.walk(Env.get('cache_dir')):
for filename in walk_files: 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) file_path = os.path.join(root, filename)
f = db.query(File).filter(File.path == toUnicode(file_path)).first() f = db.query(File).filter(File.path == toUnicode(file_path)).first()
if not f: if not f:

5
couchpotato/core/plugins/movie/main.py

@ -292,9 +292,8 @@ class MoviePlugin(Plugin):
return False return False
else: else:
try: try:
url = 'http://thetvdb.com/api/GetSeriesByRemoteID.php?imdbid=%s' % params.get('identifier') is_movie = fireEvent('movie.is_movie', identifier = params.get('identifier'), single = True)
tvdb = self.getCache('thetvdb.%s' % params.get('identifier'), url = url, show_error = False) if not is_movie:
if tvdb and 'series' in tvdb.lower():
msg = 'Can\'t add movie, seems to be a TV show.' msg = 'Can\'t add movie, seems to be a TV show.'
log.error(msg) log.error(msg)
fireEvent('notify.frontend', type = 'movie.is_tvshow', message = msg) fireEvent('notify.frontend', type = 'movie.is_tvshow', message = msg)

2
couchpotato/core/plugins/renamer/main.py

@ -468,7 +468,7 @@ class Renamer(Plugin):
except: except:
log.error('Couldn\'t move file "%s" to "%s": %s', (old, dest, traceback.format_exc())) log.error('Couldn\'t move file "%s" to "%s": %s', (old, dest, traceback.format_exc()))
raise Exception raise
return True return True

2
couchpotato/core/plugins/scanner/main.py

@ -23,7 +23,7 @@ class Scanner(Plugin):
'media': 314572800, # 300MB 'media': 314572800, # 300MB
'trailer': 1048576, # 1MB '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'] ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts', 'bdmv', 'certificate']
extensions = { extensions = {
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts', 'm4v'], 'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts', 'm4v'],

51
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.logger import CPLog
from couchpotato.core.providers.movie.base import MovieProvider from couchpotato.core.providers.movie.base import MovieProvider
from couchpotato.core.settings.model import Movie from couchpotato.core.settings.model import Movie
from flask.helpers import json
import time import time
import traceback
log = CPLog(__name__) log = CPLog(__name__)
@ -17,6 +15,7 @@ class CouchPotatoApi(MovieProvider):
urls = { urls = {
'search': 'https://couchpota.to/api/search/%s/', 'search': 'https://couchpota.to/api/search/%s/',
'info': 'https://couchpota.to/api/info/%s/', 'info': 'https://couchpota.to/api/info/%s/',
'is_movie': 'https://couchpota.to/api/ismovie/%s/',
'eta': 'https://couchpota.to/api/eta/%s/', 'eta': 'https://couchpota.to/api/eta/%s/',
'suggest': 'https://couchpota.to/api/suggest/%s/%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.info', self.getInfo, priority = 1)
addEvent('movie.search', self.search, priority = 1) addEvent('movie.search', self.search, priority = 1)
addEvent('movie.release_date', self.getReleaseDate) addEvent('movie.release_date', self.getReleaseDate)
addEvent('movie.is_movie', self.isMovie)
def search(self, q, limit = 12): def search(self, q, limit = 12):
return self.getJsonData(self.urls['search'] % tryUrlencode(q), headers = self.getRequestHeaders())
cache_key = 'cpapi.cache.%s' % q def isMovie(self, identifier = None):
cached = self.getCache(cache_key, self.urls['search'] % tryUrlencode(q), headers = self.getRequestHeaders())
if cached: if not identifier:
try: return
movies = json.loads(cached)
return movies data = self.getJsonData(self.urls['is_movie'] % identifier, headers = self.getRequestHeaders())
except: if data:
log.error('Failed parsing search results: %s', traceback.format_exc()) return data.get('is_movie', True)
return [] return True
def getInfo(self, identifier = None): def getInfo(self, identifier = None):
if not identifier: if not identifier:
return return
cache_key = 'cpapi.cache.info.%s' % identifier result = self.getJsonData(self.urls['info'] % identifier, headers = self.getRequestHeaders())
cached = self.getCache(cache_key, self.urls['info'] % identifier, headers = self.getRequestHeaders()) if result: return result
if cached:
try:
movie = json.loads(cached)
return movie
except:
log.error('Failed parsing info results: %s', traceback.format_exc())
return {} return {}
def getReleaseDate(self, identifier = None): def getReleaseDate(self, identifier = None):
if identifier is None: return {} if identifier is None: return {}
try:
data = self.urlopen(self.urls['eta'] % identifier, headers = self.getRequestHeaders()) dates = self.getJsonData(self.urls['eta'] % identifier, headers = self.getRequestHeaders())
dates = json.loads(data)
log.debug('Found ETA for %s: %s', (identifier, dates)) 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 {} return dates
def suggest(self, movies = [], ignore = []): def suggest(self, movies = [], ignore = []):
try:
data = self.urlopen(self.urls['suggest'] % (','.join(movies), ','.join(ignore))) suggestions = self.getJsonData(self.urls['suggest'] % (','.join(movies), ','.join(ignore)))
suggestions = json.loads(data)
log.info('Found Suggestions for %s', (suggestions)) log.info('Found Suggestions for %s', (suggestions))
except Exception, e:
log.error('Error getting suggestions for %s: %s', (movies, e))
return suggestions return suggestions

2
couchpotato/environment.py

@ -20,7 +20,7 @@ class Env(object):
_options = None _options = None
_args = None _args = None
_quiet = False _quiet = False
_deamonize = False _daemonized = False
_desktop = None _desktop = None
_session = None _session = None

2
couchpotato/static/scripts/couchpotato.js

@ -24,7 +24,7 @@ var CouchPotato = new Class({
if(window.location.hash) if(window.location.hash)
History.handleInitialState(); History.handleInitialState();
else
self.openPage(window.location.pathname); self.openPage(window.location.pathname);
History.addEvent('change', self.openPage.bind(self)); History.addEvent('change', self.openPage.bind(self));

20
couchpotato/static/scripts/library/prefix_free.js

@ -24,6 +24,9 @@ var self = window.StyleFix = {
var url = link.href || link.getAttribute('data-href'), var url = link.href || link.getAttribute('data-href'),
base = url.replace(/[^\/]+$/, ''), 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, parent = link.parentNode,
xhr = new XMLHttpRequest(), xhr = new XMLHttpRequest(),
process; process;
@ -43,12 +46,23 @@ var self = window.StyleFix = {
// Convert relative URLs to absolute, if needed // Convert relative URLs to absolute, if needed
if(base) { if(base) {
css = css.replace(/url\(\s*?((?:"|')?)(.+?)\1\s*?\)/gi, function($0, quote, url) { 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 // 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 'url("' + base + url + '")';
} }
return $0;
}); });
// behavior URLs shoudn’t be converted (Issue #19) // behavior URLs shoudn’t be converted (Issue #19)

5
couchpotato/static/scripts/page/wanted.js

@ -52,7 +52,8 @@ Page.Wanted = new Class({
var start_text = self.manual_search.get('text'); var start_text = self.manual_search.get('text');
self.progress_interval = setInterval(function(){ 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){ 'onComplete': function(json){
self.search_in_progress = true; self.search_in_progress = true;
if(!json.progress){ 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() + '%)'); self.manual_search.set('text', 'Searching.. (' + (((progress.total-progress.to_go)/progress.total)*100).round() + '%)');
} }
} }
}) });
}, 1000); }, 1000);
} }

12
couchpotato/static/style/page/settings.css → couchpotato/static/style/settings.css

@ -118,7 +118,7 @@
border: 0; border: 0;
} }
.page .ctrlHolder.save_success:not(:first-child) { .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:last-child { border: none; }
.page .ctrlHolder:hover { background-color: rgba(255,255,255,0.05); } .page .ctrlHolder:hover { background-color: rgba(255,255,255,0.05); }
@ -250,7 +250,7 @@
padding: 0 4% 0 4px; padding: 0 4% 0 4px;
font-size: 13px; font-size: 13px;
width: 30%; width: 30%;
background-image: url('../../images/icon.folder.gif'); background-image: url('../images/icon.folder.gif');
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: 97% center; background-position: 97% center;
overflow: hidden; overflow: hidden;
@ -298,7 +298,7 @@
cursor: pointer; cursor: pointer;
margin: 0 !important; margin: 0 !important;
border-top: 1px solid rgba(255,255,255,0.1); 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 { .page .directory_list li:last-child {
border-bottom: 1px solid rgba(255,255,255,0.1); border-bottom: 1px solid rgba(255,255,255,0.1);
@ -484,7 +484,7 @@
margin: -9px 0 0 -16px; margin: -9px 0 0 -16px;
border-radius: 30px 30px 0 0; border-radius: 30px 30px 0 0;
cursor: pointer; 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, 270deg,
#5b9bd1 0%, #5b9bd1 0%,
#5b9bd1 100% #5b9bd1 100%
@ -558,7 +558,7 @@
} }
.page .tab_about .usenet li { .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; padding: 0 0 0 25px;
} }
@ -646,6 +646,6 @@
} }
.active .group_imdb_automation:not(.disabled) { .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; min-height: 210px;
} }

43
couchpotato/templates/_desktop.html

@ -1,43 +1,14 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<link rel="stylesheet" href="{{ url_for('web.static', filename='style/main.css') }}" type="text/css"> {% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'front', single = True) %}
<link rel="stylesheet" href="{{ url_for('web.static', filename='style/uniform.generic.css') }}" type="text/css"> <link rel="stylesheet" href="{{ url_for('web.index') }}{{ url }}" type="text/css">{% endfor %}
<link rel="stylesheet" href="{{ url_for('web.static', filename='style/uniform.css') }}" type="text/css"> {% 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 %}
<link rel="stylesheet" href="{{ url_for('web.static', filename='style/page/settings.css') }}" type="text/css">
{% for url in fireEvent('clientscript.get_scripts', as_html = True, location = 'head', single = True) %}
<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) %}
<script type="text/javascript" src="{{ url_for('web.index') }}{{ url }}"></script>{% endfor %} <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 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" /> <link href="{{ url_for('web.static', filename='images/favicon.ico') }}" rel="icon" type="image/x-icon" />

2
libs/daemon.py

@ -92,6 +92,7 @@ class Daemon():
""" """
Stop the daemon Stop the daemon
""" """
# Get the pid from the pidfile # Get the pid from the pidfile
try: try:
pf = file(self.pidfile, 'r') pf = file(self.pidfile, 'r')
@ -115,7 +116,6 @@ class Daemon():
if err.find("No such process") > 0: if err.find("No such process") > 0:
self.delpid() self.delpid()
else: else:
print str(err)
sys.exit(1) sys.exit(1)
def restart(self): def restart(self):

0
libs/minify/__init__.py

223
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()

218
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)
Loading…
Cancel
Save