461a0b3645
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'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
528 lines
11 KiB
JavaScript
528 lines
11 KiB
JavaScript
var CouchPotato = new Class({
|
|
|
|
Implements: [Events, Options],
|
|
|
|
defaults: {
|
|
page: 'home',
|
|
action: 'index',
|
|
params: {}
|
|
},
|
|
|
|
pages: [],
|
|
block: [],
|
|
|
|
setup: function(options) {
|
|
var self = this;
|
|
self.setOptions(options);
|
|
|
|
self.c = $(document.body)
|
|
|
|
self.route = new Route(self.defaults);
|
|
|
|
self.createLayout();
|
|
self.createPages();
|
|
|
|
if(window.location.hash)
|
|
History.handleInitialState();
|
|
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));
|
|
self.c.addEvent('click:relay(a[href^=http])', self.openDerefered.bind(self));
|
|
|
|
// Check if device is touchenabled
|
|
self.touch_device = 'ontouchstart' in document.documentElement;
|
|
if(self.touch_device)
|
|
self.c.addClass('touch_enabled');
|
|
|
|
},
|
|
|
|
getOption: function(name){
|
|
try {
|
|
return this.options[name];
|
|
}
|
|
catch(e){
|
|
return null
|
|
}
|
|
},
|
|
|
|
pushState: function(e){
|
|
var self = this;
|
|
if((!e.meta && Browser.Platform.mac) || (!e.control && !Browser.Platform.mac)){
|
|
(e).preventDefault();
|
|
var url = e.target.get('href');
|
|
if(History.getPath() != url)
|
|
History.push(url);
|
|
}
|
|
},
|
|
|
|
createLayout: function(){
|
|
var self = this;
|
|
|
|
self.block.header = new Block();
|
|
|
|
self.c.adopt(
|
|
$(self.block.header).addClass('header').adopt(
|
|
new Element('div').adopt(
|
|
self.block.navigation = new Block.Navigation(self, {}),
|
|
self.block.search = new Block.Search(self, {}),
|
|
self.block.more = new Block.Menu(self, {'button_class': 'icon2.cog'})
|
|
)
|
|
),
|
|
self.content = new Element('div.content'),
|
|
self.block.footer = new Block.Footer(self, {})
|
|
);
|
|
|
|
var setting_links = [
|
|
new Element('a', {
|
|
'text': 'About CouchPotato',
|
|
'href': App.createUrl('settings/about')
|
|
}),
|
|
new Element('a', {
|
|
'text': 'Check for Updates',
|
|
'events': {
|
|
'click': self.checkForUpdate.bind(self, null)
|
|
}
|
|
}),
|
|
new Element('span.separator'),
|
|
new Element('a', {
|
|
'text': 'Settings',
|
|
'href': App.createUrl('settings/general')
|
|
}),
|
|
new Element('a', {
|
|
'text': 'Logs',
|
|
'href': App.createUrl('log')
|
|
}),
|
|
new Element('span.separator'),
|
|
new Element('a', {
|
|
'text': 'Restart',
|
|
'events': {
|
|
'click': self.restartQA.bind(self)
|
|
}
|
|
}),
|
|
new Element('a', {
|
|
'text': 'Shutdown',
|
|
'events': {
|
|
'click': self.shutdownQA.bind(self)
|
|
}
|
|
})
|
|
]
|
|
|
|
setting_links.each(function(a){
|
|
self.block.more.addLink(a)
|
|
})
|
|
|
|
|
|
new ScrollSpy({
|
|
min: 10,
|
|
onLeave: function(){
|
|
$(self.block.header).removeClass('with_shadow')
|
|
},
|
|
onEnter: function(){
|
|
$(self.block.header).addClass('with_shadow')
|
|
}
|
|
})
|
|
},
|
|
|
|
createPages: function(){
|
|
var self = this;
|
|
|
|
Object.each(Page, function(page_class, class_name){
|
|
pg = new Page[class_name](self, {});
|
|
self.pages[class_name] = pg;
|
|
|
|
$(pg).inject(self.content);
|
|
});
|
|
|
|
self.fireEvent('load');
|
|
|
|
},
|
|
|
|
openPage: function(url) {
|
|
var self = this;
|
|
|
|
self.route.parse();
|
|
var page_name = self.route.getPage().capitalize();
|
|
var action = self.route.getAction();
|
|
var params = self.route.getParams();
|
|
|
|
var current_url = self.route.getCurrentUrl();
|
|
if(current_url == self.current_url)
|
|
return;
|
|
|
|
if(self.current_page)
|
|
self.current_page.hide()
|
|
|
|
try {
|
|
var page = self.pages[page_name] || self.pages.Home;
|
|
page.open(action, params, current_url);
|
|
page.show();
|
|
}
|
|
catch(e){
|
|
console.error("Can't open page:" + url, e)
|
|
}
|
|
|
|
self.current_page = page;
|
|
self.current_url = current_url;
|
|
|
|
},
|
|
|
|
getBlock: function(block_name){
|
|
return this.block[block_name]
|
|
},
|
|
|
|
getPage: function(name){
|
|
return this.pages[name]
|
|
},
|
|
|
|
shutdown: function(){
|
|
var self = this;
|
|
|
|
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(e){
|
|
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();
|
|
q.close.delay(100, q);
|
|
}
|
|
}
|
|
}, {
|
|
'text': 'No, nevah!',
|
|
'cancel': true
|
|
}]);
|
|
},
|
|
|
|
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);
|
|
q.close.delay(100, q);
|
|
}
|
|
}
|
|
}, {
|
|
'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;
|
|
|
|
(function(){
|
|
|
|
Api.request('app.available', {
|
|
'onFailure': function(){
|
|
self.checkAvailable.delay(1000, self, [delay, onAvailable]);
|
|
self.fireEvent('unload');
|
|
},
|
|
'onSuccess': function(){
|
|
if(onAvailable)
|
|
onAvailable()
|
|
self.unBlockPage();
|
|
self.fireEvent('reload');
|
|
}
|
|
});
|
|
|
|
}).delay(delay || 0)
|
|
},
|
|
|
|
blockPage: function(message, title){
|
|
var self = this;
|
|
|
|
self.unBlockPage();
|
|
|
|
var body = $(document.body);
|
|
self.mask = new Element('div.mask').adopt(
|
|
new Element('div').adopt(
|
|
new Element('h1', {'text': title || 'Unavailable'}),
|
|
new Element('div', {'text': message || 'Something must have crashed.. check the logs ;)'})
|
|
)
|
|
).fade('hide').inject(document.body).fade('in');
|
|
|
|
createSpinner(self.mask, {
|
|
'top': -50
|
|
});
|
|
},
|
|
|
|
unBlockPage: function(){
|
|
var self = this;
|
|
if(self.mask)
|
|
self.mask.get('tween').start('opacity', 0).chain(function(){
|
|
this.element.destroy()
|
|
});
|
|
},
|
|
|
|
createUrl: function(action, params){
|
|
return this.options.base_url + (action ? action+'/' : '') + (params ? '?'+Object.toQueryString(params) : '')
|
|
},
|
|
|
|
openDerefered: function(e, el){
|
|
(e).stop();
|
|
|
|
var url = 'http://www.dereferer.org/?' + el.get('href');
|
|
|
|
if(el.get('target') == '_blank' || (e.meta && Browser.Platform.mac) || (e.control && !Browser.Platform.mac))
|
|
window.open(url);
|
|
else
|
|
window.location = url;
|
|
},
|
|
|
|
createUserscriptButtons: function(){
|
|
|
|
var host_url = window.location.protocol + '//' + window.location.host;
|
|
|
|
return new Element('div.group_userscript').adopt(
|
|
new Element('a.userscript.button', {
|
|
'text': 'Install userscript',
|
|
'href': Api.createUrl('userscript.get')+randomString()+'/couchpotato.user.js',
|
|
'target': '_blank'
|
|
}),
|
|
new Element('span.or[text=or]'),
|
|
new Element('span.bookmarklet').adopt(
|
|
new Element('a.button.orange', {
|
|
'text': '+CouchPotato',
|
|
'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)})());",
|
|
'target': '',
|
|
'events': {
|
|
'click': function(e){
|
|
(e).stop()
|
|
alert('Drag it to your bookmark ;)')
|
|
}
|
|
}
|
|
}),
|
|
new Element('span', {
|
|
'text': '⇽ Drag this to your bookmarks'
|
|
})
|
|
)
|
|
);
|
|
}
|
|
|
|
});
|
|
window.App = new CouchPotato();
|
|
|
|
var Route = new Class({
|
|
|
|
defaults: {},
|
|
page: '',
|
|
action: 'index',
|
|
params: {},
|
|
|
|
initialize: function(defaults){
|
|
var self = this
|
|
self.defaults = defaults
|
|
},
|
|
|
|
parse: function(){
|
|
var self = this;
|
|
|
|
var rep = function(pa){
|
|
return pa.replace(Api.getOption('url'), '/').replace(App.getOption('base_url'), '/')
|
|
}
|
|
|
|
var path = rep(History.getPath())
|
|
if(path == '/' && location.hash){
|
|
path = rep(location.hash.replace('#', '/'))
|
|
}
|
|
self.current = path.replace(/^\/+|\/+$/g, '')
|
|
var url = self.current.split('/')
|
|
|
|
self.page = (url.length > 0) ? url.shift() : self.defaults.page
|
|
self.action = (url.length > 0) ? url.shift() : self.defaults.action
|
|
|
|
self.params = Object.merge({}, self.defaults.params);
|
|
if(url.length > 1){
|
|
var key
|
|
url.each(function(el, nr){
|
|
if(nr%2 == 0)
|
|
key = el
|
|
else if(key) {
|
|
self.params[key] = el
|
|
key = null
|
|
}
|
|
})
|
|
}
|
|
else if(url.length == 1){
|
|
self.params[url] = true;
|
|
}
|
|
|
|
return self
|
|
},
|
|
|
|
getPage: function(){
|
|
return this.page
|
|
},
|
|
|
|
getAction: function(){
|
|
return this.action
|
|
},
|
|
|
|
getParams: function(){
|
|
return this.params
|
|
},
|
|
|
|
getCurrentUrl: function(){
|
|
return this.current
|
|
},
|
|
|
|
get: function(param){
|
|
return this.params[param]
|
|
}
|
|
|
|
});
|
|
|
|
var p = function(){
|
|
if(typeof(console) !== 'undefined' && console != null)
|
|
console.log(arguments)
|
|
};
|
|
|
|
|
|
(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;
|
|
}
|
|
}
|
|
};
|
|
})();
|
|
|
|
|
|
function randomString(length, extra) {
|
|
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz" + (extra ? '-._!@#$%^&*()+=' : '');
|
|
var stringLength = length || 8;
|
|
var randomString = '';
|
|
for (var i = 0; i < stringLength; i++) {
|
|
var rnum = Math.floor(Math.random() * chars.length);
|
|
randomString += chars.charAt(rnum);
|
|
}
|
|
return randomString;
|
|
}
|
|
|
|
(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;
|
|
path.each(function(key) { ptr = ptr[key] });
|
|
return ptr;
|
|
};
|
|
|
|
var comparer = function(a, b) {
|
|
for (var i = 0, l = keyPaths.length; i < l; i++) {
|
|
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.sort(comparer);
|
|
}
|
|
});
|
|
|
|
})();
|
|
|
|
var createSpinner = function(target, options){
|
|
var opts = Object.merge({
|
|
lines: 12,
|
|
length: 5,
|
|
width: 4,
|
|
radius: 9,
|
|
color: '#fff',
|
|
speed: 1.9,
|
|
trail: 53,
|
|
shadow: false,
|
|
hwaccel: true,
|
|
className: 'spinner',
|
|
zIndex: 2e9,
|
|
top: 'auto',
|
|
left: 'auto'
|
|
}, options);
|
|
|
|
return new Spinner(opts).spin(target);
|
|
} |