You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

681 lines
15 KiB

11 years ago
var CouchPotato = new Class({
14 years ago
Implements: [Events, Options],
14 years ago
defaults: {
12 years ago
page: 'home',
14 years ago
action: 'index',
params: {}
},
pages: [],
block: [],
14 years ago
initialize: function(){
var self = this;
self.global_events = {};
},
setup: function(options) {
14 years ago
var self = this;
self.setOptions(options);
self.c = $(document.body);
14 years ago
self.createLayout();
self.createPages();
if(window.location.hash)
History.handleInitialState();
else
self.openPage(window.location.pathname);
14 years ago
History.addEvent('change', self.openPage.bind(self));
self.c.addEvent('click:relay(.header a, .navigation a, .movie_details a, .list_list .movie)', self.ripple.bind(self));
self.c.addEvent('click:relay(a[href^=/]:not([target]))', self.pushState.bind(self));
self.c.addEvent('click:relay(a[href^=http])', self.openDerefered.bind(self));
// Check if device is touchenabled
self.touch_device = 'ontouchstart' in window || navigator.msMaxTouchPoints;
if(self.touch_device){
self.c.addClass('touch_enabled');
FastClick.attach(document.body);
}
window.addEvent('resize', self.resize.bind(self));
self.resize();
//self.checkCache();
10 years ago
},
checkCache: function(){
window.addEventListener('load', function() {
window.applicationCache.addEventListener('updateready', function(e) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
window.applicationCache.swapCache();
window.location.reload();
}
}, false);
}, false);
},
resize: function(){
var self = this;
self.mobile_screen = Math.max(document.documentElement.clientWidth, window.innerWidth || 0) <= 480;
self.c[self.mobile_screen ? 'addClass' : 'removeClass']('mobile');
},
14 years ago
10 years ago
ripple: function(e, el){
var self = this,
button = el.getCoordinates(),
x = e.page.x - button.left,
y = e.page.y - button.top,
ripple = new Element('div.ripple', {
'styles': {
'left': x,
'top': y
}
});
ripple.inject(el);
10 years ago
requestTimeout(function(){ ripple.addClass('animate'); }, 0);
requestTimeout(function(){ ripple.dispose(); }, 2100);
10 years ago
},
getOption: function(name){
14 years ago
try {
return this.options[name];
}
catch(e){
11 years ago
return null;
14 years ago
}
},
pushState: function(e, el){
var self = this;
if((!e.meta && App.isMac()) || (!e.control && !App.isMac())){
(e).preventDefault();
var url = el.get('href');
// Middle click
if(e.event && e.event.button === 1)
window.open(url);
else if(History.getPath() != url)
History.push(url);
}
self.fireEvent('history.push');
14 years ago
},
isMac: function(){
11 years ago
return Browser.platform == 'mac';
},
14 years ago
createLayout: function(){
var self = this;
// TODO : sorry, it's a crutch... Need to move self.hide_update initialization to appropriate place..
// WebUI Feature:
self.hide_update = !! App.options && App.options.webui_feature && App.options.webui_feature.hide_menuitem_update;
self.block.header = new BlockBase();
14 years ago
14 years ago
self.c.adopt(
14 years ago
$(self.block.header).addClass('header').adopt(
11 years ago
self.block.navigation = new BlockHeader(self, {}),
11 years ago
self.block.search = new BlockSearch(self, {}),
10 years ago
self.support = new Element('a.donate.icon-donate', {
'href': 'https://couchpota.to/support/',
'target': '_blank'
}).grab(
new Element('span', {
'text': 'Donate'
})
),
11 years ago
self.block.more = new BlockMenu(self, {'button_class': 'icon-settings'})
14 years ago
),
10 years ago
new Element('div.corner_background'),
11 years ago
self.content = new Element('div.content').adopt(
self.pages_container = new Element('div.pages'),
11 years ago
self.block.footer = new BlockFooter(self, {})
)
);
12 years ago
var setting_links = [
new Element('a', {
'text': 'About CouchPotato',
'href': App.createUrl('settings/about')
}),
new Element('a', {
'text': 'Settings',
'href': App.createUrl('settings/general')
}),
new Element('a', {
'text': 'Logs',
'href': App.createUrl('log')
}),
new Element('a', {
'text': 'Restart',
'events': {
'click': self.restartQA.bind(self)
}
}),
new Element('a', {
'text': 'Shutdown',
'events': {
'click': self.shutdownQA.bind(self)
}
})
];
if (!self.hide_update){
setting_links.splice(1, 0, new Element('a', {
'text': 'Check for Updates',
'events': {
'click': self.checkForUpdate.bind(self, null)
}
}));
}
12 years ago
setting_links.each(function(a){
11 years ago
self.block.more.addLink(a);
});
10 years ago
// Set theme
self.addEvent('setting.save.core.dark_theme', function(enabled){
document.html[enabled ? 'addClass' : 'removeClass']('dark');
});
14 years ago
},
createPages: function(){
14 years ago
var self = this;
var pages = [];
Object.each(Page, function(page_class, class_name){
var pg = new Page[class_name](self, {
'level': 1
});
self.pages[class_name] = pg;
14 years ago
pages.include({
'order': pg.order,
'name': class_name,
'class': pg
});
});
pages.stableSort(self.sortPageByOrder).each(function(page){
page['class'].load();
self.fireEvent('load'+page.name);
$(page['class']).inject(self.getPageContainer());
});
self.fireEvent('load');
},
14 years ago
sortPageByOrder: function(a, b){
11 years ago
return (a.order || 100) - (b.order || 100);
},
openPage: function(url) {
11 years ago
var self = this,
route = new Route(self.defaults);
route.parse(rep(History.getPath()));
11 years ago
var page_name = route.getPage().capitalize(),
action = route.getAction(),
params = route.getParams(),
current_url = route.getCurrentUrl(),
11 years ago
page;
14 years ago
if(current_url == self.current_url)
return;
if(self.current_page)
self.current_page.hide();
try {
11 years ago
page = self.pages[page_name] || self.pages.Home;
page.open(action, params, current_url);
14 years ago
page.show();
}
catch(e){
11 years ago
console.error("Can't open page:" + url, e);
}
14 years ago
self.current_page = page;
14 years ago
self.current_url = current_url;
},
getBlock: function(block_name){
11 years ago
return this.block[block_name];
},
getPage: function(name){
11 years ago
return this.pages[name];
},
getPageContainer: function(){
return this.pages_container;
},
shutdown: function(){
var self = this;
Seeding support Design intent: - Option to turn seeding support on or off - After torrent downloading is complete the seeding phase starts, seeding parameters can be set per torrent provide (0 disables them) - When the seeding phase starts the checkSnatched function renames all files if (sym)linking/copying is used. The movie is set to done (!), the release to seeding status. - Note that Direct symlink functionality is removed as the original file needs to end up in the movies store and not the downloader store (if the downloader cleans up his files, the original is deleted and the symlinks are useless) - checkSnatched waits until downloader sets the download to completed (met the seeding parameters) - When completed, checkSnatched intiates the renamer if move is used, or if linking is used asks the downloader to remove the torrent and clean-up it&#39;s files and sets the release to downloaded - Updated some of the .ignore file behavior to allow the downloader to remove its files Known items/issues: - only implemented for uTorrent and Transmission - text in downloader settings is too long and messes up the layout... To do (after this PR): - implement for other torrent downloaders - complete download removal for NZBs (remove from history in sabNZBd) - failed download management for torrents (no seeders, takes too long, etc.) - unrar support Updates: - Added transmission support - Simplified uTorrent - Added checkSnatched to renamer to make sure the poller is always first - Updated default values and removed advanced option tag for providers - Updated the tagger to allow removing of ignore tags and tagging when the group is not known - Added tagging of downloading torrents - fixed subtitles being leftover after seeding
12 years ago
self.blockPage('You have shutdown. This is what is supposed to happen ;)');
Api.request('app.shutdown', {
'onComplete': self.blockPage.bind(self)
});
self.checkAvailable(1000);
},
shutdownQA: function(){
var self = this;
var q = new Question('Are you sure you want to shutdown CouchPotato?', '', [{
'text': 'Shutdown',
'class': 'shutdown red',
'events': {
'click': function(e){
(e).preventDefault();
self.shutdown();
10 years ago
requestTimeout(q.close.bind(q), 100);
}
}
}, {
'text': 'No, nevah!',
'cancel': true
}]);
},
13 years ago
restart: function(message, title){
var self = this;
self.blockPage(message || 'Restarting... please wait. If this takes too long, something must have gone wrong.', title);
Api.request('app.restart');
self.checkAvailable(1000);
},
restartQA: function(e, message, title){
var self = this;
var q = new Question('Are you sure you want to restart CouchPotato?', '', [{
'text': 'Restart',
'class': 'restart orange',
'events': {
'click': function(e){
(e).preventDefault();
self.restart(message, title);
10 years ago
requestTimeout(q.close.bind(q), 100);
}
}
}, {
'text': 'No, nevah!',
'cancel': true
}]);
},
checkForUpdate: function(onComplete){
var self = this;
Updater.check(onComplete);
self.blockPage('Please wait. If this takes too long, something must have gone wrong.', 'Checking for updates');
self.checkAvailable(3000);
},
checkAvailable: function(delay, onAvailable){
var self = this;
10 years ago
requestTimeout(function(){
var onFailure = function(){
10 years ago
requestTimeout(function(){
self.checkAvailable(delay, onAvailable);
}, 1000);
self.fireEvent('unload');
11 years ago
};
var request = Api.request('app.available', {
'timeout': 2000,
'onTimeout': function(){
request.cancel();
onFailure();
},
'onFailure': onFailure,
'onSuccess': function(){
if(onAvailable)
onAvailable();
self.unBlockPage();
self.fireEvent('reload');
}
});
10 years ago
}, delay || 0);
},
blockPage: function(message, title){
var self = this;
self.unBlockPage();
self.mask = new Element('div.mask.with_message').adopt(
new Element('div.message').adopt(
new Element('h1', {'text': title || 'Unavailable'}),
new Element('div', {'text': message || 'Something must have crashed.. check the logs ;)'})
)
).inject(document.body);
createSpinner(self.mask);
10 years ago
requestTimeout(function(){
self.mask.addClass('show');
}, 10);
},
unBlockPage: function(){
var self = this;
if(self.mask)
self.mask.get('tween').start('opacity', 0).chain(function(){
11 years ago
this.element.destroy();
});
14 years ago
},
createUrl: function(action, params){
11 years ago
return this.options.base_url + (action ? action+'/' : '') + (params ? '?'+Object.toQueryString(params) : '');
},
openDerefered: function(e, el){
var self = this;
(e).stop();
var url = el.get('href');
if(self.getOption('dereferer')){
url = self.getOption('dereferer') + el.get('href');
}
if(el.get('target') == '_blank' || (e.meta && self.isMac()) || (e.control && !self.isMac()))
window.open(url);
else
window.location = url;
13 years ago
},
createUserscriptButtons: function(){
var host_url = window.location.protocol + '//' + window.location.host;
return new Element('div.group_userscript').adopt(
new Element('div').adopt(
new Element('a.userscript.button', {
'text': 'Install extension',
'href': 'https://couchpota.to/extension/',
'target': '_blank'
13 years ago
}),
new Element('span.or[text=or]'),
new Element('span.bookmarklet').adopt(
new Element('a.button', {
'text': '+CouchPotato',
/* jshint ignore:start */
'href': "javascript:void((function(){var e=document.createElement('script');e.setAttribute('type','text/javascript');e.setAttribute('charset','UTF-8');e.setAttribute('src','" +
host_url + Api.createUrl('userscript.bookmark') +
"?host="+ encodeURI(host_url + Api.createUrl('userscript.get')+randomString()+'/') +
"&r='+Math.random()*99999999);document.body.appendChild(e)})());",
/* jshint ignore:end */
'target': '',
'events': {
'click': function(e){
(e).stop();
alert('Drag it to your bookmark ;)');
}
}
}),
new Element('span', {
'text': '⇽ Drag this to your bookmarks'
})
)
),
new Element('img', {
'src': 'https://couchpota.to/media/images/userscript.gif'
})
13 years ago
);
},
/*
* Global events
*/
on: function(name, handle){
var self = this;
if(!self.global_events[name])
self.global_events[name] = [];
self.global_events[name].push(handle);
},
trigger: function(name, args, on_complete){
var self = this;
if(!self.global_events[name]){ return; }
if(!on_complete && typeOf(args) == 'function'){
on_complete = args;
args = [];
}
// Create parallel callback
11 years ago
self.global_events[name].each(function(handle){
10 years ago
requestTimeout(function(){
var results = handle.apply(handle, args || []);
11 years ago
if(on_complete)
on_complete(results);
}, 0);
});
},
off: function(name, handle){
var self = this;
if(!self.global_events[name]) return;
// Remove single
if(handle){
self.global_events[name] = self.global_events[name].erase(handle);
}
// Reset full event
else {
self.global_events[name] = [];
}
}
});
window.App = new CouchPotato();
14 years ago
var Route = new Class({
11 years ago
defaults: null,
14 years ago
page: '',
action: 'index',
params: {},
initialize: function(defaults){
var self = this;
11 years ago
self.defaults = defaults || {};
},
11 years ago
parse: function(path){
14 years ago
var self = this;
12 years ago
if(path == '/' && location.hash){
11 years ago
path = rep(location.hash.replace('#', '/'));
12 years ago
}
self.current = path.replace(/^\/+|\/+$/g, '');
var url = self.current.split('/');
self.page = (url.length > 0) ? url.shift() : self.defaults.page;
11 years ago
self.action = (url.length > 0) ? url.join('/') : self.defaults.action;
self.params = Object.merge({}, self.defaults.params);
if(url.length > 1){
var key;
url.each(function(el, nr){
11 years ago
if(nr%2 === 0)
key = el;
else if(key) {
self.params[key] = el;
11 years ago
key = null;
}
11 years ago
});
}
else if(url.length == 1){
self.params[url] = true;
}
14 years ago
11 years ago
return self;
14 years ago
},
getPage: function(){
11 years ago
return this.page;
14 years ago
},
getAction: function(){
11 years ago
return this.action;
14 years ago
},
getParams: function(){
11 years ago
return this.params;
14 years ago
},
getCurrentUrl: function(){
11 years ago
return this.current;
},
14 years ago
get: function(param){
11 years ago
return this.params[param];
14 years ago
}
});
var p = function(){
11 years ago
if(typeof(console) !== 'undefined' && console !== null)
console.log(arguments);
};
14 years ago
(function(){
var events;
var check = function(e) {
var target = $(e.target);
var parents = target.getParents();
events.each(function(item) {
var element = item.element;
if (element != target && !parents.contains(element))
item.fn.call(element, e);
});
};
Element.Events.outerClick = {
onAdd : function(fn) {
if (!events) {
document.addEvent('click', check);
events = [];
}
events.push( {
element : this,
fn : fn
});
},
onRemove : function(fn) {
events = events.filter(function(item) {
return item.element != this || item.fn != fn;
}, this);
if (!events.length) {
document.removeEvent('click', check);
events = null;
}
}
};
})();
14 years ago
function randomString(length, extra) {
11 years ago
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz" + (extra ? '-._!@#$%^&*()+=' : ''),
string_length = length || 8,
random_string = '';
for (var i = 0; i < string_length; i++) {
14 years ago
var rnum = Math.floor(Math.random() * chars.length);
11 years ago
random_string += chars.charAt(rnum);
14 years ago
}
11 years ago
return random_string;
14 years ago
}
(function(){
var keyPaths = [];
var saveKeyPath = function(path) {
keyPaths.push({
sign: (path[0] === '+' || path[0] === '-')? parseInt(path.shift()+1) : 1,
path: path
});
};
var valueOf = function(object, path) {
var ptr = object;
11 years ago
path.each(function(key) { ptr = ptr[key]; });
return ptr;
};
var comparer = function(a, b) {
for (var i = 0, l = keyPaths.length; i < l; i++) {
var aVal = valueOf(a, keyPaths[i].path),
bVal = valueOf(b, keyPaths[i].path);
if (aVal > bVal) return keyPaths[i].sign;
if (aVal < bVal) return -keyPaths[i].sign;
}
return 0;
};
Array.implement({
sortBy: function(){
keyPaths.empty();
Array.each(arguments, function(argument) {
switch (typeOf(argument)) {
case "array": saveKeyPath(argument); break;
case "string": saveKeyPath(argument.match(/[+-]|[^.]+/g)); break;
}
});
return this.stableSort(comparer);
}
});
14 years ago
})();
11 years ago
var createSpinner = function(container){
var spinner = new Element('div.spinner');
container.grab(spinner);
return spinner;
};
11 years ago
var rep = function (pa) {
return pa.replace(Api.getOption('url'), '/').replace(App.getOption('base_url'), '/');
};