diff --git a/couchpotato/__init__.py b/couchpotato/__init__.py
index 04756fa..b8aa3ab 100644
--- a/couchpotato/__init__.py
+++ b/couchpotato/__init__.py
@@ -22,7 +22,7 @@ class BaseHandler(RequestHandler):
username = Env.setting('username')
password = Env.setting('password')
- if username or password:
+ if username and password:
return self.get_secure_cookie('user')
else: # Login when no username or password are set
return True
diff --git a/couchpotato/api.py b/couchpotato/api.py
index a9f449b..091de42 100644
--- a/couchpotato/api.py
+++ b/couchpotato/api.py
@@ -44,13 +44,15 @@ class NonBlockHandler(RequestHandler):
def onNewMessage(self, response):
if self.request.connection.stream.closed():
+ self.on_connection_close()
return
try:
self.finish(response)
except:
- log.error('Failed doing nonblock request: %s', (traceback.format_exc()))
- self.finish({'success': False, 'error': 'Failed returning results'})
+ log.debug('Failed doing nonblock request, probably already closed: %s', (traceback.format_exc()))
+ try: self.finish({'success': False, 'error': 'Failed returning results'})
+ except: pass
def on_connection_close(self):
diff --git a/couchpotato/core/_base/_core/main.py b/couchpotato/core/_base/_core/main.py
index 9647d95..803ac5a 100644
--- a/couchpotato/core/_base/_core/main.py
+++ b/couchpotato/core/_base/_core/main.py
@@ -56,7 +56,7 @@ class Core(Plugin):
self.signalHandler()
def md5Password(self, value):
- return md5(value.encode(Env.get('encoding'))) if value else ''
+ return md5(value) if value else ''
def checkApikey(self, value):
return value if value and len(value) > 3 else uuid4().hex
diff --git a/couchpotato/core/downloaders/utorrent/main.py b/couchpotato/core/downloaders/utorrent/main.py
index d933007..ce82c8c 100644
--- a/couchpotato/core/downloaders/utorrent/main.py
+++ b/couchpotato/core/downloaders/utorrent/main.py
@@ -77,7 +77,7 @@ class uTorrent(Downloader):
else:
info = bdecode(filedata)["info"]
torrent_hash = sha1(benc(info)).hexdigest().upper()
- torrent_filename = self.createFileName(data, filedata, movie)
+ torrent_filename = self.createFileName(data, filedata, movie)
if data.get('seed_ratio'):
torrent_params['seed_override'] = 1
@@ -93,7 +93,7 @@ class uTorrent(Downloader):
# Send request to uTorrent
if data.get('protocol') == 'torrent_magnet':
- self.utorrent_api.add_torrent_uri(data.get('url'))
+ self.utorrent_api.add_torrent_uri(torrent_filename, data.get('url'))
else:
self.utorrent_api.add_torrent_file(torrent_filename, filedata)
@@ -102,6 +102,39 @@ class uTorrent(Downloader):
if self.conf('paused', default = 0):
self.utorrent_api.pause_torrent(torrent_hash)
+ count = 0
+ while True:
+
+ count += 1
+ # Check if torrent is saved in subfolder of torrent name
+ data = self.utorrent_api.get_files(torrent_hash)
+
+ torrent_files = json.loads(data)
+ if torrent_files.get('error'):
+ log.error('Error getting data from uTorrent: %s', torrent_files.get('error'))
+ return False
+
+ if (torrent_files.get('files') and len(torrent_files['files'][1]) > 0) or count > 60:
+ break
+
+ time.sleep(1)
+
+ # Torrent has only one file, so uTorrent wont create a folder for it
+ if len(torrent_files['files'][1]) == 1:
+ # Remove torrent and try again
+ self.utorrent_api.remove_torrent(torrent_hash, remove_data = True)
+
+ # Send request to uTorrent
+ if data.get('protocol') == 'torrent_magnet':
+ self.utorrent_api.add_torrent_uri(torrent_filename, data.get('url'), add_folder = True)
+ else:
+ self.utorrent_api.add_torrent_file(torrent_filename, filedata, add_folder = True)
+
+ # Change settings of added torrent
+ self.utorrent_api.set_torrent(torrent_hash, torrent_params)
+ if self.conf('paused', default = 0):
+ self.utorrent_api.pause_torrent(torrent_hash)
+
return self.downloadReturnId(torrent_hash)
def getAllDownloadStatus(self):
@@ -224,12 +257,16 @@ class uTorrentAPI(object):
token = re.findall("
(.*?)", request.read())[0]
return token
- def add_torrent_uri(self, torrent):
+ def add_torrent_uri(self, filename, torrent, add_folder = False):
action = "action=add-url&s=%s" % urllib.quote(torrent)
+ if add_folder:
+ action += "&path=%s" % urllib.quote(filename)
return self._request(action)
- def add_torrent_file(self, filename, filedata):
+ def add_torrent_file(self, filename, filedata, add_folder = False):
action = "action=add-file"
+ if add_folder:
+ action += "&path=%s" % urllib.quote(filename)
return self._request(action, {"torrent_file": (ss(filename), filedata)})
def set_torrent(self, hash, params):
@@ -291,3 +328,7 @@ class uTorrentAPI(object):
action = 'action=setsetting' + ''.join(['&s=%s&v=%s' % (key, value) for (key, value) in settings_dict.items()])
return self._request(action)
+
+ def get_files(self, hash):
+ action = "action=getfiles&hash=%s" % hash
+ return self._request(action)
diff --git a/couchpotato/core/helpers/encoding.py b/couchpotato/core/helpers/encoding.py
index 6e86444..5fa2e2a 100644
--- a/couchpotato/core/helpers/encoding.py
+++ b/couchpotato/core/helpers/encoding.py
@@ -38,8 +38,14 @@ def toUnicode(original, *args):
return toUnicode(ascii_text)
def ss(original, *args):
- from couchpotato.environment import Env
- return toUnicode(original, *args).encode(Env.get('encoding'))
+
+ u_original = toUnicode(original, *args)
+ try:
+ from couchpotato.environment import Env
+ return u_original.encode(Env.get('encoding'))
+ except Exception, e:
+ log.debug('Failed ss encoding char, force UTF8: %s', e)
+ return u_original.encode('UTF-8')
def ek(original, *args):
if isinstance(original, (str, unicode)):
diff --git a/couchpotato/core/helpers/variable.py b/couchpotato/core/helpers/variable.py
index 8f393d0..d93c941 100644
--- a/couchpotato/core/helpers/variable.py
+++ b/couchpotato/core/helpers/variable.py
@@ -1,4 +1,4 @@
-from couchpotato.core.helpers.encoding import simplifyString, toSafeString
+from couchpotato.core.helpers.encoding import simplifyString, toSafeString, ss
from couchpotato.core.logger import CPLog
import hashlib
import os.path
@@ -101,7 +101,7 @@ def flattenList(l):
return l
def md5(text):
- return hashlib.md5(text).hexdigest()
+ return hashlib.md5(ss(text)).hexdigest()
def sha1(text):
return hashlib.sha1(text).hexdigest()
@@ -123,7 +123,12 @@ def cleanHost(host):
return host
-def getImdb(txt, check_inside = True, multiple = False):
+def getImdb(txt, check_inside = False, multiple = False):
+
+ if not check_inside:
+ txt = simplifyString(txt)
+ else:
+ txt = ss(txt)
if check_inside and os.path.isfile(txt):
output = open(txt, 'r')
@@ -168,8 +173,11 @@ def getTitle(library_dict):
if title.default:
return title.title
except:
- log.error('Could not get title for %s', library_dict.identifier)
- return None
+ try:
+ return library_dict['info']['titles'][0]
+ except:
+ log.error('Could not get title for %s', library_dict.identifier)
+ return None
log.error('Could not get title for %s', library_dict['identifier'])
return None
diff --git a/couchpotato/core/media/movie/_base/main.py b/couchpotato/core/media/movie/_base/main.py
index 3e84cae..e5aecc2 100644
--- a/couchpotato/core/media/movie/_base/main.py
+++ b/couchpotato/core/media/movie/_base/main.py
@@ -469,7 +469,7 @@ class MovieBase(MovieTypeBase):
fireEvent('release.delete', release.id, single = True)
m.profile_id = params.get('profile_id', default_profile.get('id'))
- m.category_id = tryInt(cat_id) if cat_id is not None and tryInt(cat_id) > 0 else None
+ m.category_id = tryInt(cat_id) if cat_id is not None and tryInt(cat_id) > 0 else (m.category_id or None)
else:
log.debug('Movie already exists, not updating: %s', params)
added = False
diff --git a/couchpotato/core/media/movie/_base/static/movie.actions.js b/couchpotato/core/media/movie/_base/static/movie.actions.js
index 7d8c37f..e9f6141 100644
--- a/couchpotato/core/media/movie/_base/static/movie.actions.js
+++ b/couchpotato/core/media/movie/_base/static/movie.actions.js
@@ -18,11 +18,13 @@ var MovieAction = new Class({
create: function(){},
disable: function(){
- this.el.addClass('disable')
+ if(this.el)
+ this.el.addClass('disable')
},
enable: function(){
- this.el.removeClass('disable')
+ if(this.el)
+ this.el.removeClass('disable')
},
getTitle: function(){
@@ -252,35 +254,38 @@ MA.Release = new Class({
});
if(self.last_release)
- self.release_container.getElement('#release_'+self.last_release.id).addClass('last_release');
+ self.release_container.getElements('#release_'+self.last_release.id).addClass('last_release');
if(self.next_release)
- self.release_container.getElement('#release_'+self.next_release.id).addClass('next_release');
+ self.release_container.getElements('#release_'+self.next_release.id).addClass('next_release');
if(self.next_release || (self.last_release && ['ignored', 'failed'].indexOf(self.last_release.status.identifier) === false)){
self.trynext_container = new Element('div.buttons.try_container').inject(self.release_container, 'top');
+
+ var nr = self.next_release,
+ lr = self.last_release;
self.trynext_container.adopt(
new Element('span.or', {
'text': 'This movie is snatched, if anything went wrong, download'
}),
- self.last_release ? new Element('a.button.orange', {
+ lr ? new Element('a.button.orange', {
'text': 'the same release again',
'events': {
'click': function(){
- self.download(self.last_release);
+ self.download(lr);
}
}
}) : null,
- self.next_release && self.last_release ? new Element('span.or', {
+ nr && lr ? new Element('span.or', {
'text': ','
}) : null,
- self.next_release ? [new Element('a.button.green', {
- 'text': self.last_release ? 'another release' : 'the best release',
+ nr ? [new Element('a.button.green', {
+ 'text': lr ? 'another release' : 'the best release',
'events': {
'click': function(){
- self.download(self.next_release);
+ self.download(nr);
}
}
}),
@@ -362,19 +367,25 @@ MA.Release = new Class({
var release_el = self.release_container.getElement('#release_'+release.id),
icon = release_el.getElement('.download.icon2');
- icon.addClass('icon spinner').removeClass('download');
+ if(icon)
+ icon.addClass('icon spinner').removeClass('download');
Api.request('release.download', {
'data': {
'id': release.id
},
'onComplete': function(json){
- icon.removeClass('icon spinner');
+ if(icon)
+ icon.removeClass('icon spinner');
- if(json.success)
- icon.addClass('completed');
+ if(json.success){
+ if(icon)
+ icon.addClass('completed');
+ release_el.getElement('.release_status').set('text', 'snatched');
+ }
else
- icon.addClass('attention').set('title', 'Something went wrong when downloading, please check logs.');
+ if(icon)
+ icon.addClass('attention').set('title', 'Something went wrong when downloading, please check logs.');
}
});
},
@@ -388,11 +399,11 @@ MA.Release = new Class({
},
'onComplete': function(){
var el = release.el;
- if(el.hasClass('failed') || el.hasClass('ignored')){
+ if(el && (el.hasClass('failed') || el.hasClass('ignored'))){
el.removeClass('failed').removeClass('ignored');
el.getElement('.release_status').set('text', 'available');
}
- else {
+ else if(el) {
el.addClass('ignored');
el.getElement('.release_status').set('text', 'ignored');
}
diff --git a/couchpotato/core/media/movie/_base/static/movie.js b/couchpotato/core/media/movie/_base/static/movie.js
index 363d860..6defc2a 100644
--- a/couchpotato/core/media/movie/_base/static/movie.js
+++ b/couchpotato/core/media/movie/_base/static/movie.js
@@ -181,18 +181,18 @@ var Movie = new Class({
// Add releases
if(self.data.releases)
self.data.releases.each(function(release){
-
+
var q = self.quality.getElement('.q_id'+ release.quality_id),
status = Status.get(release.status_id);
-
+
if(!q && (status.identifier == 'snatched' || status.identifier == 'done'))
var q = self.addQuality(release.quality_id)
-
+
if (status && q && !q.hasClass(status.identifier)){
q.addClass(status.identifier);
q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status.label)
}
-
+
});
Object.each(self.options.actions, function(action, key){
@@ -256,7 +256,8 @@ var Movie = new Class({
self.el.removeEvents('outerClick')
setTimeout(function(){
- self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide();
+ if(self.el)
+ self.el.getElements('> :not(.data):not(.poster):not(.movie_container)').hide();
}, 600);
self.data_container.removeClass('hide_right');
@@ -266,9 +267,10 @@ var Movie = new Class({
changeView: function(new_view){
var self = this;
- self.el
- .removeClass(self.view+'_view')
- .addClass(new_view+'_view')
+ if(self.el)
+ self.el
+ .removeClass(self.view+'_view')
+ .addClass(new_view+'_view')
self.view = new_view;
},
diff --git a/couchpotato/core/media/movie/_base/static/search.css b/couchpotato/core/media/movie/_base/static/search.css
index dc74734..80c1815 100644
--- a/couchpotato/core/media/movie/_base/static/search.css
+++ b/couchpotato/core/media/movie/_base/static/search.css
@@ -59,6 +59,11 @@
.search_form.shown .input input {
opacity: 1;
}
+
+ .search_form input::-ms-clear {
+ width : 0;
+ height: 0;
+ }
@media all and (max-width: 480px) {
.search_form .input input {
diff --git a/couchpotato/core/media/movie/searcher/main.py b/couchpotato/core/media/movie/searcher/main.py
index 37571fb..79d2ed8 100644
--- a/couchpotato/core/media/movie/searcher/main.py
+++ b/couchpotato/core/media/movie/searcher/main.py
@@ -1,7 +1,7 @@
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
-from couchpotato.core.helpers.encoding import simplifyString, toUnicode
+from couchpotato.core.helpers.encoding import simplifyString, toUnicode, ss
from couchpotato.core.helpers.variable import md5, getTitle, splitString, \
possibleTitles, getImdb
from couchpotato.core.logger import CPLog
@@ -308,7 +308,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
return False
# Ignore porn stuff
- pron_tags = ['xxx', 'sex', 'anal', 'tits', 'fuck', 'porn', 'orgy', 'milf', 'boobs', 'erotica', 'erotic']
+ pron_tags = ['xxx', 'sex', 'anal', 'tits', 'fuck', 'porn', 'orgy', 'milf', 'boobs', 'erotica', 'erotic', 'cock', 'dick']
pron_words = list(set(nzb_words) & set(pron_tags) - set(movie_words))
if pron_words:
log.info('Wrong: %s, probably pr0n', (nzb['name']))
diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py
index a9a20b0..04acf28 100644
--- a/couchpotato/core/notifications/core/main.py
+++ b/couchpotato/core/notifications/core/main.py
@@ -198,13 +198,16 @@ class CoreNotifier(Notification):
def removeListener(self, callback):
self.m_lock.acquire()
+ new_listeners = []
for list_tuple in self.listeners:
try:
listener, last_id = list_tuple
- if listener == callback:
- self.listeners.remove(list_tuple)
+ if listener != callback:
+ new_listeners.append(list_tuple)
except:
log.debug('Failed removing listener: %s', traceback.format_exc())
+
+ self.listeners = new_listeners
self.m_lock.release()
def cleanMessages(self):
diff --git a/couchpotato/core/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js
index b05eb1a..e485976 100644
--- a/couchpotato/core/notifications/core/static/notification.js
+++ b/couchpotato/core/notifications/core/static/notification.js
@@ -157,7 +157,7 @@ var NotificationBase = new Class({
}
// Restart poll
- self.startPoll()
+ self.startPoll.delay(1500, self);
},
showMessage: function(message, sticky, data){
diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py
index b9ec0c0..ce7c1b4 100644
--- a/couchpotato/core/plugins/base.py
+++ b/couchpotato/core/plugins/base.py
@@ -259,7 +259,7 @@ class Plugin(object):
def getCache(self, cache_key, url = None, **kwargs):
- cache_key_md5 = md5(ss(cache_key))
+ cache_key_md5 = md5(cache_key)
cache = Env.get('cache').get(cache_key_md5)
if cache:
if not Env.get('dev'): log.debug('Getting cache %s', cache_key)
@@ -284,7 +284,7 @@ class Plugin(object):
return ''
def setCache(self, cache_key, value, timeout = 300):
- cache_key_md5 = md5(ss(cache_key))
+ cache_key_md5 = md5(cache_key)
log.debug('Setting cache %s', cache_key)
Env.get('cache').set(cache_key_md5, value, timeout)
return value
diff --git a/couchpotato/core/plugins/category/main.py b/couchpotato/core/plugins/category/main.py
index cc098c4..87cd0ea 100644
--- a/couchpotato/core/plugins/category/main.py
+++ b/couchpotato/core/plugins/category/main.py
@@ -54,12 +54,11 @@ class CategoryPlugin(Plugin):
db.add(c)
c.order = kwargs.get('order', c.order if c.order else 0)
- c.label = toUnicode(kwargs.get('label'))
- c.path = toUnicode(kwargs.get('path'))
- c.ignored = toUnicode(kwargs.get('ignored'))
- c.preferred = toUnicode(kwargs.get('preferred'))
- c.required = toUnicode(kwargs.get('required'))
- c.destination = toUnicode(kwargs.get('destination'))
+ c.label = toUnicode(kwargs.get('label', ''))
+ c.ignored = toUnicode(kwargs.get('ignored', ''))
+ c.preferred = toUnicode(kwargs.get('preferred', ''))
+ c.required = toUnicode(kwargs.get('required', ''))
+ c.destination = toUnicode(kwargs.get('destination', ''))
db.commit()
diff --git a/couchpotato/core/plugins/dashboard/main.py b/couchpotato/core/plugins/dashboard/main.py
index 330fd95..f006ac4 100644
--- a/couchpotato/core/plugins/dashboard/main.py
+++ b/couchpotato/core/plugins/dashboard/main.py
@@ -4,9 +4,10 @@ from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.variable import splitString, tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
-from couchpotato.core.settings.model import Media, Library, LibraryTitle
+from couchpotato.core.settings.model import Movie, Library, LibraryTitle, \
+ Release
from sqlalchemy.orm import joinedload_all
-from sqlalchemy.sql.expression import asc
+from sqlalchemy.sql.expression import asc, or_
import random as rndm
import time
@@ -48,12 +49,14 @@ class Dashboard(Plugin):
limit = tryInt(splt[0])
# Get all active movies
- active_status = fireEvent('status.get', ['active'], single = True)
- q = db.query(Media) \
+ active_status, ignored_status = fireEvent('status.get', ['active', 'ignored'], single = True)
+ q = db.query(Movie) \
.join(Library) \
- .filter(Media.status_id == active_status.get('id')) \
- .with_entities(Media.id, Media.profile_id, Library.info, Library.year) \
- .group_by(Media.id)
+ .outerjoin(Movie.releases) \
+ .filter(Movie.status_id == active_status.get('id')) \
+ .with_entities(Movie.id, Movie.profile_id, Library.info, Library.year) \
+ .group_by(Movie.id) \
+ .filter(or_(Release.id == None, Release.status_id == ignored_status.get('id')))
if not random:
q = q.join(LibraryTitle) \
@@ -98,11 +101,11 @@ class Dashboard(Plugin):
if len(movie_ids) > 0:
# Get all movie information
- movies_raw = db.query(Media) \
+ movies_raw = db.query(Movie) \
.options(joinedload_all('library.titles')) \
.options(joinedload_all('library.files')) \
.options(joinedload_all('files')) \
- .filter(Media.id.in_(movie_ids)) \
+ .filter(Movie.id.in_(movie_ids)) \
.all()
# Create dict by movie id
diff --git a/couchpotato/core/plugins/file/main.py b/couchpotato/core/plugins/file/main.py
index 238bc76..fc63aca 100644
--- a/couchpotato/core/plugins/file/main.py
+++ b/couchpotato/core/plugins/file/main.py
@@ -71,11 +71,11 @@ 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 or 'minified' in root or 'version' in filename or 'temp_updater' in root: continue
- file_path = os.path.join(root, filename)
- f = db.query(File).filter(File.path == toUnicode(file_path)).first()
- if not f:
- os.remove(file_path)
+ if os.path.splitext(filename)[1] in ['.png', '.jpg', '.jpeg']:
+ file_path = os.path.join(root, filename)
+ f = db.query(File).filter(File.path == toUnicode(file_path)).first()
+ if not f:
+ os.remove(file_path)
except:
log.error('Failed removing unused file: %s', traceback.format_exc())
diff --git a/couchpotato/core/plugins/renamer/__init__.py b/couchpotato/core/plugins/renamer/__init__.py
index 6472a2d..921b3e1 100755
--- a/couchpotato/core/plugins/renamer/__init__.py
+++ b/couchpotato/core/plugins/renamer/__init__.py
@@ -120,7 +120,13 @@ config = [{
{
'advanced': True,
'name': 'separator',
- 'label': 'Separator',
+ 'label': 'File-Separator',
+ 'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.',
+ },
+ {
+ 'advanced': True,
+ 'name': 'foldersep',
+ 'label': 'Folder-Separator',
'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.',
},
{
diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py
old mode 100644
new mode 100755
index 82434f5..ad7df1c
--- a/couchpotato/core/plugins/renamer/main.py
+++ b/couchpotato/core/plugins/renamer/main.py
@@ -173,8 +173,8 @@ class Renamer(Plugin):
# Overwrite destination when set in category
destination = self.conf('to')
- for movie in library_ent.media:
- if movie.category and movie.category.destination and len(movie.category.destination) > 0:
+ for movie in library_ent.movies:
+ if movie.category and movie.category.destination and len(movie.category.destination) > 0 and movie.category.destination != 'None':
destination = movie.category.destination
log.debug('Setting category destination for "%s": %s' % (movie_title, destination))
else:
@@ -252,7 +252,7 @@ class Renamer(Plugin):
replacements['cd_nr'] = cd if multiple else ''
# Naming
- final_folder_name = self.doReplace(folder_name, replacements)
+ final_folder_name = self.doReplace(folder_name, replacements, folder = True)
final_file_name = self.doReplace(file_name, replacements)
replacements['filename'] = final_file_name[:-(len(getExt(final_file_name)) + 1)]
@@ -342,13 +342,13 @@ class Renamer(Plugin):
remove_leftovers = True
# Add it to the wanted list before we continue
- if len(library_ent.media) == 0:
+ if len(library_ent.movies) == 0:
profile = db.query(Profile).filter_by(core = True, label = group['meta_data']['quality']['label']).first()
fireEvent('movie.add', params = {'identifier': group['library']['identifier'], 'profile_id': profile.id}, search_after = False)
db.expire_all()
library_ent = db.query(Library).filter_by(identifier = group['library']['identifier']).first()
- for movie in library_ent.media:
+ for movie in library_ent.movies:
# Mark movie "done" once it's found the quality with the finish check
try:
@@ -508,7 +508,7 @@ class Renamer(Plugin):
for extra in set(filter(test, group['files'][extra_type])):
replacements['ext'] = getExt(extra)
- final_folder_name = self.doReplace(folder_name, replacements, remove_multiple = remove_multiple)
+ final_folder_name = self.doReplace(folder_name, replacements, remove_multiple = remove_multiple, folder = True)
final_file_name = self.doReplace(file_name, replacements, remove_multiple = remove_multiple)
rename_files[extra] = os.path.join(destination, final_folder_name, final_file_name)
@@ -603,7 +603,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
return True
- def doReplace(self, string, replacements, remove_multiple = False):
+ def doReplace(self, string, replacements, remove_multiple = False, folder = False):
"""
replace confignames with the real thing
"""
@@ -623,7 +623,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
replaced = re.sub(r"[\x00:\*\?\"<>\|]", '', replaced)
- sep = self.conf('separator')
+ sep = self.conf('foldersep') if folder else self.conf('separator')
return self.replaceDoubles(replaced.lstrip('. ')).replace(' ', ' ' if not sep else sep)
def replaceDoubles(self, string):
diff --git a/couchpotato/core/plugins/scanner/main.py b/couchpotato/core/plugins/scanner/main.py
index 706f6c7..f38bef6 100644
--- a/couchpotato/core/plugins/scanner/main.py
+++ b/couchpotato/core/plugins/scanner/main.py
@@ -565,7 +565,7 @@ class Scanner(Plugin):
if not imdb_id:
try:
for nf in files['nfo']:
- imdb_id = getImdb(nf)
+ imdb_id = getImdb(nf, check_inside = True)
if imdb_id:
log.debug('Found movie via nfo file: %s', nf)
nfo_file = nf
@@ -578,7 +578,7 @@ class Scanner(Plugin):
try:
for filetype in files:
for filetype_file in files[filetype]:
- imdb_id = getImdb(filetype_file, check_inside = False)
+ imdb_id = getImdb(filetype_file)
if imdb_id:
log.debug('Found movie via imdb in filename: %s', nfo_file)
break
@@ -819,6 +819,13 @@ class Scanner(Plugin):
return None
def findYear(self, text):
+
+ # Search year inside () or [] first
+ matches = re.search('(\(|\[)(?P19[0-9]{2}|20[0-9]{2})(\]|\))', text)
+ if matches:
+ return matches.group('year')
+
+ # Search normal
matches = re.search('(?P19[0-9]{2}|20[0-9]{2})', text)
if matches:
return matches.group('year')
@@ -831,11 +838,11 @@ class Scanner(Plugin):
guess = {}
if file_name:
try:
- guess = guess_movie_info(toUnicode(file_name))
- if guess.get('title') and guess.get('year'):
+ guessit = guess_movie_info(toUnicode(file_name))
+ if guessit.get('title') and guessit.get('year'):
guess = {
- 'name': guess.get('title'),
- 'year': guess.get('year'),
+ 'name': guessit.get('title'),
+ 'year': guessit.get('year'),
}
except:
log.debug('Could not detect via guessit "%s": %s', (file_name, traceback.format_exc()))
@@ -843,7 +850,13 @@ class Scanner(Plugin):
# Backup to simple
cleaned = ' '.join(re.split('\W+', simplifyString(release_name)))
cleaned = re.sub(self.clean, ' ', cleaned)
- year = self.findYear(cleaned)
+
+ for year_str in [file_name, cleaned]:
+ if not year_str: continue
+ year = self.findYear(year_str)
+ if year:
+ break
+
cp_guess = {}
if year: # Split name on year
diff --git a/couchpotato/core/plugins/suggestion/main.py b/couchpotato/core/plugins/suggestion/main.py
index d2b33c4..f29281e 100644
--- a/couchpotato/core/plugins/suggestion/main.py
+++ b/couchpotato/core/plugins/suggestion/main.py
@@ -73,7 +73,7 @@ class Suggestion(Plugin):
def updateSuggestionCache(self, ignore_imdb = None, limit = 6, ignored = None, seen = None):
# Combine with previous suggestion_cache
- cached_suggestion = self.getCache('suggestion_cached')
+ cached_suggestion = self.getCache('suggestion_cached') or []
new_suggestions = []
ignored = [] if not ignored else ignored
seen = [] if not seen else seen
@@ -102,6 +102,6 @@ class Suggestion(Plugin):
if suggestions:
new_suggestions.extend(suggestions)
- self.setCache('suggestion_cached', new_suggestions, timeout = 6048000)
+ self.setCache('suggestion_cached', new_suggestions, timeout = 3024000)
return new_suggestions
diff --git a/couchpotato/core/plugins/suggestion/static/suggest.js b/couchpotato/core/plugins/suggestion/static/suggest.js
index e622671..40fe53b 100644
--- a/couchpotato/core/plugins/suggestion/static/suggest.js
+++ b/couchpotato/core/plugins/suggestion/static/suggest.js
@@ -58,54 +58,59 @@ var SuggestList = new Class({
var self = this;
- if(!json) return;
+ if(!json || json.count == 0){
+ self.el.hide();
+ }
+ else {
+
+ Object.each(json.suggestions, function(movie){
+
+ var m = new Block.Search.Item(movie, {
+ 'onAdded': function(){
+ self.afterAdded(m, movie)
+ }
+ });
+ m.data_container.grab(
+ new Element('div.actions').adopt(
+ new Element('a.add.icon2', {
+ 'title': 'Add movie with your default quality',
+ 'data-add': movie.imdb,
+ 'events': {
+ 'click': m.showOptions.bind(m)
+ }
+ }),
+ $(new MA.IMDB(m)),
+ $(new MA.Trailer(m, {
+ 'height': 150
+ })),
+ new Element('a.delete.icon2', {
+ 'title': 'Don\'t suggest this movie again',
+ 'data-ignore': movie.imdb
+ }),
+ new Element('a.eye-open.icon2', {
+ 'title': 'Seen it, like it, don\'t add',
+ 'data-seen': movie.imdb
+ })
+ )
+ );
+ m.data_container.removeEvents('click');
+
+ // Add rating
+ m.info_container.adopt(
+ m.rating = m.info.rating && m.info.rating.imdb.length == 2 && parseFloat(m.info.rating.imdb[0]) > 0 ? new Element('span.rating', {
+ 'text': parseFloat(m.info.rating.imdb[0]),
+ 'title': parseInt(m.info.rating.imdb[1]) + ' votes'
+ }) : null,
+ m.genre = m.info.genres && m.info.genres.length > 0 ? new Element('span.genres', {
+ 'text': m.info.genres.slice(0, 3).join(', ')
+ }) : null
+ )
- Object.each(json.suggestions, function(movie){
+ $(m).inject(self.el);
- var m = new Block.Search.Item(movie, {
- 'onAdded': function(){
- self.afterAdded(m, movie)
- }
});
- m.data_container.grab(
- new Element('div.actions').adopt(
- new Element('a.add.icon2', {
- 'title': 'Add movie with your default quality',
- 'data-add': movie.imdb,
- 'events': {
- 'click': m.showOptions.bind(m)
- }
- }),
- $(new MA.IMDB(m)),
- $(new MA.Trailer(m, {
- 'height': 150
- })),
- new Element('a.delete.icon2', {
- 'title': 'Don\'t suggest this movie again',
- 'data-ignore': movie.imdb
- }),
- new Element('a.eye-open.icon2', {
- 'title': 'Seen it, like it, don\'t add',
- 'data-seen': movie.imdb
- })
- )
- );
- m.data_container.removeEvents('click');
-
- // Add rating
- m.info_container.adopt(
- m.rating = m.info.rating && m.info.rating.imdb.length == 2 && parseFloat(m.info.rating.imdb[0]) > 0 ? new Element('span.rating', {
- 'text': parseFloat(m.info.rating.imdb[0]),
- 'title': parseInt(m.info.rating.imdb[1]) + ' votes'
- }) : null,
- m.genre = m.info.genres && m.info.genres.length > 0 ? new Element('span.genres', {
- 'text': m.info.genres.slice(0, 3).join(', ')
- }) : null
- )
-
- $(m).inject(self.el);
- });
+ }
self.fireEvent('loaded');
diff --git a/couchpotato/core/plugins/userscript/static/userscript.js b/couchpotato/core/plugins/userscript/static/userscript.js
index b784976..2aeb7b5 100644
--- a/couchpotato/core/plugins/userscript/static/userscript.js
+++ b/couchpotato/core/plugins/userscript/static/userscript.js
@@ -96,7 +96,7 @@ var UserscriptSettingTab = new Class({
})
)
).setStyles({
- 'background-image': "url('"+Api.createUrl('static/userscript/userscript.png')+"')"
+ 'background-image': "url('"+App.createUrl('static/plugin/userscript/userscript.png')+"')"
});
});
diff --git a/couchpotato/core/plugins/wizard/static/wizard.js b/couchpotato/core/plugins/wizard/static/wizard.js
index 71910e2..b4857ab 100644
--- a/couchpotato/core/plugins/wizard/static/wizard.js
+++ b/couchpotato/core/plugins/wizard/static/wizard.js
@@ -24,9 +24,10 @@ Page.Wizard = new Class({
'title': 'What download apps are you using?',
'description': 'CP needs an external download app to work with. Choose one below. For more downloaders check settings after you have filled in the wizard. If your download app isn\'t in the list, use the default Blackhole.'
},
- 'providers': {
+ 'searcher': {
+ 'label': 'Providers',
'title': 'Are you registered at any of these sites?',
- 'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have a few more. Check settings for the full list of available providers.'
+ 'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have more.'
},
'renamer': {
'title': 'Move & rename the movies after downloading?',
@@ -38,7 +39,7 @@ Page.Wizard = new Class({
'
Once installed, just click the bookmarklet on a movie page and watch the magic happen ;)',
'content': function(){
return App.createUserscriptButtons().setStyles({
- 'background-image': "url('"+Api.createUrl('static/userscript/userscript.png')+"')"
+ 'background-image': "url('"+App.createUrl('static/plugin/userscript/userscript.png')+"')"
})
}
},
@@ -76,7 +77,7 @@ Page.Wizard = new Class({
)
}
},
- groups: ['welcome', 'general', 'downloaders', 'searcher', 'providers', 'renamer', 'automation', 'finish'],
+ groups: ['welcome', 'general', 'downloaders', 'searcher', 'renamer', 'automation', 'finish'],
open: function(action, params){
var self = this;
@@ -195,8 +196,7 @@ Page.Wizard = new Class({
self.el.getElement('.advanced_toggle').destroy();
// Hide retention
- self.el.getElement('.tab_searcher').hide();
- self.el.getElement('.t_searcher').hide();
+ self.el.getElement('.section_nzb').hide();
// Add pointer
new Element('.tab_wrapper').wraps(tabs);
diff --git a/couchpotato/core/providers/info/themoviedb/main.py b/couchpotato/core/providers/info/themoviedb/main.py
index 387355f..376ddad 100644
--- a/couchpotato/core/providers/info/themoviedb/main.py
+++ b/couchpotato/core/providers/info/themoviedb/main.py
@@ -1,6 +1,5 @@
from couchpotato.core.event import addEvent
-from couchpotato.core.helpers.encoding import simplifyString, toUnicode, ss
-from couchpotato.core.helpers.variable import md5
+from couchpotato.core.helpers.encoding import simplifyString, toUnicode
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.info.base import MovieProvider
import tmdb3
@@ -129,11 +128,9 @@ class TheMovieDb(MovieProvider):
movie_data['titles'].append(movie.originaltitle)
for alt in movie.alternate_titles:
alt_name = alt.title
- if alt_name and not alt_name in movie_data['titles'] and alt_name.lower() != 'none' and alt_name is not None:
+ if alt_name and alt_name not in movie_data['titles'] and alt_name.lower() != 'none' and alt_name is not None:
movie_data['titles'].append(alt_name)
- movie_data['titles'] = list(set(movie_data['titles']))
-
# Cache movie parsed
self.setCache(cache_key, movie_data)
@@ -143,7 +140,7 @@ class TheMovieDb(MovieProvider):
image_url = ''
try:
- image_url = getattr(movie, type).geturl(size='original')
+ image_url = getattr(movie, type).geturl(size = 'original')
except:
log.debug('Failed getting %s.%s for "%s"', (type, size, movie.title))
diff --git a/couchpotato/core/providers/nzb/binsearch/main.py b/couchpotato/core/providers/nzb/binsearch/main.py
index dee5fc7..770ed50 100644
--- a/couchpotato/core/providers/nzb/binsearch/main.py
+++ b/couchpotato/core/providers/nzb/binsearch/main.py
@@ -56,6 +56,10 @@ class BinSearch(NZBProvider):
info = row.find('span', attrs = {'class':'d'})
size_match = re.search('size:.(?P[0-9\.]+.[GMB]+)', info.text)
+ age = 0
+ try: age = re.search('(?P\d+d)', row.find_all('td')[-1:][0].text).group('size')[:-1]
+ except: pass
+
def extra_check(item):
parts = re.search('available:.(?P\d+)./.(?P\d+)', info.text)
total = tryInt(parts.group('total'))
@@ -74,7 +78,7 @@ class BinSearch(NZBProvider):
results.append({
'id': nzb_id,
'name': title.text,
- 'age': tryInt(re.search('(?P\d+d)', row.find_all('td')[-1:][0].text).group('size')[:-1]),
+ 'age': tryInt(age),
'size': self.parseSize(size_match.group('size')),
'url': self.urls['download'] % nzb_id,
'detail_url': self.urls['detail'] % info.find('a')['href'],
diff --git a/couchpotato/core/providers/nzb/ftdworld/__init__.py b/couchpotato/core/providers/nzb/ftdworld/__init__.py
deleted file mode 100644
index 5a004a7..0000000
--- a/couchpotato/core/providers/nzb/ftdworld/__init__.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from .main import FTDWorld
-
-def start():
- return FTDWorld()
-
-config = [{
- 'name': 'ftdworld',
- 'groups': [
- {
- 'tab': 'searcher',
- 'list': 'nzb_providers',
- 'name': 'FTDWorld',
- 'description': 'Free provider, less accurate. See FTDWorld',
- 'wizard': True,
- 'options': [
- {
- 'name': 'enabled',
- 'type': 'enabler',
- },
- {
- 'name': 'username',
- 'default': '',
- },
- {
- 'name': 'password',
- 'default': '',
- 'type': 'password',
- },
- {
- 'name': 'extra_score',
- 'advanced': True,
- 'label': 'Extra Score',
- 'type': 'int',
- 'default': 0,
- 'description': 'Starting score for each release found via this provider.',
- }
- ],
- },
- ],
-}]
diff --git a/couchpotato/core/providers/nzb/ftdworld/main.py b/couchpotato/core/providers/nzb/ftdworld/main.py
deleted file mode 100644
index 9940cee..0000000
--- a/couchpotato/core/providers/nzb/ftdworld/main.py
+++ /dev/null
@@ -1,83 +0,0 @@
-from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
-from couchpotato.core.helpers.variable import tryInt
-from couchpotato.core.logger import CPLog
-from couchpotato.core.providers.nzb.base import NZBProvider
-from couchpotato.environment import Env
-import json
-import traceback
-
-log = CPLog(__name__)
-
-
-class FTDWorld(NZBProvider):
-
- urls = {
- 'search': 'http://ftdworld.net/api/index.php?%s',
- 'detail': 'http://ftdworld.net/spotinfo.php?id=%s',
- 'download': 'http://ftdworld.net/cgi-bin/nzbdown.pl?fileID=%s',
- 'login': 'http://ftdworld.net/api/login.php',
- 'login_check': 'http://ftdworld.net/api/login.php',
- }
-
- http_time_between_calls = 3 #seconds
-
- cat_ids = [
- ([4, 11], ['dvdr']),
- ([1], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr', 'brrip']),
- ([7, 10, 13, 14], ['bd50', '720p', '1080p']),
- ]
- cat_backup_id = 1
-
- def _searchOnTitle(self, title, movie, quality, results):
-
- q = '"%s" %s' % (title, movie['library']['year'])
-
- params = tryUrlencode({
- 'ctitle': q,
- 'customQuery': 'usr',
- 'cage': Env.setting('retention', 'nzb'),
- 'csizemin': quality.get('size_min'),
- 'csizemax': quality.get('size_max'),
- 'ccategory': 14,
- 'ctype': ','.join([str(x) for x in self.getCatId(quality['identifier'])]),
- })
-
- data = self.getJsonData(self.urls['search'] % params, opener = self.login_opener)
-
- if data:
- try:
-
- if data.get('numRes') == 0:
- return
-
- for item in data.get('data'):
-
- nzb_id = tryInt(item.get('id'))
- results.append({
- 'id': nzb_id,
- 'name': toUnicode(item.get('Title')),
- 'age': self.calculateAge(tryInt(item.get('Created'))),
- 'size': item.get('Size', 0),
- 'url': self.urls['download'] % nzb_id,
- 'detail_url': self.urls['detail'] % nzb_id,
- 'score': (tryInt(item.get('webPlus', 0)) - tryInt(item.get('webMin', 0))) * 3,
- })
-
- except:
- log.error('Failed to parse HTML response from FTDWorld: %s', traceback.format_exc())
-
- def getLoginParams(self):
- return tryUrlencode({
- 'userlogin': self.conf('username'),
- 'passlogin': self.conf('password'),
- 'submit': 'Log In',
- })
-
- def loginSuccess(self, output):
- try:
- return json.loads(output).get('goodToGo', False)
- except:
- return False
-
- loginCheckSuccess = loginSuccess
-
diff --git a/couchpotato/core/providers/nzb/newznab/__init__.py b/couchpotato/core/providers/nzb/newznab/__init__.py
index 3902ab1..5435927 100644
--- a/couchpotato/core/providers/nzb/newznab/__init__.py
+++ b/couchpotato/core/providers/nzb/newznab/__init__.py
@@ -20,6 +20,7 @@ config = [{
{
'name': 'enabled',
'type': 'enabler',
+ 'default': True,
},
{
'name': 'use',
diff --git a/couchpotato/core/providers/torrent/torrentshack/main.py b/couchpotato/core/providers/torrent/torrentshack/main.py
index b9d12c7..353b606 100644
--- a/couchpotato/core/providers/torrent/torrentshack/main.py
+++ b/couchpotato/core/providers/torrent/torrentshack/main.py
@@ -11,12 +11,12 @@ log = CPLog(__name__)
class TorrentShack(TorrentProvider):
urls = {
- 'test' : 'http://www.torrentshack.net/',
- 'login' : 'http://www.torrentshack.net/login.php',
- 'login_check': 'http://www.torrentshack.net/inbox.php',
- 'detail' : 'http://www.torrentshack.net/torrent/%s',
- 'search' : 'http://www.torrentshack.net/torrents.php?searchstr=%s&filter_cat[%d]=1',
- 'download' : 'http://www.torrentshack.net/%s',
+ 'test' : 'https://torrentshack.net/',
+ 'login' : 'https://torrentshack.net/login.php',
+ 'login_check': 'https://torrentshack.net/inbox.php',
+ 'detail' : 'https://torrentshack.net/torrent/%s',
+ 'search' : 'https://torrentshack.net/torrents.php?searchstr=%s&filter_cat[%d]=1',
+ 'download' : 'https://torrentshack.net/%s',
}
cat_ids = [
diff --git a/couchpotato/core/providers/trailer/hdtrailers/main.py b/couchpotato/core/providers/trailer/hdtrailers/main.py
index 320a583..abb9165 100644
--- a/couchpotato/core/providers/trailer/hdtrailers/main.py
+++ b/couchpotato/core/providers/trailer/hdtrailers/main.py
@@ -90,21 +90,18 @@ class HDTrailers(TrailerProvider):
html = BeautifulSoup(data, parse_only = tables)
result_table = html.find('table', attrs = {'class':'bottomTable'})
-
for tr in result_table.find_all('tr'):
trtext = str(tr).lower()
if 'clips' in trtext:
break
- if 'trailer' in trtext and not 'clip' in trtext and provider in trtext:
- nr = 0
+
+ if 'trailer' in trtext and not 'clip' in trtext and provider in trtext and not '3d' in trtext:
if 'trailer' not in tr.find('span', 'standardTrailerName').text.lower():
continue
resolutions = tr.find_all('td', attrs = {'class':'bottomTableResolution'})
for res in resolutions:
- results[str(res.a.contents[0])].insert(0, res.a['href'])
- nr += 1
-
- return results
+ if res.a:
+ results[str(res.a.contents[0])].insert(0, res.a['href'])
except AttributeError:
log.debug('No trailers found in provider %s.', provider)
diff --git a/couchpotato/runner.py b/couchpotato/runner.py
index 3e1505e..a89cd08 100644
--- a/couchpotato/runner.py
+++ b/couchpotato/runner.py
@@ -85,18 +85,7 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
# Backup before start and cleanup old databases
new_backup = toUnicode(os.path.join(data_dir, 'db_backup', str(int(time.time()))))
-
- # Create path and copy
if not os.path.isdir(new_backup): os.makedirs(new_backup)
- src_files = [options.config_file, db_path, db_path + '-shm', db_path + '-wal']
- for src_file in src_files:
- if os.path.isfile(src_file):
- dst_file = toUnicode(os.path.join(new_backup, os.path.basename(src_file)))
- shutil.copyfile(src_file, dst_file)
-
- # Try and copy stats seperately
- try: shutil.copystat(src_file, dst_file)
- except: pass
# Remove older backups, keep backups 3 days or at least 3
backups = []
@@ -105,6 +94,19 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
if os.path.isdir(backup):
backups.append(backup)
+ latest_backup = tryInt(os.path.basename(sorted(backups)[-1])) if len(backups) > 0 else 0
+ if latest_backup < time.time() - 3600:
+ # Create path and copy
+ src_files = [options.config_file, db_path, db_path + '-shm', db_path + '-wal']
+ for src_file in src_files:
+ if os.path.isfile(src_file):
+ dst_file = toUnicode(os.path.join(new_backup, os.path.basename(src_file)))
+ shutil.copyfile(src_file, dst_file)
+
+ # Try and copy stats seperately
+ try: shutil.copystat(src_file, dst_file)
+ except: pass
+
total_backups = len(backups)
for backup in backups:
if total_backups > 3:
diff --git a/init/ubuntu b/init/ubuntu
index 511d9ee..7f770a6 100644
--- a/init/ubuntu
+++ b/init/ubuntu
@@ -52,7 +52,7 @@ APP_PATH=${CP_HOME-/opt/couchpotato/}
DATA_DIR=${CP_DATA-/var/couchpotato}
# Path to store PID file
-PID_FILE=${CP_PID_FILE-/var/run/couchpotato.pid}
+PID_FILE=${CP_PIDFILE-/var/run/couchpotato.pid}
# path to python bin
DAEMON=${PYTHON_BIN-/usr/bin/python}
diff --git a/libs/guessit/__init__.py b/libs/guessit/__init__.py
index 386aa7f..ce14024 100755
--- a/libs/guessit/__init__.py
+++ b/libs/guessit/__init__.py
@@ -20,7 +20,7 @@
from __future__ import unicode_literals
-__version__ = '0.6-dev'
+__version__ = '0.7-dev'
__all__ = ['Guess', 'Language',
'guess_file_info', 'guess_video_info',
'guess_movie_info', 'guess_episode_info']
@@ -91,7 +91,28 @@ log.addHandler(h)
def _guess_filename(filename, filetype):
+ def find_nodes(tree, props):
+ """Yields all nodes containing any of the given props."""
+ if isinstance(props, base_text_type):
+ props = [props]
+ for node in tree.nodes():
+ if any(prop in node.guess for prop in props):
+ yield node
+
+ def warning(title):
+ log.warning('%s, guesses: %s - %s' % (title, m.nice_string(), m2.nice_string()))
+ return m
+
mtree = IterativeMatcher(filename, filetype=filetype)
+
+ # if there are multiple possible years found, we assume the first one is
+ # part of the title, reparse the tree taking this into account
+ years = set(n.value for n in find_nodes(mtree.match_tree, 'year'))
+ if len(years) >= 2:
+ mtree = IterativeMatcher(filename, filetype=filetype,
+ opts=['skip_first_year'])
+
+
m = mtree.matched()
if 'language' not in m and 'subtitleLanguage' not in m:
@@ -102,20 +123,10 @@ def _guess_filename(filename, filetype):
opts=['nolanguage', 'nocountry'])
m2 = mtree2.matched()
- def find_nodes(tree, props):
- """Yields all nodes containing any of the given props."""
- if isinstance(props, base_text_type):
- props = [props]
- for node in tree.nodes():
- if any(prop in node.guess for prop in props):
- yield node
-
- def warning(title):
- log.warning('%s, guesses: %s - %s' % (title, m.nice_string(), m2.nice_string()))
+ if m.get('title') is None:
return m
-
if m.get('title') != m2.get('title'):
title = next(find_nodes(mtree.match_tree, 'title'))
title2 = next(find_nodes(mtree2.match_tree, 'title'))
diff --git a/libs/guessit/fileutils.py b/libs/guessit/fileutils.py
index 45f07e8..dc077e6 100755
--- a/libs/guessit/fileutils.py
+++ b/libs/guessit/fileutils.py
@@ -77,12 +77,12 @@ def file_in_same_dir(ref_file, desired_file):
def load_file_in_same_dir(ref_file, filename):
"""Load a given file. Works even when the file is contained inside a zip."""
- path = split_path(ref_file)[:-1] + [str(filename)]
+ path = split_path(ref_file)[:-1] + [filename]
for i, p in enumerate(path):
- if p[-4:] == '.zip':
+ if p.endswith('.zip'):
zfilename = os.path.join(*path[:i + 1])
zfile = zipfile.ZipFile(zfilename)
return zfile.read('/'.join(path[i + 1:]))
- return u(io.open(os.path.join(*path), encoding = 'utf-8').read())
+ return u(io.open(os.path.join(*path), encoding='utf-8').read())
diff --git a/libs/guessit/guess.py b/libs/guessit/guess.py
index 62385e8..33d3651 100755
--- a/libs/guessit/guess.py
+++ b/libs/guessit/guess.py
@@ -295,7 +295,7 @@ def merge_all(guesses, append=None):
# then merge the remaining ones
dups = set(result) & set(g)
if dups:
- log.warning('duplicate properties %s in merged result...' % dups)
+ log.warning('duplicate properties %s in merged result...' % [ (result[p], g[p]) for p in dups] )
result.update_highest_confidence(g)
diff --git a/libs/guessit/language.py b/libs/guessit/language.py
index 3b3a86a..2714c6e 100755
--- a/libs/guessit/language.py
+++ b/libs/guessit/language.py
@@ -326,7 +326,7 @@ def search_language(string, lang_filter=None):
'la', 'el', 'del', 'por', 'mar',
# other
'ind', 'arw', 'ts', 'ii', 'bin', 'chan', 'ss', 'san', 'oss', 'iii',
- 'vi', 'ben', 'da'
+ 'vi', 'ben', 'da', 'lt'
])
sep = r'[](){} \._-+'
diff --git a/libs/guessit/matcher.py b/libs/guessit/matcher.py
index cc77b81..4337819 100755
--- a/libs/guessit/matcher.py
+++ b/libs/guessit/matcher.py
@@ -128,12 +128,14 @@ class IterativeMatcher(object):
apply_transfo(name)
# more guessers for both movies and episodes
- for name in ['guess_bonus_features', 'guess_year']:
- apply_transfo(name)
+ apply_transfo('guess_bonus_features')
+ apply_transfo('guess_year', skip_first_year=('skip_first_year' in opts))
if 'nocountry' not in opts:
apply_transfo('guess_country')
+ apply_transfo('guess_idnumber')
+
# split into '-' separated subgroups (with required separator chars
# around the dash)
diff --git a/libs/guessit/matchtree.py b/libs/guessit/matchtree.py
index 2853c3a..0725e83 100755
--- a/libs/guessit/matchtree.py
+++ b/libs/guessit/matchtree.py
@@ -275,7 +275,7 @@ class MatchTree(BaseMatchTree):
for string_part in ('title', 'series', 'container', 'format',
'releaseGroup', 'website', 'audioCodec',
'videoCodec', 'screenSize', 'episodeFormat',
- 'audioChannels'):
+ 'audioChannels', 'idNumber'):
merge_similar_guesses(parts, string_part, choose_string)
# 2- merge the rest, potentially discarding information not properly
diff --git a/libs/guessit/patterns.py b/libs/guessit/patterns.py
index a8a0607..ed3982b 100755
--- a/libs/guessit/patterns.py
+++ b/libs/guessit/patterns.py
@@ -43,13 +43,13 @@ episode_rexps = [ # ... Season 2 ...
(r'saison (?P[0-9]+)', 1.0, (0, 0)),
# ... s02e13 ...
- (r'[Ss](?P[0-9]{1,2}).?(?P(?:[Ee-][0-9]{1,2})+)[^0-9]', 1.0, (0, -1)),
+ (r'[Ss](?P[0-9]{1,3})[^0-9]?(?P(?:-?[eE-][0-9]{1,3})+)[^0-9]', 1.0, (0, -1)),
- # ... s03-x02 ...
- (r'[Ss](?P[0-9]{1,2}).?(?P(?:[Xx][0-9]{1,2})+)[^0-9]', 1.0, (0, -1)),
+ # ... s03-x02 ... # FIXME: redundant? remove it?
+ #(r'[Ss](?P[0-9]{1,3})[^0-9]?(?P(?:-?[xX-][0-9]{1,3})+)[^0-9]', 1.0, (0, -1)),
# ... 2x13 ...
- (r'[^0-9](?P[0-9]{1,2}).?(?P(?:[xX][0-9]{1,2})+)[^0-9]', 0.8, (1, -1)),
+ (r'[^0-9](?P[0-9]{1,2})[^0-9]?(?P(?:-?[xX][0-9]{1,3})+)[^0-9]', 1.0, (1, -1)),
# ... s02 ...
#(sep + r's(?P[0-9]{1,2})' + sep, 0.6, (1, -1)),
@@ -122,20 +122,25 @@ prop_multi = { 'format': { 'DVD': [ 'DVD', 'DVD-Rip', 'VIDEO-TS', 'DVDivX' ],
'VHS': [ 'VHS' ],
'WEB-DL': [ 'WEB-DL' ] },
- 'screenSize': { '480p': [ '480p?' ],
- '720p': [ '720p?' ],
- '1080p': [ '1080p?' ] },
+ 'screenSize': { '480p': [ '480[pi]?' ],
+ '720p': [ '720[pi]?' ],
+ '1080p': [ '1080[pi]?' ] },
'videoCodec': { 'XviD': [ 'Xvid' ],
'DivX': [ 'DVDivX', 'DivX' ],
'h264': [ '[hx]-264' ],
- 'Rv10': [ 'Rv10' ] },
+ 'Rv10': [ 'Rv10' ],
+ 'Mpeg2': [ 'Mpeg2' ] },
+
+ # has nothing to do here (or on filenames for that matter), but some
+ # releases use it and it helps to identify release groups, so we adapt
+ 'videoApi': { 'DXVA': [ 'DXVA' ] },
'audioCodec': { 'AC3': [ 'AC3' ],
'DTS': [ 'DTS' ],
'AAC': [ 'He-AAC', 'AAC-He', 'AAC' ] },
- 'audioChannels': { '5.1': [ r'5\.1', 'DD5\.1', '5ch' ] },
+ 'audioChannels': { '5.1': [ r'5\.1', 'DD5[\._ ]1', '5ch' ] },
'episodeFormat': { 'Minisode': [ 'Minisodes?' ] }
@@ -143,14 +148,21 @@ prop_multi = { 'format': { 'DVD': [ 'DVD', 'DVD-Rip', 'VIDEO-TS', 'DVDivX' ],
# prop_single dict of { property_name: [ canonical_form ] }
prop_single = { 'releaseGroup': [ 'ESiR', 'WAF', 'SEPTiC', r'\[XCT\]', 'iNT', 'PUKKA',
- 'CHD', 'ViTE', 'TLF', 'DEiTY', 'FLAiTE',
- 'MDX', 'GM4F', 'DVL', 'SVD', 'iLUMiNADOS', 'FiNaLe',
- 'UnSeeN', 'aXXo', 'KLAXXON', 'NoTV', 'ZeaL', 'LOL',
- 'SiNNERS', 'DiRTY', 'REWARD', 'ECI', 'KiNGS', 'CLUE',
- 'CtrlHD', 'POD', 'WiKi', 'DIMENSION', 'IMMERSE', 'FQM',
- '2HD', 'REPTiLE', 'CTU', 'HALCYON', 'EbP', 'SiTV',
- 'SAiNTS', 'HDBRiSe', 'AlFleNi-TeaM', 'EVOLVE', '0TV',
- 'TLA', 'NTB', 'ASAP', 'MOMENTUM', 'FoV', 'D-Z0N3' ],
+ 'CHD', 'ViTE', 'TLF', 'FLAiTE',
+ 'MDX', 'GM4F', 'DVL', 'SVD', 'iLUMiNADOS',
+ 'aXXo', 'KLAXXON', 'NoTV', 'ZeaL', 'LOL',
+ 'CtrlHD', 'POD', 'WiKi','IMMERSE', 'FQM',
+ '2HD', 'CTU', 'HALCYON', 'EbP', 'SiTV',
+ 'HDBRiSe', 'AlFleNi-TeaM', 'EVOLVE', '0TV',
+ 'TLA', 'NTB', 'ASAP', 'MOMENTUM', 'FoV', 'D-Z0N3',
+ 'TrollHD', 'ECI'
+ ],
+
+ # potentially confusing release group names (they are words)
+ 'weakReleaseGroup': [ 'DEiTY', 'FiNaLe', 'UnSeeN', 'KiNGS', 'CLUE', 'DIMENSION',
+ 'SAiNTS', 'ARROW', 'EuReKA', 'SiNNERS', 'DiRTY', 'REWARD',
+ 'REPTiLE',
+ ],
'other': [ 'PROPER', 'REPACK', 'LIMITED', 'DualAudio', 'Audiofixed', 'R5',
'complete', 'classic', # not so sure about these ones, could appear in a title
@@ -179,6 +191,10 @@ properties_rexps.update(dict((type, dict((canonical_form, [ _to_rexp(canonical_f
def find_properties(string):
result = []
for property_name, props in properties_rexps.items():
+ # FIXME: this should be done in a more flexible way...
+ if property_name in ['weakReleaseGroup']:
+ continue
+
for canonical_form, rexps in props.items():
for value_rexp in rexps:
match = value_rexp.search(string)
diff --git a/libs/guessit/transfo/guess_episodes_rexps.py b/libs/guessit/transfo/guess_episodes_rexps.py
index 4ebfb54..29562be 100755
--- a/libs/guessit/transfo/guess_episodes_rexps.py
+++ b/libs/guessit/transfo/guess_episodes_rexps.py
@@ -28,7 +28,13 @@ import logging
log = logging.getLogger(__name__)
def number_list(s):
- return list(re.sub('[^0-9]+', ' ', s).split())
+ l = [ int(n) for n in re.sub('[^0-9]+', ' ', s).split() ]
+
+ if len(l) == 2:
+ # it is an episode interval, return all numbers in between
+ return range(l[0], l[1]+1)
+
+ return l
def guess_episodes_rexps(string):
for rexp, confidence, span_adjust in episode_rexps:
@@ -38,23 +44,23 @@ def guess_episodes_rexps(string):
span = (match.start() + span_adjust[0],
match.end() + span_adjust[1])
- # episodes which have a season > 25 are most likely errors
+ # episodes which have a season > 30 are most likely errors
# (Simpsons is at 24!)
- if int(guess.get('season', 0)) > 25:
+ if int(guess.get('season', 0)) > 30:
continue
# decide whether we have only a single episode number or an
# episode list
if guess.get('episodeNumber'):
eplist = number_list(guess['episodeNumber'])
- guess.set('episodeNumber', int(eplist[0]), confidence=confidence)
+ guess.set('episodeNumber', eplist[0], confidence=confidence)
if len(eplist) > 1:
- guess.set('episodeList', list(map(int, eplist)), confidence=confidence)
+ guess.set('episodeList', eplist, confidence=confidence)
if guess.get('bonusNumber'):
eplist = number_list(guess['bonusNumber'])
- guess.set('bonusNumber', int(eplist[0]), confidence=confidence)
+ guess.set('bonusNumber', eplist[0], confidence=confidence)
return guess, span
diff --git a/libs/guessit/transfo/guess_idnumber.py b/libs/guessit/transfo/guess_idnumber.py
new file mode 100755
index 0000000..0e15af5
--- /dev/null
+++ b/libs/guessit/transfo/guess_idnumber.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# GuessIt - A library for guessing information from filenames
+# Copyright (c) 2013 Nicolas Wack
+#
+# GuessIt is free software; you can redistribute it and/or modify it under
+# the terms of the Lesser GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# GuessIt is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Lesser GNU General Public License for more details.
+#
+# You should have received a copy of the Lesser GNU General Public License
+# along with this program. If not, see .
+#
+
+from __future__ import unicode_literals
+from guessit.transfo import SingleNodeGuesser
+from guessit.patterns import find_properties
+import re
+import logging
+
+log = logging.getLogger(__name__)
+
+
+def guess_properties(string):
+ try:
+ prop, value, pos, end = find_properties(string)[0]
+ return { prop: value }, (pos, end)
+ except IndexError:
+ return None, None
+
+_idnum = re.compile(r'(?P[a-zA-Z0-9-]{10,})') # 1.0, (0, 0))
+
+def guess_idnumber(string):
+ match = _idnum.search(string)
+ if match is not None:
+ result = match.groupdict()
+ switch_count = 0
+ DIGIT = 0
+ LETTER = 1
+ OTHER = 2
+ last = LETTER
+ for c in result['idNumber']:
+ if c in '0123456789':
+ ci = DIGIT
+ elif c in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ':
+ ci = LETTER
+ else:
+ ci = OTHER
+
+ if ci != last:
+ switch_count += 1
+
+ last = ci
+
+ switch_ratio = float(switch_count) / len(result['idNumber'])
+
+ # only return the result as probable if we alternate often between
+ # char type (more likely for hash values than for common words)
+ if switch_ratio > 0.4:
+ return result, match.span()
+
+ return None, None
+
+def process(mtree):
+ SingleNodeGuesser(guess_idnumber, 0.4, log).process(mtree)
diff --git a/libs/guessit/transfo/guess_release_group.py b/libs/guessit/transfo/guess_release_group.py
index 2ff237d..b72c736 100755
--- a/libs/guessit/transfo/guess_release_group.py
+++ b/libs/guessit/transfo/guess_release_group.py
@@ -31,16 +31,22 @@ def get_patterns(property_name):
CODECS = get_patterns('videoCodec')
FORMATS = get_patterns('format')
+VAPIS = get_patterns('videoApi')
-GROUP_NAMES = [ r'(?P' + codec + r')-?(?P.*?)[ \.]'
+# RG names following a codec or format, with a potential space or dash inside the name
+GROUP_NAMES = [ r'(?P' + codec + r')[ \.-](?P.+?([- \.].*?)??)[ \.]'
for codec in CODECS ]
-GROUP_NAMES += [ r'(?P' + fmt + r')-?(?P.*?)[ \.]'
+GROUP_NAMES += [ r'(?P' + fmt + r')[ \.-](?P.+?([- \.].*?)??)[ \.]'
for fmt in FORMATS ]
+GROUP_NAMES += [ r'(?P' + api + r')[ \.-](?P.+?([- \.].*?)??)[ \.]'
+ for api in VAPIS ]
GROUP_NAMES2 = [ r'\.(?P' + codec + r')-(?P.*?)(-(.*?))?[ \.]'
for codec in CODECS ]
-GROUP_NAMES2 += [ r'\.(?P' + fmt + r')-(?P.*?)(-(.*?))?[ \.]'
+GROUP_NAMES2 += [ r'\.(?P' + fmt + r')-(?P.*?)(-(.*?))?[ \.]'
for fmt in FORMATS ]
+GROUP_NAMES2 += [ r'\.(?P' + vapi + r')-(?P.*?)(-(.*?))?[ \.]'
+ for vapi in VAPIS ]
GROUP_NAMES = [ re.compile(r, re.IGNORECASE) for r in GROUP_NAMES ]
GROUP_NAMES2 = [ re.compile(r, re.IGNORECASE) for r in GROUP_NAMES2 ]
@@ -54,12 +60,17 @@ def guess_release_group(string):
# first try to see whether we have both a known codec and a known release group
for rexp in GROUP_NAMES:
match = rexp.search(string)
- if match:
+ while match:
metadata = match.groupdict()
- release_group = compute_canonical_form('releaseGroup', metadata['releaseGroup'])
+ # make sure this is an actual release group we caught
+ release_group = (compute_canonical_form('releaseGroup', metadata['releaseGroup']) or
+ compute_canonical_form('weakReleaseGroup', metadata['releaseGroup']))
if release_group:
return adjust_metadata(metadata), (match.start(1), match.end(2))
+ # we didn't find anything conclusive, keep searching
+ match = rexp.search(string, match.span()[0]+1)
+
# pick anything as releaseGroup as long as we have a codec in front
# this doesn't include a potential dash ('-') ending the release group
# eg: [...].X264-HiS@SiLUHD-English.[...]
diff --git a/libs/guessit/transfo/guess_year.py b/libs/guessit/transfo/guess_year.py
index 4bc9b86..c193af7 100755
--- a/libs/guessit/transfo/guess_year.py
+++ b/libs/guessit/transfo/guess_year.py
@@ -33,6 +33,18 @@ def guess_year(string):
else:
return None, None
+def guess_year_skip_first(string):
+ year, span = search_year(string)
+ if year:
+ year2, span2 = guess_year(string[span[1]:])
+ if year2:
+ return year2, (span2[0]+span[1], span2[1]+span[1])
+
+ return None, None
-def process(mtree):
- SingleNodeGuesser(guess_year, 1.0, log).process(mtree)
+
+def process(mtree, skip_first_year=False):
+ if skip_first_year:
+ SingleNodeGuesser(guess_year_skip_first, 1.0, log).process(mtree)
+ else:
+ SingleNodeGuesser(guess_year, 1.0, log).process(mtree)
diff --git a/libs/synchronousdeluge/transfer.py b/libs/synchronousdeluge/transfer.py
index 29b1ebe..979ffb1 100644
--- a/libs/synchronousdeluge/transfer.py
+++ b/libs/synchronousdeluge/transfer.py
@@ -19,7 +19,7 @@ class DelugeTransfer(object):
self.disconnect()
self.sock = socket.create_connection(hostport)
- self.conn = ssl.wrap_socket(self.sock)
+ self.conn = ssl.wrap_socket(self.sock, None, None, False, ssl.CERT_NONE, ssl.PROTOCOL_SSLv3)
self.connected = True
def disconnect(self):
diff --git a/libs/unrar2/PKG-INFO b/libs/unrar2/PKG-INFO
deleted file mode 100644
index 7e49592..0000000
--- a/libs/unrar2/PKG-INFO
+++ /dev/null
@@ -1,27 +0,0 @@
-Metadata-Version: 1.0
-Name: pyUnRAR2
-Version: 0.99.2
-Summary: Improved Python wrapper around the free UnRAR.dll
-Home-page: http://code.google.com/py-unrar2
-Author: Konstantin Yegupov
-Author-email: yk4ever@gmail.com
-License: MIT
-Description: pyUnRAR2 is a ctypes based wrapper around the free UnRAR.dll.
-
- It is an modified version of Jimmy Retzlaff's pyUnRAR - more simple,
- stable and foolproof.
- Notice that it has INCOMPATIBLE interface.
-
- It enables reading and unpacking of archives created with the
- RAR/WinRAR archivers. There is a low-level interface which is very
- similar to the C interface provided by UnRAR. There is also a
- higher level interface which makes some common operations easier.
-Platform: Windows
-Classifier: Development Status :: 4 - Beta
-Classifier: Environment :: Win32 (MS Windows)
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Natural Language :: English
-Classifier: Operating System :: Microsoft :: Windows
-Classifier: Programming Language :: Python
-Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Classifier: Topic :: System :: Archiving :: Compression
diff --git a/libs/unrar2/UnRAR2.html b/libs/unrar2/UnRAR2.html
deleted file mode 100644
index 0553ee0..0000000
--- a/libs/unrar2/UnRAR2.html
+++ /dev/null
@@ -1,191 +0,0 @@
-
-
-Python: package UnRAR2
-
-
-
- pyUnRAR2 is a ctypes based wrapper around the free UnRAR.dll.
-
-It is an modified version of Jimmy Retzlaff's pyUnRAR - more simple,
-stable and foolproof.
-Notice that it has INCOMPATIBLE interface.
-
-It enables reading and unpacking of archives created with the
-RAR/WinRAR archivers. There is a low-level interface which is very
-similar to the C interface provided by UnRAR. There is also a
-higher level interface which makes some common operations easier.
-
-
-
-
-Package Contents |
-
- | |
- |
-
-
-
-Classes |
-
- | |
-
-- UnRAR2.windows.RarFileImplementation(__builtin__.object)
-
-
-
-- RarFile
-
-
-- __builtin__.object
-
-
-
-- RarInfo
-
-
-
-
-
-
-
-class RarFile(UnRAR2.windows.RarFileImplementation) |
-
- | |
-- Method resolution order:
-- RarFile
-- UnRAR2.windows.RarFileImplementation
-- __builtin__.object
-
-
-Methods defined here:
-- __del__(self)
-
-- __init__(self, archiveName, password=None)
- Instantiate the archive.
-
-archiveName is the name of the RAR file.
-password is used to decrypt the files in the archive.
-
-Properties:
- comment - comment associated with the archive
-
->>> print RarFile('test.rar').comment
-This is a test.
-
-- extract(self, condition='*', path='.', withSubpath=True, overwrite=True)
- Extract specific files from archive to disk.
-
-If "condition" is a list of numbers, then extract files which have those positions in infolist.
-If "condition" is a string, then it is treated as a wildcard for names of files to extract.
-If "condition" is a function, it is treated as a callback function, which accepts a RarInfo object
- and returns either boolean True (extract) or boolean False (skip).
-DEPRECATED: If "condition" callback returns string (only supported for Windows) -
- that string will be used as a new name to save the file under.
-If "condition" is omitted, all files are extracted.
-
-"path" is a directory to extract to
-"withSubpath" flag denotes whether files are extracted with their full path in the archive.
-"overwrite" flag denotes whether extracted files will overwrite old ones. Defaults to true.
-
-Returns list of RarInfos for extracted files.
-
-- infoiter(self)
- Iterate over all the files in the archive, generating RarInfos.
-
->>> import os
->>> for fileInArchive in RarFile('test.rar').infoiter():
-... print os.path.split(fileInArchive.filename)[-1],
-... print fileInArchive.isdir,
-... print fileInArchive.size,
-... print fileInArchive.comment,
-... print tuple(fileInArchive.datetime)[0:5],
-... print time.strftime('%a, %d %b %Y %H:%M', fileInArchive.datetime)
-test True 0 None (2003, 6, 30, 1, 59) Mon, 30 Jun 2003 01:59
-test.txt False 20 None (2003, 6, 30, 2, 1) Mon, 30 Jun 2003 02:01
-this.py False 1030 None (2002, 2, 8, 16, 47) Fri, 08 Feb 2002 16:47
-
-- infolist(self)
- Return a list of RarInfos, descripting the contents of the archive.
-
-- read_files(self, condition='*')
- Read specific files from archive into memory.
-If "condition" is a list of numbers, then return files which have those positions in infolist.
-If "condition" is a string, then it is treated as a wildcard for names of files to extract.
-If "condition" is a function, it is treated as a callback function, which accepts a RarInfo object
- and returns boolean True (extract) or False (skip).
-If "condition" is omitted, all files are returned.
-
-Returns list of tuples (RarInfo info, str contents)
-
-
-Methods inherited from UnRAR2.windows.RarFileImplementation:
-- destruct(self)
-
-- init(self, password=None)
-
-- make_sure_ready(self)
-
-
-Data descriptors inherited from UnRAR2.windows.RarFileImplementation:
-- __dict__
-- dictionary for instance variables (if defined)
-
-- __weakref__
-- list of weak references to the object (if defined)
-
- |
-
-
-
-class RarInfo(__builtin__.object) |
-
- |
-Represents a file header in an archive. Don't instantiate directly.
-Use only to obtain information about file.
-YOU CANNOT EXTRACT FILE CONTENTS USING THIS OBJECT.
-USE METHODS OF RarFile CLASS INSTEAD.
-
-Properties:
- index - index of file within the archive
- filename - name of the file in the archive including path (if any)
- datetime - file date/time as a struct_time suitable for time.strftime
- isdir - True if the file is a directory
- size - size in bytes of the uncompressed file
- comment - comment associated with the file
-
-Note - this is not currently intended to be a Python file-like object. |
- |
-Methods defined here:
-- __init__(self, rarfile, data)
-
-- __str__(self)
-
-
-Data descriptors defined here:
-- __dict__
-- dictionary for instance variables (if defined)
-
-- __weakref__
-- list of weak references to the object (if defined)
-
- | |
-
-
-
-Functions |
-
- | |
-- condition2checker(condition)
- Converts different condition types to callback
- |
-
-
-
-Data |
-
- | |
-__version__ = '0.99.1'
-in_windows = True |
-
\ No newline at end of file
diff --git a/libs/unrar2/UnRARDLL/license.txt b/libs/unrar2/UnRARDLL/license.txt
deleted file mode 100644
index 0c1540e..0000000
--- a/libs/unrar2/UnRARDLL/license.txt
+++ /dev/null
@@ -1,18 +0,0 @@
- The unrar.dll library is freeware. This means:
-
- 1. All copyrights to RAR and the unrar.dll are exclusively
- owned by the author - Alexander Roshal.
-
- 2. The unrar.dll library may be used in any software to handle RAR
- archives without limitations free of charge.
-
- 3. THE RAR ARCHIVER AND THE UNRAR.DLL LIBRARY ARE DISTRIBUTED "AS IS".
- NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED. YOU USE AT
- YOUR OWN RISK. THE AUTHOR WILL NOT BE LIABLE FOR DATA LOSS,
- DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING
- OR MISUSING THIS SOFTWARE.
-
- Thank you for your interest in RAR and unrar.dll.
-
-
- Alexander L. Roshal
\ No newline at end of file
diff --git a/libs/unrar2/UnRARDLL/unrar.h b/libs/unrar2/UnRARDLL/unrar.h
deleted file mode 100644
index 4582f2c..0000000
--- a/libs/unrar2/UnRARDLL/unrar.h
+++ /dev/null
@@ -1,140 +0,0 @@
-#ifndef _UNRAR_DLL_
-#define _UNRAR_DLL_
-
-#define ERAR_END_ARCHIVE 10
-#define ERAR_NO_MEMORY 11
-#define ERAR_BAD_DATA 12
-#define ERAR_BAD_ARCHIVE 13
-#define ERAR_UNKNOWN_FORMAT 14
-#define ERAR_EOPEN 15
-#define ERAR_ECREATE 16
-#define ERAR_ECLOSE 17
-#define ERAR_EREAD 18
-#define ERAR_EWRITE 19
-#define ERAR_SMALL_BUF 20
-#define ERAR_UNKNOWN 21
-#define ERAR_MISSING_PASSWORD 22
-
-#define RAR_OM_LIST 0
-#define RAR_OM_EXTRACT 1
-#define RAR_OM_LIST_INCSPLIT 2
-
-#define RAR_SKIP 0
-#define RAR_TEST 1
-#define RAR_EXTRACT 2
-
-#define RAR_VOL_ASK 0
-#define RAR_VOL_NOTIFY 1
-
-#define RAR_DLL_VERSION 4
-
-#ifdef _UNIX
-#define CALLBACK
-#define PASCAL
-#define LONG long
-#define HANDLE void *
-#define LPARAM long
-#define UINT unsigned int
-#endif
-
-struct RARHeaderData
-{
- char ArcName[260];
- char FileName[260];
- unsigned int Flags;
- unsigned int PackSize;
- unsigned int UnpSize;
- unsigned int HostOS;
- unsigned int FileCRC;
- unsigned int FileTime;
- unsigned int UnpVer;
- unsigned int Method;
- unsigned int FileAttr;
- char *CmtBuf;
- unsigned int CmtBufSize;
- unsigned int CmtSize;
- unsigned int CmtState;
-};
-
-
-struct RARHeaderDataEx
-{
- char ArcName[1024];
- wchar_t ArcNameW[1024];
- char FileName[1024];
- wchar_t FileNameW[1024];
- unsigned int Flags;
- unsigned int PackSize;
- unsigned int PackSizeHigh;
- unsigned int UnpSize;
- unsigned int UnpSizeHigh;
- unsigned int HostOS;
- unsigned int FileCRC;
- unsigned int FileTime;
- unsigned int UnpVer;
- unsigned int Method;
- unsigned int FileAttr;
- char *CmtBuf;
- unsigned int CmtBufSize;
- unsigned int CmtSize;
- unsigned int CmtState;
- unsigned int Reserved[1024];
-};
-
-
-struct RAROpenArchiveData
-{
- char *ArcName;
- unsigned int OpenMode;
- unsigned int OpenResult;
- char *CmtBuf;
- unsigned int CmtBufSize;
- unsigned int CmtSize;
- unsigned int CmtState;
-};
-
-struct RAROpenArchiveDataEx
-{
- char *ArcName;
- wchar_t *ArcNameW;
- unsigned int OpenMode;
- unsigned int OpenResult;
- char *CmtBuf;
- unsigned int CmtBufSize;
- unsigned int CmtSize;
- unsigned int CmtState;
- unsigned int Flags;
- unsigned int Reserved[32];
-};
-
-enum UNRARCALLBACK_MESSAGES {
- UCM_CHANGEVOLUME,UCM_PROCESSDATA,UCM_NEEDPASSWORD
-};
-
-typedef int (CALLBACK *UNRARCALLBACK)(UINT msg,LPARAM UserData,LPARAM P1,LPARAM P2);
-
-typedef int (PASCAL *CHANGEVOLPROC)(char *ArcName,int Mode);
-typedef int (PASCAL *PROCESSDATAPROC)(unsigned char *Addr,int Size);
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-HANDLE PASCAL RAROpenArchive(struct RAROpenArchiveData *ArchiveData);
-HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *ArchiveData);
-int PASCAL RARCloseArchive(HANDLE hArcData);
-int PASCAL RARReadHeader(HANDLE hArcData,struct RARHeaderData *HeaderData);
-int PASCAL RARReadHeaderEx(HANDLE hArcData,struct RARHeaderDataEx *HeaderData);
-int PASCAL RARProcessFile(HANDLE hArcData,int Operation,char *DestPath,char *DestName);
-int PASCAL RARProcessFileW(HANDLE hArcData,int Operation,wchar_t *DestPath,wchar_t *DestName);
-void PASCAL RARSetCallback(HANDLE hArcData,UNRARCALLBACK Callback,LPARAM UserData);
-void PASCAL RARSetChangeVolProc(HANDLE hArcData,CHANGEVOLPROC ChangeVolProc);
-void PASCAL RARSetProcessDataProc(HANDLE hArcData,PROCESSDATAPROC ProcessDataProc);
-void PASCAL RARSetPassword(HANDLE hArcData,char *Password);
-int PASCAL RARGetDllVersion();
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/libs/unrar2/UnRARDLL/unrar.lib b/libs/unrar2/UnRARDLL/unrar.lib
deleted file mode 100644
index 0f6b314..0000000
Binary files a/libs/unrar2/UnRARDLL/unrar.lib and /dev/null differ
diff --git a/libs/unrar2/UnRARDLL/unrardll.txt b/libs/unrar2/UnRARDLL/unrardll.txt
deleted file mode 100644
index c49dd5b..0000000
--- a/libs/unrar2/UnRARDLL/unrardll.txt
+++ /dev/null
@@ -1,606 +0,0 @@
-
- UnRAR.dll Manual
- ~~~~~~~~~~~~~~~~
-
- UnRAR.dll is a 32-bit Windows dynamic-link library which provides
- file extraction from RAR archives.
-
-
- Exported functions
-
-====================================================================
-HANDLE PASCAL RAROpenArchive(struct RAROpenArchiveData *ArchiveData)
-====================================================================
-
-Description
-~~~~~~~~~~~
- Open RAR archive and allocate memory structures
-
-Parameters
-~~~~~~~~~~
-ArchiveData Points to RAROpenArchiveData structure
-
-struct RAROpenArchiveData
-{
- char *ArcName;
- UINT OpenMode;
- UINT OpenResult;
- char *CmtBuf;
- UINT CmtBufSize;
- UINT CmtSize;
- UINT CmtState;
-};
-
-Structure fields:
-
-ArcName
- Input parameter which should point to zero terminated string
- containing the archive name.
-
-OpenMode
- Input parameter.
-
- Possible values
-
- RAR_OM_LIST
- Open archive for reading file headers only.
-
- RAR_OM_EXTRACT
- Open archive for testing and extracting files.
-
- RAR_OM_LIST_INCSPLIT
- Open archive for reading file headers only. If you open an archive
- in such mode, RARReadHeader[Ex] will return all file headers,
- including those with "file continued from previous volume" flag.
- In case of RAR_OM_LIST such headers are automatically skipped.
- So if you process RAR volumes in RAR_OM_LIST_INCSPLIT mode, you will
- get several file header records for same file if file is split between
- volumes. For such files only the last file header record will contain
- the correct file CRC and if you wish to get the correct packed size,
- you need to sum up packed sizes of all parts.
-
-OpenResult
- Output parameter.
-
- Possible values
-
- 0 Success
- ERAR_NO_MEMORY Not enough memory to initialize data structures
- ERAR_BAD_DATA Archive header broken
- ERAR_BAD_ARCHIVE File is not valid RAR archive
- ERAR_UNKNOWN_FORMAT Unknown encryption used for archive headers
- ERAR_EOPEN File open error
-
-CmtBuf
- Input parameter which should point to the buffer for archive
- comments. Maximum comment size is limited to 64Kb. Comment text is
- zero terminated. If the comment text is larger than the buffer
- size, the comment text will be truncated. If CmtBuf is set to
- NULL, comments will not be read.
-
-CmtBufSize
- Input parameter which should contain size of buffer for archive
- comments.
-
-CmtSize
- Output parameter containing size of comments actually read into the
- buffer, cannot exceed CmtBufSize.
-
-CmtState
- Output parameter.
-
- Possible values
-
- 0 comments not present
- 1 Comments read completely
- ERAR_NO_MEMORY Not enough memory to extract comments
- ERAR_BAD_DATA Broken comment
- ERAR_UNKNOWN_FORMAT Unknown comment format
- ERAR_SMALL_BUF Buffer too small, comments not completely read
-
-Return values
-~~~~~~~~~~~~~
- Archive handle or NULL in case of error
-
-
-========================================================================
-HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *ArchiveData)
-========================================================================
-
-Description
-~~~~~~~~~~~
- Similar to RAROpenArchive, but uses RAROpenArchiveDataEx structure
- allowing to specify Unicode archive name and returning information
- about archive flags.
-
-Parameters
-~~~~~~~~~~
-ArchiveData Points to RAROpenArchiveDataEx structure
-
-struct RAROpenArchiveDataEx
-{
- char *ArcName;
- wchar_t *ArcNameW;
- unsigned int OpenMode;
- unsigned int OpenResult;
- char *CmtBuf;
- unsigned int CmtBufSize;
- unsigned int CmtSize;
- unsigned int CmtState;
- unsigned int Flags;
- unsigned int Reserved[32];
-};
-
-Structure fields:
-
-ArcNameW
- Input parameter which should point to zero terminated Unicode string
- containing the archive name or NULL if Unicode name is not specified.
-
-Flags
- Output parameter. Combination of bit flags.
-
- Possible values
-
- 0x0001 - Volume attribute (archive volume)
- 0x0002 - Archive comment present
- 0x0004 - Archive lock attribute
- 0x0008 - Solid attribute (solid archive)
- 0x0010 - New volume naming scheme ('volname.partN.rar')
- 0x0020 - Authenticity information present
- 0x0040 - Recovery record present
- 0x0080 - Block headers are encrypted
- 0x0100 - First volume (set only by RAR 3.0 and later)
-
-Reserved[32]
- Reserved for future use. Must be zero.
-
-Information on other structure fields and function return values
-is available above, in RAROpenArchive function description.
-
-
-====================================================================
-int PASCAL RARCloseArchive(HANDLE hArcData)
-====================================================================
-
-Description
-~~~~~~~~~~~
- Close RAR archive and release allocated memory. It must be called when
- archive processing is finished, even if the archive processing was stopped
- due to an error.
-
-Parameters
-~~~~~~~~~~
-hArcData
- This parameter should contain the archive handle obtained from the
- RAROpenArchive function call.
-
-Return values
-~~~~~~~~~~~~~
- 0 Success
- ERAR_ECLOSE Archive close error
-
-
-====================================================================
-int PASCAL RARReadHeader(HANDLE hArcData,
- struct RARHeaderData *HeaderData)
-====================================================================
-
-Description
-~~~~~~~~~~~
- Read header of file in archive.
-
-Parameters
-~~~~~~~~~~
-hArcData
- This parameter should contain the archive handle obtained from the
- RAROpenArchive function call.
-
-HeaderData
- It should point to RARHeaderData structure:
-
-struct RARHeaderData
-{
- char ArcName[260];
- char FileName[260];
- UINT Flags;
- UINT PackSize;
- UINT UnpSize;
- UINT HostOS;
- UINT FileCRC;
- UINT FileTime;
- UINT UnpVer;
- UINT Method;
- UINT FileAttr;
- char *CmtBuf;
- UINT CmtBufSize;
- UINT CmtSize;
- UINT CmtState;
-};
-
-Structure fields:
-
-ArcName
- Output parameter which contains a zero terminated string of the
- current archive name. May be used to determine the current volume
- name.
-
-FileName
- Output parameter which contains a zero terminated string of the
- file name in OEM (DOS) encoding.
-
-Flags
- Output parameter which contains file flags:
-
- 0x01 - file continued from previous volume
- 0x02 - file continued on next volume
- 0x04 - file encrypted with password
- 0x08 - file comment present
- 0x10 - compression of previous files is used (solid flag)
-
- bits 7 6 5
-
- 0 0 0 - dictionary size 64 Kb
- 0 0 1 - dictionary size 128 Kb
- 0 1 0 - dictionary size 256 Kb
- 0 1 1 - dictionary size 512 Kb
- 1 0 0 - dictionary size 1024 Kb
- 1 0 1 - dictionary size 2048 KB
- 1 1 0 - dictionary size 4096 KB
- 1 1 1 - file is directory
-
- Other bits are reserved.
-
-PackSize
- Output parameter means packed file size or size of the
- file part if file was split between volumes.
-
-UnpSize
- Output parameter - unpacked file size.
-
-HostOS
- Output parameter - operating system used for archiving:
-
- 0 - MS DOS;
- 1 - OS/2.
- 2 - Win32
- 3 - Unix
-
-FileCRC
- Output parameter which contains unpacked file CRC. In case of file parts
- split between volumes only the last part contains the correct CRC
- and it is accessible only in RAR_OM_LIST_INCSPLIT listing mode.
-
-FileTime
- Output parameter - contains date and time in standard MS DOS format.
-
-UnpVer
- Output parameter - RAR version needed to extract file.
- It is encoded as 10 * Major version + minor version.
-
-Method
- Output parameter - packing method.
-
-FileAttr
- Output parameter - file attributes.
-
-CmtBuf
- File comments support is not implemented in the new DLL version yet.
- Now CmtState is always 0.
-
-/*
- * Input parameter which should point to the buffer for file
- * comments. Maximum comment size is limited to 64Kb. Comment text is
- * a zero terminated string in OEM encoding. If the comment text is
- * larger than the buffer size, the comment text will be truncated.
- * If CmtBuf is set to NULL, comments will not be read.
- */
-
-CmtBufSize
- Input parameter which should contain size of buffer for archive
- comments.
-
-CmtSize
- Output parameter containing size of comments actually read into the
- buffer, should not exceed CmtBufSize.
-
-CmtState
- Output parameter.
-
- Possible values
-
- 0 Absent comments
- 1 Comments read completely
- ERAR_NO_MEMORY Not enough memory to extract comments
- ERAR_BAD_DATA Broken comment
- ERAR_UNKNOWN_FORMAT Unknown comment format
- ERAR_SMALL_BUF Buffer too small, comments not completely read
-
-Return values
-~~~~~~~~~~~~~
-
- 0 Success
- ERAR_END_ARCHIVE End of archive
- ERAR_BAD_DATA File header broken
-
-
-====================================================================
-int PASCAL RARReadHeaderEx(HANDLE hArcData,
- struct RARHeaderDataEx *HeaderData)
-====================================================================
-
-Description
-~~~~~~~~~~~
- Similar to RARReadHeader, but uses RARHeaderDataEx structure,
-containing information about Unicode file names and 64 bit file sizes.
-
-struct RARHeaderDataEx
-{
- char ArcName[1024];
- wchar_t ArcNameW[1024];
- char FileName[1024];
- wchar_t FileNameW[1024];
- unsigned int Flags;
- unsigned int PackSize;
- unsigned int PackSizeHigh;
- unsigned int UnpSize;
- unsigned int UnpSizeHigh;
- unsigned int HostOS;
- unsigned int FileCRC;
- unsigned int FileTime;
- unsigned int UnpVer;
- unsigned int Method;
- unsigned int FileAttr;
- char *CmtBuf;
- unsigned int CmtBufSize;
- unsigned int CmtSize;
- unsigned int CmtState;
- unsigned int Reserved[1024];
-};
-
-
-====================================================================
-int PASCAL RARProcessFile(HANDLE hArcData,
- int Operation,
- char *DestPath,
- char *DestName)
-====================================================================
-
-Description
-~~~~~~~~~~~
- Performs action and moves the current position in the archive to
- the next file. Extract or test the current file from the archive
- opened in RAR_OM_EXTRACT mode. If the mode RAR_OM_LIST is set,
- then a call to this function will simply skip the archive position
- to the next file.
-
-Parameters
-~~~~~~~~~~
-hArcData
- This parameter should contain the archive handle obtained from the
- RAROpenArchive function call.
-
-Operation
- File operation.
-
- Possible values
-
- RAR_SKIP Move to the next file in the archive. If the
- archive is solid and RAR_OM_EXTRACT mode was set
- when the archive was opened, the current file will
- be processed - the operation will be performed
- slower than a simple seek.
-
- RAR_TEST Test the current file and move to the next file in
- the archive. If the archive was opened with
- RAR_OM_LIST mode, the operation is equal to
- RAR_SKIP.
-
- RAR_EXTRACT Extract the current file and move to the next file.
- If the archive was opened with RAR_OM_LIST mode,
- the operation is equal to RAR_SKIP.
-
-
-DestPath
- This parameter should point to a zero terminated string containing the
- destination directory to which to extract files to. If DestPath is equal
- to NULL, it means extract to the current directory. This parameter has
- meaning only if DestName is NULL.
-
-DestName
- This parameter should point to a string containing the full path and name
- to assign to extracted file or it can be NULL to use the default name.
- If DestName is defined (not NULL), it overrides both the original file
- name saved in the archive and path specigied in DestPath setting.
-
- Both DestPath and DestName must be in OEM encoding. If necessary,
- use CharToOem to convert text to OEM before passing to this function.
-
-Return values
-~~~~~~~~~~~~~
- 0 Success
- ERAR_BAD_DATA File CRC error
- ERAR_BAD_ARCHIVE Volume is not valid RAR archive
- ERAR_UNKNOWN_FORMAT Unknown archive format
- ERAR_EOPEN Volume open error
- ERAR_ECREATE File create error
- ERAR_ECLOSE File close error
- ERAR_EREAD Read error
- ERAR_EWRITE Write error
-
-
-Note: if you wish to cancel extraction, return -1 when processing
- UCM_PROCESSDATA callback message.
-
-
-====================================================================
-int PASCAL RARProcessFileW(HANDLE hArcData,
- int Operation,
- wchar_t *DestPath,
- wchar_t *DestName)
-====================================================================
-
-Description
-~~~~~~~~~~~
- Unicode version of RARProcessFile. It uses Unicode DestPath
- and DestName parameters, other parameters and return values
- are the same as in RARProcessFile.
-
-
-====================================================================
-void PASCAL RARSetCallback(HANDLE hArcData,
- int PASCAL (*CallbackProc)(UINT msg,LPARAM UserData,LPARAM P1,LPARAM P2),
- LPARAM UserData);
-====================================================================
-
-Description
-~~~~~~~~~~~
- Set a user-defined callback function to process Unrar events.
-
-Parameters
-~~~~~~~~~~
-hArcData
- This parameter should contain the archive handle obtained from the
- RAROpenArchive function call.
-
-CallbackProc
- It should point to a user-defined callback function.
-
- The function will be passed four parameters:
-
-
- msg Type of event. Described below.
-
- UserData User defined value passed to RARSetCallback.
-
- P1 and P2 Event dependent parameters. Described below.
-
-
- Possible events
-
- UCM_CHANGEVOLUME Process volume change.
-
- P1 Points to the zero terminated name
- of the next volume.
-
- P2 The function call mode:
-
- RAR_VOL_ASK Required volume is absent. The function should
- prompt user and return a positive value
- to retry or return -1 value to terminate
- operation. The function may also specify a new
- volume name, placing it to the address specified
- by P1 parameter.
-
- RAR_VOL_NOTIFY Required volume is successfully opened.
- This is a notification call and volume name
- modification is not allowed. The function should
- return a positive value to continue or -1
- to terminate operation.
-
- UCM_PROCESSDATA Process unpacked data. It may be used to read
- a file while it is being extracted or tested
- without actual extracting file to disk.
- Return a positive value to continue process
- or -1 to cancel the archive operation
-
- P1 Address pointing to the unpacked data.
- Function may refer to the data but must not
- change it.
-
- P2 Size of the unpacked data. It is guaranteed
- only that the size will not exceed the maximum
- dictionary size (4 Mb in RAR 3.0).
-
- UCM_NEEDPASSWORD DLL needs a password to process archive.
- This message must be processed if you wish
- to be able to handle archives with encrypted
- file names. It can be also used as replacement
- of RARSetPassword function even for usual
- encrypted files with non-encrypted names.
-
- P1 Address pointing to the buffer for a password.
- You need to copy a password here.
-
- P2 Size of the password buffer.
-
-
-UserData
- User data passed to callback function.
-
- Other functions of UnRAR.dll should not be called from the callback
- function.
-
-Return values
-~~~~~~~~~~~~~
- None
-
-
-
-====================================================================
-void PASCAL RARSetChangeVolProc(HANDLE hArcData,
- int PASCAL (*ChangeVolProc)(char *ArcName,int Mode));
-====================================================================
-
-Obsoleted, use RARSetCallback instead.
-
-
-
-====================================================================
-void PASCAL RARSetProcessDataProc(HANDLE hArcData,
- int PASCAL (*ProcessDataProc)(unsigned char *Addr,int Size))
-====================================================================
-
-Obsoleted, use RARSetCallback instead.
-
-
-====================================================================
-void PASCAL RARSetPassword(HANDLE hArcData,
- char *Password);
-====================================================================
-
-Description
-~~~~~~~~~~~
- Set a password to decrypt files.
-
-Parameters
-~~~~~~~~~~
-hArcData
- This parameter should contain the archive handle obtained from the
- RAROpenArchive function call.
-
-Password
- It should point to a string containing a zero terminated password.
-
-Return values
-~~~~~~~~~~~~~
- None
-
-
-====================================================================
-void PASCAL RARGetDllVersion();
-====================================================================
-
-Description
-~~~~~~~~~~~
- Returns API version.
-
-Parameters
-~~~~~~~~~~
- None.
-
-Return values
-~~~~~~~~~~~~~
- Returns an integer value denoting UnRAR.dll API version, which is also
-defined in unrar.h as RAR_DLL_VERSION. API version number is incremented
-only in case of noticeable changes in UnRAR.dll API. Do not confuse it
-with version of UnRAR.dll stored in DLL resources, which is incremented
-with every DLL rebuild.
-
- If RARGetDllVersion() returns a value lower than UnRAR.dll which your
-application was designed for, it may indicate that DLL version is too old
-and it will fail to provide all necessary functions to your application.
-
- This function is absent in old versions of UnRAR.dll, so it is safer
-to use LoadLibrary and GetProcAddress to access this function.
-
diff --git a/libs/unrar2/UnRARDLL/whatsnew.txt b/libs/unrar2/UnRARDLL/whatsnew.txt
deleted file mode 100644
index 874d19b..0000000
--- a/libs/unrar2/UnRARDLL/whatsnew.txt
+++ /dev/null
@@ -1,80 +0,0 @@
-List of unrar.dll API changes. We do not include performance and reliability
-improvements into this list, but this library and RAR/UnRAR tools share
-the same source code. So the latest version of unrar.dll usually contains
-same decompression algorithm changes as the latest UnRAR version.
-============================================================================
-
--- 18 January 2008
-
-all LONG parameters of CallbackProc function were changed
-to LPARAM type for 64 bit mode compatibility.
-
-
--- 12 December 2007
-
-Added new RAR_OM_LIST_INCSPLIT open mode for function RAROpenArchive.
-
-
--- 14 August 2007
-
-Added NoCrypt\unrar_nocrypt.dll without decryption code for those
-applications where presence of encryption or decryption code is not
-allowed because of legal restrictions.
-
-
--- 14 December 2006
-
-Added ERAR_MISSING_PASSWORD error type. This error is returned
-if empty password is specified for encrypted file.
-
-
--- 12 June 2003
-
-Added RARProcessFileW function, Unicode version of RARProcessFile
-
-
--- 9 August 2002
-
-Added RAROpenArchiveEx function allowing to specify Unicode archive
-name and get archive flags.
-
-
--- 24 January 2002
-
-Added RARReadHeaderEx function allowing to read Unicode file names
-and 64 bit file sizes.
-
-
--- 23 January 2002
-
-Added ERAR_UNKNOWN error type (it is used for all errors which
-do not have special ERAR code yet) and UCM_NEEDPASSWORD callback
-message.
-
-Unrar.dll automatically opens all next volumes not only when extracting,
-but also in RAR_OM_LIST mode.
-
-
--- 27 November 2001
-
-RARSetChangeVolProc and RARSetProcessDataProc are replaced by
-the single callback function installed with RARSetCallback.
-Unlike old style callbacks, the new function accepts the user defined
-parameter. Unrar.dll still supports RARSetChangeVolProc and
-RARSetProcessDataProc for compatibility purposes, but if you write
-a new application, better use RARSetCallback.
-
-File comments support is not implemented in the new DLL version yet.
-Now CmtState is always 0.
-
-
--- 13 August 2001
-
-Added RARGetDllVersion function, so you may distinguish old unrar.dll,
-which used C style callback functions and the new one with PASCAL callbacks.
-
-
--- 10 May 2001
-
-Callback functions in RARSetChangeVolProc and RARSetProcessDataProc
-use PASCAL style call convention now.
diff --git a/libs/unrar2/UnRARDLL/x64/readme.txt b/libs/unrar2/UnRARDLL/x64/readme.txt
deleted file mode 100644
index bbfb340..0000000
--- a/libs/unrar2/UnRARDLL/x64/readme.txt
+++ /dev/null
@@ -1 +0,0 @@
-This is x64 version of unrar.dll.
diff --git a/libs/unrar2/UnRARDLL/x64/unrar64.lib b/libs/unrar2/UnRARDLL/x64/unrar64.lib
deleted file mode 100644
index fd03791..0000000
Binary files a/libs/unrar2/UnRARDLL/x64/unrar64.lib and /dev/null differ
diff --git a/libs/unrar2/license.txt b/libs/unrar2/license.txt
deleted file mode 100644
index a395801..0000000
--- a/libs/unrar2/license.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-Copyright (c) 2003-2005 Jimmy Retzlaff, 2008 Konstantin Yegupov
-
-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 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.
diff --git a/libs/unrar2/unix.py b/libs/unrar2/unix.py
index 9d87b18..21f384c 100644
--- a/libs/unrar2/unix.py
+++ b/libs/unrar2/unix.py
@@ -38,38 +38,38 @@ def call_unrar(params):
"Calls rar/unrar command line executable, returns stdout pipe"
global rar_executable_cached
if rar_executable_cached is None:
- for command in ('unrar', 'rar'):
+ for command in ('unrar', 'rar', os.path.join(os.path.dirname(__file__), 'unrar')):
try:
- subprocess.Popen([command], stdout=subprocess.PIPE)
+ subprocess.Popen([command], stdout = subprocess.PIPE)
rar_executable_cached = command
break
except OSError:
pass
if rar_executable_cached is None:
raise UnpackerNotInstalled("No suitable RAR unpacker installed")
-
+
assert type(params) == list, "params must be list"
args = [rar_executable_cached] + params
try:
gc.disable() # See http://bugs.python.org/issue1336
- return subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ return subprocess.Popen(args, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
finally:
gc.enable()
class RarFileImplementation(object):
- def init(self, password=None):
+ def init(self, password = None):
self.password = password
-
-
+
+
stdoutdata, stderrdata = self.call('v', []).communicate()
-
+
for line in stderrdata.splitlines():
if line.strip().startswith("Cannot open"):
raise FileOpenError
- if line.find("CRC failed")>=0:
- raise IncorrectRARPassword
+ if line.find("CRC failed") >= 0:
+ raise IncorrectRARPassword
accum = []
source = iter(stdoutdata.splitlines())
line = ''
@@ -85,39 +85,39 @@ class RarFileImplementation(object):
self.comment = '\n'.join(accum[:-1])
else:
self.comment = None
-
+
def escaped_password(self):
return '-' if self.password == None else self.password
-
-
- def call(self, cmd, options=[], files=[]):
- options2 = options + ['p'+self.escaped_password()]
- soptions = ['-'+x for x in options2]
- return call_unrar([cmd]+soptions+['--',self.archiveName]+files)
+
+
+ def call(self, cmd, options = [], files = []):
+ options2 = options + ['p' + self.escaped_password()]
+ soptions = ['-' + x for x in options2]
+ return call_unrar([cmd] + soptions + ['--', self.archiveName] + files)
def infoiter(self):
-
+
stdoutdata, stderrdata = self.call('v', ['c-']).communicate()
-
+
for line in stderrdata.splitlines():
if line.strip().startswith("Cannot open"):
raise FileOpenError
-
+
accum = []
source = iter(stdoutdata.splitlines())
line = ''
while not line.startswith('--------------'):
if line.strip().endswith('is not RAR archive'):
raise InvalidRARArchive
- if line.find("CRC failed")>=0:
- raise IncorrectRARPassword
+ if line.find("CRC failed") >= 0:
+ raise IncorrectRARPassword
line = source.next()
line = source.next()
i = 0
re_spaces = re.compile(r"\s+")
while not line.startswith('--------------'):
accum.append(line)
- if len(accum)==2:
+ if len(accum) == 2:
data = {}
data['index'] = i
data['filename'] = accum[0].strip()
@@ -125,7 +125,7 @@ class RarFileImplementation(object):
data['size'] = int(info[0])
attr = info[5]
data['isdir'] = 'd' in attr.lower()
- data['datetime'] = time.strptime(info[3]+" "+info[4], '%d-%m-%y %H:%M')
+ data['datetime'] = time.strptime(info[3] + " " + info[4], '%d-%m-%y %H:%M')
data['comment'] = None
yield data
accum = []
@@ -136,12 +136,12 @@ class RarFileImplementation(object):
res = []
for info in self.infoiter():
checkres = checker(info)
- if checkres==True and not info.isdir:
+ if checkres == True and not info.isdir:
pipe = self.call('p', ['inul'], [info.filename]).stdout
res.append((info, pipe.read()))
- return res
+ return res
+
-
def extract(self, checker, path, withSubpath, overwrite):
res = []
command = 'x'
@@ -159,17 +159,17 @@ class RarFileImplementation(object):
checkres = checker(info)
if type(checkres) in [str, unicode]:
raise NotImplementedError("Condition callbacks returning strings are deprecated and only supported in Windows")
- if checkres==True and not info.isdir:
+ if checkres == True and not info.isdir:
names.append(info.filename)
res.append(info)
names.append(path)
proc = self.call(command, options, names)
stdoutdata, stderrdata = proc.communicate()
- if stderrdata.find("CRC failed")>=0:
- raise IncorrectRARPassword
- return res
-
+ if stderrdata.find("CRC failed") >= 0:
+ raise IncorrectRARPassword
+ return res
+
def destruct(self):
pass
-
+
diff --git a/libs/unrar2/unrar b/libs/unrar2/unrar
new file mode 100755
index 0000000..3de1676
Binary files /dev/null and b/libs/unrar2/unrar differ
diff --git a/libs/unrar2/UnRARDLL/unrar.dll b/libs/unrar2/unrar.dll
similarity index 100%
rename from libs/unrar2/UnRARDLL/unrar.dll
rename to libs/unrar2/unrar.dll
diff --git a/libs/unrar2/UnRARDLL/x64/unrar64.dll b/libs/unrar2/unrar64.dll
similarity index 100%
rename from libs/unrar2/UnRARDLL/x64/unrar64.dll
rename to libs/unrar2/unrar64.dll
diff --git a/libs/unrar2/windows.py b/libs/unrar2/windows.py
index bb92481..e249f8f 100644
--- a/libs/unrar2/windows.py
+++ b/libs/unrar2/windows.py
@@ -23,10 +23,10 @@
# Low level interface - see UnRARDLL\UNRARDLL.TXT
from __future__ import generators
-
-import ctypes, ctypes.wintypes
-import os, os.path, sys
-import Queue
+from couchpotato.environment import Env
+from shutil import copyfile
+import ctypes.wintypes
+import os.path
import time
from rar_exceptions import *
@@ -64,13 +64,18 @@ UCM_NEEDPASSWORD = 2
architecture_bits = ctypes.sizeof(ctypes.c_voidp)*8
dll_name = "unrar.dll"
if architecture_bits == 64:
- dll_name = "x64\\unrar64.dll"
-
-
-try:
- unrar = ctypes.WinDLL(os.path.join(os.path.split(__file__)[0], 'UnRARDLL', dll_name))
-except WindowsError:
- unrar = ctypes.WinDLL(dll_name)
+ dll_name = "unrar64.dll"
+
+# Copy dll first
+dll_file = os.path.join(os.path.dirname(__file__), dll_name)
+dll_copy = os.path.join(Env.get('cache_dir'), 'copied.dll')
+
+if os.path.isfile(dll_copy):
+ os.remove(dll_copy)
+
+copyfile(dll_file, dll_copy)
+
+unrar = ctypes.WinDLL(dll_copy)
class RAROpenArchiveDataEx(ctypes.Structure):