Merge branch 'develop'
Conflicts: couchpotato/core/database.py
This commit is contained in:
@@ -29,17 +29,21 @@ OS X:
|
|||||||
* Then do `python CouchPotatoServer/CouchPotato.py`
|
* Then do `python CouchPotatoServer/CouchPotato.py`
|
||||||
* Your browser should open up, but if it doesn't go to `http://localhost:5050/`
|
* Your browser should open up, but if it doesn't go to `http://localhost:5050/`
|
||||||
|
|
||||||
Linux (Ubuntu / Debian):
|
Linux:
|
||||||
|
|
||||||
* Install [GIT](http://git-scm.com/) with `apt-get install git-core`
|
* (Ubuntu / Debian) Install [GIT](http://git-scm.com/) with `apt-get install git-core`
|
||||||
|
* (Fedora / CentOS) Install [GIT](http://git-scm.com/) with `yum install git`
|
||||||
* 'cd' to the folder of your choosing.
|
* 'cd' to the folder of your choosing.
|
||||||
* Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git`
|
* Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git`
|
||||||
* Then do `python CouchPotatoServer/CouchPotato.py` to start
|
* Then do `python CouchPotatoServer/CouchPotato.py` to start
|
||||||
* To run on boot copy the init script `sudo cp CouchPotatoServer/init/ubuntu /etc/init.d/couchpotato`
|
* (Ubuntu / Debian) To run on boot copy the init script `sudo cp CouchPotatoServer/init/ubuntu /etc/init.d/couchpotato`
|
||||||
* Copy the default paths file `sudo cp CouchPotatoServer/init/ubuntu.default /etc/default/couchpotato`
|
* (Ubuntu / Debian) Copy the default paths file `sudo cp CouchPotatoServer/init/ubuntu.default /etc/default/couchpotato`
|
||||||
* Change the paths inside the default file `sudo nano /etc/default/couchpotato`
|
* (Ubuntu / Debian) Change the paths inside the default file `sudo nano /etc/default/couchpotato`
|
||||||
* Make it executable `sudo chmod +x /etc/init.d/couchpotato`
|
* (Ubuntu / Debian) Make it executable `sudo chmod +x /etc/init.d/couchpotato`
|
||||||
* Add it to defaults `sudo update-rc.d couchpotato defaults`
|
* (Ubuntu / Debian) Add it to defaults `sudo update-rc.d couchpotato defaults`
|
||||||
|
* (systemd) To run on boot copy the systemd config `sudo cp CouchPotatoServer/init/couchpotato.fedora.service /etc/systemd/system/couchpotato.service`
|
||||||
|
* (systemd) Update the systemd config file with your user and path to CouchPotato.py
|
||||||
|
* (systemd) Enable it at boot with `sudo systemctl enable couchpotato`
|
||||||
* Open your browser and go to `http://localhost:5050/`
|
* Open your browser and go to `http://localhost:5050/`
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -13,6 +13,8 @@ Lastly, for anything related to CouchPotato, feel free to stop by the [forum](ht
|
|||||||
## Issues
|
## Issues
|
||||||
Issues are intended for reporting bugs and weird behaviour or suggesting improvements to CouchPotatoServer.
|
Issues are intended for reporting bugs and weird behaviour or suggesting improvements to CouchPotatoServer.
|
||||||
Before you submit an issue, please go through the following checklist:
|
Before you submit an issue, please go through the following checklist:
|
||||||
|
* **FILL IN ALL THE FIELDS ASKED FOR**
|
||||||
|
* **POST MORE THAN A SINGLE LINE LOG**, if you do, you'd better have a easy reproducable bug
|
||||||
* Search through existing issues (*including closed issues!*) first: you might be able to get your answer there.
|
* Search through existing issues (*including closed issues!*) first: you might be able to get your answer there.
|
||||||
* Double check your issue manually, because it could be an external issue.
|
* Double check your issue manually, because it could be an external issue.
|
||||||
* Post logs with your issue: Without seeing what is going on, the developers can't reproduce the error.
|
* Post logs with your issue: Without seeing what is going on, the developers can't reproduce the error.
|
||||||
@@ -25,12 +27,14 @@ Before you submit an issue, please go through the following checklist:
|
|||||||
* What hardware / OS are you using and what are its limitations? For example: NAS can be slow and maybe have a different version of python installed than when you use CP on OS X or Windows.
|
* What hardware / OS are you using and what are its limitations? For example: NAS can be slow and maybe have a different version of python installed than when you use CP on OS X or Windows.
|
||||||
* Your issue might be marked with the "can't reproduce" tag. Don't ask why your issue was closed if it says so in the tag.
|
* Your issue might be marked with the "can't reproduce" tag. Don't ask why your issue was closed if it says so in the tag.
|
||||||
* If you're running on a NAS (QNAP, Austor, Synology etc.) with pre-made packages, make sure these are set up to use our source repository (RuudBurger/CouchPotatoServer) and nothing else!
|
* If you're running on a NAS (QNAP, Austor, Synology etc.) with pre-made packages, make sure these are set up to use our source repository (RuudBurger/CouchPotatoServer) and nothing else!
|
||||||
|
* Do not "bump" issues with "Any updates on this" or whatever. Yes I've seen it, you don't have to remind me of it. There will be an update when the code is done or I need information. If you feel the need to do so, you'd better have more info on the issue.
|
||||||
|
|
||||||
The more relevant information you provide, the more likely that your issue will be resolved.
|
The more relevant information you provide, the more likely that your issue will be resolved.
|
||||||
|
If you don't follow any of the checks above, I'll close the issue. If you are wondering why (and ask) I'll block you from posting new issues and the repo.
|
||||||
|
|
||||||
## Pull Requests
|
## Pull Requests
|
||||||
Pull requests are intended for contributing code or documentation to the project. Before you submit a pull request, consider the following:
|
Pull requests are intended for contributing code or documentation to the project. Before you submit a pull request, consider the following:
|
||||||
* Make sure your pull request is made for the *develop* branch (or relevant feature branch).
|
* Make sure your pull request is made for the *develop* branch (or relevant feature branch).
|
||||||
* Have you tested your PR? If not, why?
|
* Have you tested your PR? If not, why?
|
||||||
* Does your PR have any limitations we should know of?
|
* Does your PR have any limitations I should know of?
|
||||||
* Is your PR up-to-date with the branch you're trying to push into?
|
* Is your PR up-to-date with the branch you're trying to push into?
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ class Updater(Plugin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def doShutdown(self, *args, **kwargs):
|
def doShutdown(self, *args, **kwargs):
|
||||||
if not Env.get('dev'):
|
if not Env.get('dev') and not Env.get('desktop'):
|
||||||
removePyc(Env.get('app_dir'), show_logs = False)
|
removePyc(Env.get('app_dir'), show_logs = False)
|
||||||
|
|
||||||
return super(Updater, self).doShutdown(*args, **kwargs)
|
return super(Updater, self).doShutdown(*args, **kwargs)
|
||||||
|
|||||||
@@ -238,6 +238,7 @@ class Database(object):
|
|||||||
old_db = os.path.join(Env.get('data_dir'), 'couchpotato.db.old')
|
old_db = os.path.join(Env.get('data_dir'), 'couchpotato.db.old')
|
||||||
|
|
||||||
if not os.path.isdir(failed_location) and os.path.isfile(old_db):
|
if not os.path.isdir(failed_location) and os.path.isfile(old_db):
|
||||||
|
log.error('Corrupt database, trying migrate again')
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
# Rename database folder
|
# Rename database folder
|
||||||
@@ -331,6 +332,8 @@ class Database(object):
|
|||||||
log.info('Getting data took %s', time.time() - migrate_start)
|
log.info('Getting data took %s', time.time() - migrate_start)
|
||||||
|
|
||||||
db = self.getDB()
|
db = self.getDB()
|
||||||
|
if not db.opened:
|
||||||
|
return
|
||||||
|
|
||||||
# Use properties
|
# Use properties
|
||||||
properties = migrate_data['properties']
|
properties = migrate_data['properties']
|
||||||
|
|||||||
@@ -23,16 +23,14 @@ class Transmission(DownloaderBase):
|
|||||||
log = CPLog(__name__)
|
log = CPLog(__name__)
|
||||||
trpc = None
|
trpc = None
|
||||||
|
|
||||||
def connect(self, reconnect = False):
|
def connect(self):
|
||||||
# Load host from config and split out port.
|
# Load host from config and split out port.
|
||||||
host = cleanHost(self.conf('host'), protocol = False).split(':')
|
host = cleanHost(self.conf('host'), protocol = False).split(':')
|
||||||
if not isInt(host[1]):
|
if not isInt(host[1]):
|
||||||
log.error('Config properties are not filled in correctly, port is missing.')
|
log.error('Config properties are not filled in correctly, port is missing.')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not self.trpc or reconnect:
|
self.trpc = TransmissionRPC(host[0], port = host[1], rpc_url = self.conf('rpc_url').strip('/ '), username = self.conf('username'), password = self.conf('password'))
|
||||||
self.trpc = TransmissionRPC(host[0], port = host[1], rpc_url = self.conf('rpc_url').strip('/ '), username = self.conf('username'), password = self.conf('password'))
|
|
||||||
|
|
||||||
return self.trpc
|
return self.trpc
|
||||||
|
|
||||||
def download(self, data = None, media = None, filedata = None):
|
def download(self, data = None, media = None, filedata = None):
|
||||||
@@ -88,7 +86,7 @@ class Transmission(DownloaderBase):
|
|||||||
return self.downloadReturnId(remote_torrent['torrent-added']['hashString'])
|
return self.downloadReturnId(remote_torrent['torrent-added']['hashString'])
|
||||||
|
|
||||||
def test(self):
|
def test(self):
|
||||||
if self.connect(True) and self.trpc.get_session():
|
if self.connect() and self.trpc.get_session():
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import time
|
|||||||
import traceback
|
import traceback
|
||||||
from string import ascii_lowercase
|
from string import ascii_lowercase
|
||||||
|
|
||||||
from CodernityDB.database import RecordNotFound
|
from CodernityDB.database import RecordNotFound, RecordDeleted
|
||||||
from couchpotato import tryInt, get_db
|
from couchpotato import tryInt, get_db
|
||||||
from couchpotato.api import addApiView
|
from couchpotato.api import addApiView
|
||||||
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
|
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
|
||||||
@@ -146,7 +146,7 @@ class MediaPlugin(MediaBase):
|
|||||||
|
|
||||||
return media
|
return media
|
||||||
|
|
||||||
except RecordNotFound:
|
except (RecordNotFound, RecordDeleted):
|
||||||
log.error('Media with id "%s" not found', media_id)
|
log.error('Media with id "%s" not found', media_id)
|
||||||
except:
|
except:
|
||||||
raise
|
raise
|
||||||
@@ -415,7 +415,7 @@ class MediaPlugin(MediaBase):
|
|||||||
db.delete(release)
|
db.delete(release)
|
||||||
total_deleted += 1
|
total_deleted += 1
|
||||||
|
|
||||||
if (total_releases == total_deleted and media['status'] != 'active') or (total_releases == 0 and not new_media_status) or (not new_media_status and delete_from == 'late'):
|
if (total_releases == total_deleted) or (total_releases == 0 and not new_media_status) or (not new_media_status and delete_from == 'late'):
|
||||||
db.delete(media)
|
db.delete(media)
|
||||||
deleted = True
|
deleted = True
|
||||||
elif new_media_status:
|
elif new_media_status:
|
||||||
@@ -488,18 +488,21 @@ class MediaPlugin(MediaBase):
|
|||||||
db.update(m)
|
db.update(m)
|
||||||
|
|
||||||
# Tag media as recent
|
# Tag media as recent
|
||||||
self.tag(media_id, 'recent')
|
self.tag(media_id, 'recent', update_edited = True)
|
||||||
|
|
||||||
return m['status']
|
return m['status']
|
||||||
except:
|
except:
|
||||||
log.error('Failed restatus: %s', traceback.format_exc())
|
log.error('Failed restatus: %s', traceback.format_exc())
|
||||||
|
|
||||||
def tag(self, media_id, tag):
|
def tag(self, media_id, tag, update_edited = False):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db = get_db()
|
db = get_db()
|
||||||
m = db.get('id', media_id)
|
m = db.get('id', media_id)
|
||||||
|
|
||||||
|
if update_edited:
|
||||||
|
m['last_edit'] = int(time.time())
|
||||||
|
|
||||||
tags = m.get('tags') or []
|
tags = m.get('tags') or []
|
||||||
if tag not in tags:
|
if tag not in tags:
|
||||||
tags.append(tag)
|
tags.append(tag)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class Base(NZBProvider, RSS):
|
|||||||
def _searchOnHost(self, host, media, quality, results):
|
def _searchOnHost(self, host, media, quality, results):
|
||||||
|
|
||||||
query = self.buildUrl(media, host)
|
query = self.buildUrl(media, host)
|
||||||
url = '%s&%s' % (self.getUrl(host['host']), query)
|
url = '%s%s' % (self.getUrl(host['host']), query)
|
||||||
nzbs = self.getRSSData(url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
nzbs = self.getRSSData(url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
|
||||||
|
|
||||||
for nzb in nzbs:
|
for nzb in nzbs:
|
||||||
@@ -83,7 +83,7 @@ class Base(NZBProvider, RSS):
|
|||||||
try:
|
try:
|
||||||
# Get details for extended description to retrieve passwords
|
# Get details for extended description to retrieve passwords
|
||||||
query = self.buildDetailsUrl(nzb_id, host['api_key'])
|
query = self.buildDetailsUrl(nzb_id, host['api_key'])
|
||||||
url = '%s&%s' % (self.getUrl(host['host']), query)
|
url = '%s%s' % (self.getUrl(host['host']), query)
|
||||||
nzb_details = self.getRSSData(url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})[0]
|
nzb_details = self.getRSSData(url, cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})[0]
|
||||||
|
|
||||||
description = self.getTextElement(nzb_details, 'description')
|
description = self.getTextElement(nzb_details, 'description')
|
||||||
@@ -220,7 +220,7 @@ config = [{
|
|||||||
'description': 'Enable <a href="http://newznab.com/" target="_blank">NewzNab</a> such as <a href="https://nzb.su" target="_blank">NZB.su</a>, \
|
'description': 'Enable <a href="http://newznab.com/" target="_blank">NewzNab</a> such as <a href="https://nzb.su" target="_blank">NZB.su</a>, \
|
||||||
<a href="https://nzbs.org" target="_blank">NZBs.org</a>, <a href="http://dognzb.cr/" target="_blank">DOGnzb.cr</a>, \
|
<a href="https://nzbs.org" target="_blank">NZBs.org</a>, <a href="http://dognzb.cr/" target="_blank">DOGnzb.cr</a>, \
|
||||||
<a href="https://github.com/spotweb/spotweb" target="_blank">Spotweb</a>, <a href="https://nzbgeek.info/" target="_blank">NZBGeek</a>, \
|
<a href="https://github.com/spotweb/spotweb" target="_blank">Spotweb</a>, <a href="https://nzbgeek.info/" target="_blank">NZBGeek</a>, \
|
||||||
<a href="https://smackdownonyou.com" target="_blank">SmackDown</a>, <a href="https://www.nzbfinder.ws" target="_blank">NZBFinder</a>',
|
<a href="https://www.nzbfinder.ws" target="_blank">NZBFinder</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAACVBMVEVjhwD///86aRovd/sBAAAAMklEQVQI12NgAIPQUCCRmQkjssDEShiRuRIqwZqZGcDAGBrqANUhGgIkWAOABKMDxCAA24UK50b26SAAAAAASUVORK5CYII=',
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAACVBMVEVjhwD///86aRovd/sBAAAAMklEQVQI12NgAIPQUCCRmQkjssDEShiRuRIqwZqZGcDAGBrqANUhGgIkWAOABKMDxCAA24UK50b26SAAAAAASUVORK5CYII=',
|
||||||
'options': [
|
'options': [
|
||||||
@@ -231,30 +231,30 @@ config = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'use',
|
'name': 'use',
|
||||||
'default': '0,0,0,0,0,0'
|
'default': '0,0,0,0,0'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'host',
|
'name': 'host',
|
||||||
'default': 'api.nzb.su,api.dognzb.cr,nzbs.org,https://index.nzbgeek.info, https://smackdownonyou.com, https://www.nzbfinder.ws',
|
'default': 'api.nzb.su,api.dognzb.cr,nzbs.org,https://api.nzbgeek.info,https://www.nzbfinder.ws',
|
||||||
'description': 'The hostname of your newznab provider',
|
'description': 'The hostname of your newznab provider',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'extra_score',
|
'name': 'extra_score',
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
'label': 'Extra Score',
|
'label': 'Extra Score',
|
||||||
'default': '0,0,0,0,0,0',
|
'default': '0,0,0,0,0',
|
||||||
'description': 'Starting score for each release found via this provider.',
|
'description': 'Starting score for each release found via this provider.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'custom_tag',
|
'name': 'custom_tag',
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
'label': 'Custom tag',
|
'label': 'Custom tag',
|
||||||
'default': ',,,,,',
|
'default': ',,,,',
|
||||||
'description': 'Add custom tags, for example add rls=1 to get only scene releases from nzbs.org',
|
'description': 'Add custom tags, for example add rls=1 to get only scene releases from nzbs.org',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'api_key',
|
'name': 'api_key',
|
||||||
'default': ',,,,,',
|
'default': ',,,,',
|
||||||
'label': 'Api Key',
|
'label': 'Api Key',
|
||||||
'description': 'Can be found on your profile page',
|
'description': 'Can be found on your profile page',
|
||||||
'type': 'combined',
|
'type': 'combined',
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class Base(TorrentProvider):
|
|||||||
'name': re.sub('[^A-Za-z0-9\-_ \(\).]+', '', '%s (%s) %s' % (name, year, torrent_desc)),
|
'name': re.sub('[^A-Za-z0-9\-_ \(\).]+', '', '%s (%s) %s' % (name, year, torrent_desc)),
|
||||||
'url': self.urls['download'] % (torrent_id, authkey, self.conf('passkey')),
|
'url': self.urls['download'] % (torrent_id, authkey, self.conf('passkey')),
|
||||||
'detail_url': self.urls['detail'] % torrent_id,
|
'detail_url': self.urls['detail'] % torrent_id,
|
||||||
'size': self.parseSize(entry.find('size').get_text()),
|
'size': tryInt(entry.find('size').get_text()) / 1048576,
|
||||||
'seeders': tryInt(entry.find('seeders').get_text()),
|
'seeders': tryInt(entry.find('seeders').get_text()),
|
||||||
'leechers': tryInt(entry.find('leechers').get_text()),
|
'leechers': tryInt(entry.find('leechers').get_text()),
|
||||||
'score': torrentscore
|
'score': torrentscore
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ log = CPLog(__name__)
|
|||||||
class Base(TorrentProvider):
|
class Base(TorrentProvider):
|
||||||
|
|
||||||
urls = {
|
urls = {
|
||||||
'test': 'https://torrentshack.net/',
|
'test': 'http://torrentshack.eu/',
|
||||||
'login': 'https://torrentshack.net/login.php',
|
'login': 'http://torrentshack.eu/login.php',
|
||||||
'login_check': 'https://torrentshack.net/inbox.php',
|
'login_check': 'http://torrentshack.eu/inbox.php',
|
||||||
'detail': 'https://torrentshack.net/torrent/%s',
|
'detail': 'http://torrentshack.eu/torrent/%s',
|
||||||
'search': 'https://torrentshack.net/torrents.php?action=advanced&searchstr=%s&scene=%s&filter_cat[%d]=1',
|
'search': 'http://torrentshack.eu/torrents.php?action=advanced&searchstr=%s&scene=%s&filter_cat[%d]=1',
|
||||||
'download': 'https://torrentshack.net/%s',
|
'download': 'http://torrentshack.eu/%s',
|
||||||
}
|
}
|
||||||
|
|
||||||
http_time_between_calls = 1 # Seconds
|
http_time_between_calls = 1 # Seconds
|
||||||
@@ -80,7 +80,7 @@ config = [{
|
|||||||
'tab': 'searcher',
|
'tab': 'searcher',
|
||||||
'list': 'torrent_providers',
|
'list': 'torrent_providers',
|
||||||
'name': 'TorrentShack',
|
'name': 'TorrentShack',
|
||||||
'description': '<a href="https://www.torrentshack.net/">TorrentShack</a>',
|
'description': '<a href="http://torrentshack.eu/">TorrentShack</a>',
|
||||||
'wizard': True,
|
'wizard': True,
|
||||||
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABmElEQVQoFQXBzY2cVRiE0afqvd84CQiAnxWWtyxsS6ThINBYg2Dc7mZBMEjE4mzs6e9WcY5+ePNuVFJJodQAoLo+SaWCy9rcV8cmjah3CI6iYu7oRU30kE5xxELRfamklY3k1NL19sSm7vPzP/ZdNZzKVDaY2sPZJBh9fv5ITrmG2+Vp4e1sPchVqTCQZJnVXi+/L4uuAJGly1+Pw8CprLbi8Om7tbT19/XRqJUk11JP9uHj9ulxhXbvJbI9qJvr5YkGXFG2IBT8tXczt+sfzDZCp3765f3t9tHEHGEDACma77+8o4oATKk+/PfW9YmHruRFjWoVSFsVsGu1YSKq6Oc37+n98unPZSRlY7vsKDqN+92X3yR9+PdXee3iJNKMStqdcZqoTJbUSi5JOkpfRlhSI0mSpEmCFKoU7FqSNOLAk54uGwCStMUCgLrVic62g7oDoFmmdI+P3S0pDe1xvDqb6XrZqbtzShWNoh9fv/XQHaDdM9OqrZi2M7M3UrB2vlkPS1IbdEBk7UiSoD6VlZ6aKWer4aH4f/AvKoHUTjuyAAAAAElFTkSuQmCC',
|
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABmElEQVQoFQXBzY2cVRiE0afqvd84CQiAnxWWtyxsS6ThINBYg2Dc7mZBMEjE4mzs6e9WcY5+ePNuVFJJodQAoLo+SaWCy9rcV8cmjah3CI6iYu7oRU30kE5xxELRfamklY3k1NL19sSm7vPzP/ZdNZzKVDaY2sPZJBh9fv5ITrmG2+Vp4e1sPchVqTCQZJnVXi+/L4uuAJGly1+Pw8CprLbi8Om7tbT19/XRqJUk11JP9uHj9ulxhXbvJbI9qJvr5YkGXFG2IBT8tXczt+sfzDZCp3765f3t9tHEHGEDACma77+8o4oATKk+/PfW9YmHruRFjWoVSFsVsGu1YSKq6Oc37+n98unPZSRlY7vsKDqN+92X3yR9+PdXee3iJNKMStqdcZqoTJbUSi5JOkpfRlhSI0mSpEmCFKoU7FqSNOLAk54uGwCStMUCgLrVic62g7oDoFmmdI+P3S0pDe1xvDqb6XrZqbtzShWNoh9fv/XQHaDdM9OqrZi2M7M3UrB2vlkPS1IbdEBk7UiSoD6VlZ6aKWer4aH4f/AvKoHUTjuyAAAAAElFTkSuQmCC',
|
||||||
'options': [
|
'options': [
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ autoload = 'TorrentLeech'
|
|||||||
class TorrentLeech(MovieProvider, Base):
|
class TorrentLeech(MovieProvider, Base):
|
||||||
|
|
||||||
cat_ids = [
|
cat_ids = [
|
||||||
([13], ['720p', '1080p']),
|
([13], ['720p', '1080p', 'bd50']),
|
||||||
([8], ['cam']),
|
([8], ['cam']),
|
||||||
([9], ['ts', 'tc']),
|
([9], ['ts', 'tc']),
|
||||||
([10], ['r5', 'scr']),
|
([10], ['r5', 'scr']),
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ class MovieSearcher(SearcherBase, MovieTypeBase):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if total_result_count > 0:
|
if total_result_count > 0:
|
||||||
fireEvent('media.tag', movie['_id'], 'recent', single = True)
|
fireEvent('media.tag', movie['_id'], 'recent', update_edited = True, single = True)
|
||||||
|
|
||||||
if len(too_early_to_search) > 0:
|
if len(too_early_to_search) > 0:
|
||||||
log.info2('Too early to search for %s, %s', (too_early_to_search, default_title))
|
log.info2('Too early to search for %s, %s', (too_early_to_search, default_title))
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ config = [{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'api_key',
|
'name': 'api_key',
|
||||||
'label': 'User API Key'
|
'label': 'Access Token',
|
||||||
|
'description': 'Can be found on <a href="https://www.pushbullet.com/account" target="_blank">Account Settings</a>',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'devices',
|
'name': 'devices',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from httplib import HTTPSConnection
|
from httplib import HTTPSConnection
|
||||||
|
|
||||||
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
||||||
from couchpotato.core.helpers.variable import getTitle
|
from couchpotato.core.helpers.variable import getTitle, getIdentifier
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.notifications.base import Notification
|
from couchpotato.core.notifications.base import Notification
|
||||||
|
|
||||||
@@ -27,9 +27,9 @@ class Pushover(Notification):
|
|||||||
'sound': self.conf('sound'),
|
'sound': self.conf('sound'),
|
||||||
}
|
}
|
||||||
|
|
||||||
if data and data.get('identifier'):
|
if data and getIdentifier(data):
|
||||||
api_data.update({
|
api_data.update({
|
||||||
'url': toUnicode('http://www.imdb.com/title/%s/' % data['identifier']),
|
'url': toUnicode('http://www.imdb.com/title/%s/' % getIdentifier(data)),
|
||||||
'url_title': toUnicode('%s on IMDb' % getTitle(data)),
|
'url_title': toUnicode('%s on IMDb' % getTitle(data)),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ class XBMC(Notification):
|
|||||||
log.debug('Returned from request %s: %s', (host, response))
|
log.debug('Returned from request %s: %s', (host, response))
|
||||||
|
|
||||||
return response
|
return response
|
||||||
except (MaxRetryError, requests.exceptions.Timeout):
|
except (MaxRetryError, requests.exceptions.Timeout, ConnectionError):
|
||||||
log.info2('Couldn\'t send request to XBMC, assuming it\'s turned off')
|
log.info2('Couldn\'t send request to XBMC, assuming it\'s turned off')
|
||||||
return []
|
return []
|
||||||
except:
|
except:
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ class Plugin(object):
|
|||||||
os.rmdir(full_path)
|
os.rmdir(full_path)
|
||||||
except:
|
except:
|
||||||
if show_error:
|
if show_error:
|
||||||
log.error('Couldn\'t remove empty directory %s: %s', (full_path, traceback.format_exc()))
|
log.info2('Couldn\'t remove directory %s: %s', (full_path, traceback.format_exc()))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.rmdir(folder)
|
os.rmdir(folder)
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ Running on: ...\n\
|
|||||||
'href': 'https://github.com/RuudBurger/CouchPotatoServer/blob/develop/contributing.md'
|
'href': 'https://github.com/RuudBurger/CouchPotatoServer/blob/develop/contributing.md'
|
||||||
}),
|
}),
|
||||||
new Element('span', {
|
new Element('span', {
|
||||||
'text': ' before posting, then copy the text below'
|
'text': ' before posting (kittens die if you don\'t), then copy the text below.'
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
textarea = new Element('textarea', {
|
textarea = new Element('textarea', {
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ class QualityPlugin(Plugin):
|
|||||||
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': ['br2dvd', ('dvd', 'r')], 'allow': [], 'ext':['iso', 'img', 'vob'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts', ('dvd', 'r'), 'dvd9']},
|
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': ['br2dvd', ('dvd', 'r')], 'allow': [], 'ext':['iso', 'img', 'vob'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts', ('dvd', 'r'), 'dvd9']},
|
||||||
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': [('dvd', 'rip')], 'allow': [], 'ext':['avi'], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]},
|
{'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': [('dvd', 'rip')], 'allow': [], 'ext':['avi'], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]},
|
||||||
{'identifier': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['screener', 'dvdscr', 'ppvrip', 'dvdscreener', 'hdscr'], 'allow': ['dvdr', 'dvdrip', '720p', '1080p'], 'ext':[], 'tags': ['webrip', ('web', 'rip')]},
|
{'identifier': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['screener', 'dvdscr', 'ppvrip', 'dvdscreener', 'hdscr'], 'allow': ['dvdr', 'dvdrip', '720p', '1080p'], 'ext':[], 'tags': ['webrip', ('web', 'rip')]},
|
||||||
{'identifier': 'r5', 'size': (600, 1000), 'label': 'R5', 'alternative': ['r6'], 'allow': ['dvdr'], 'ext':[]},
|
{'identifier': 'r5', 'size': (600, 1000), 'label': 'R5', 'alternative': ['r6'], 'allow': ['dvdr', '720p'], 'ext':[]},
|
||||||
{'identifier': 'tc', 'size': (600, 1000), 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': [], 'ext':[]},
|
{'identifier': 'tc', 'size': (600, 1000), 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': ['720p'], 'ext':[]},
|
||||||
{'identifier': 'ts', 'size': (600, 1000), 'label': 'TeleSync', 'alternative': ['telesync', 'hdts'], 'allow': [], 'ext':[]},
|
{'identifier': 'ts', 'size': (600, 1000), 'label': 'TeleSync', 'alternative': ['telesync', 'hdts'], 'allow': ['720p'], 'ext':[]},
|
||||||
{'identifier': 'cam', 'size': (600, 1000), 'label': 'Cam', 'alternative': ['camrip', 'hdcam'], 'allow': ['720p'], 'ext':[]}
|
{'identifier': 'cam', 'size': (600, 1000), 'label': 'Cam', 'alternative': ['camrip', 'hdcam'], 'allow': ['720p'], 'ext':[]}
|
||||||
]
|
]
|
||||||
pre_releases = ['cam', 'ts', 'tc', 'r5', 'scr']
|
pre_releases = ['cam', 'ts', 'tc', 'r5', 'scr']
|
||||||
@@ -454,6 +454,9 @@ class QualityPlugin(Plugin):
|
|||||||
'Moviename 2014 720p HDCAM XviD DualAudio': {'size': 4000, 'quality': 'cam'},
|
'Moviename 2014 720p HDCAM XviD DualAudio': {'size': 4000, 'quality': 'cam'},
|
||||||
'Moviename (2014) - 720p CAM x264': {'size': 2250, 'quality': 'cam'},
|
'Moviename (2014) - 720p CAM x264': {'size': 2250, 'quality': 'cam'},
|
||||||
'Movie Name (2014).mp4': {'size': 750, 'quality': 'brrip'},
|
'Movie Name (2014).mp4': {'size': 750, 'quality': 'brrip'},
|
||||||
|
'Moviename.2014.720p.R6.WEB-DL.x264.AC3-xyz': {'size': 750, 'quality': 'r5'},
|
||||||
|
'Movie name 2014 New Source 720p HDCAM x264 AC3 xyz': {'size': 750, 'quality': 'cam'},
|
||||||
|
'Movie.Name.2014.720p.HD.TS.AC3.x264': {'size': 750, 'quality': 'ts'}
|
||||||
}
|
}
|
||||||
|
|
||||||
correct = 0
|
correct = 0
|
||||||
|
|||||||
@@ -234,8 +234,9 @@ class Release(Plugin):
|
|||||||
db = get_db()
|
db = get_db()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rel = db.get('id', id, with_doc = True)
|
if id:
|
||||||
self.updateStatus(id, 'available' if rel['status'] in ['ignored', 'failed'] else 'ignored')
|
rel = db.get('id', id, with_doc = True)
|
||||||
|
self.updateStatus(id, 'available' if rel['status'] in ['ignored', 'failed'] else 'ignored')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'success': True
|
'success': True
|
||||||
@@ -327,7 +328,7 @@ class Release(Plugin):
|
|||||||
log_movie = '%s (%s) in %s' % (getTitle(media), media['info']['year'], rls['quality'])
|
log_movie = '%s (%s) in %s' % (getTitle(media), media['info']['year'], rls['quality'])
|
||||||
snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie)
|
snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie)
|
||||||
log.info(snatch_message)
|
log.info(snatch_message)
|
||||||
fireEvent('%s.snatched' % data['type'], message = snatch_message, data = rls)
|
fireEvent('%s.snatched' % data['type'], message = snatch_message, data = media)
|
||||||
|
|
||||||
# Mark release as snatched
|
# Mark release as snatched
|
||||||
if renamer_enabled:
|
if renamer_enabled:
|
||||||
@@ -364,7 +365,7 @@ class Release(Plugin):
|
|||||||
let_through = False
|
let_through = False
|
||||||
filtered_results = []
|
filtered_results = []
|
||||||
|
|
||||||
# If a single release comes through the "wait for", let through all
|
# Filter out ignored and other releases we don't want
|
||||||
for rel in results:
|
for rel in results:
|
||||||
|
|
||||||
if rel['status'] in ['ignored', 'failed']:
|
if rel['status'] in ['ignored', 'failed']:
|
||||||
@@ -379,6 +380,11 @@ class Release(Plugin):
|
|||||||
log.info('Ignored, size "%sMB" to low: %s', (rel['size'], rel['name']))
|
log.info('Ignored, size "%sMB" to low: %s', (rel['size'], rel['name']))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if 'seeders' in rel and rel.get('seeders') <= 0:
|
||||||
|
log.info('Ignored, no seeders: %s', (rel['name']))
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If a single release comes through the "wait for", let through all
|
||||||
rel['wait_for'] = False
|
rel['wait_for'] = False
|
||||||
if quality_custom.get('index') != 0 and quality_custom.get('wait_for', 0) > 0 and rel.get('age') <= quality_custom.get('wait_for', 0):
|
if quality_custom.get('index') != 0 and quality_custom.get('wait_for', 0) > 0 and rel.get('age') <= quality_custom.get('wait_for', 0):
|
||||||
rel['wait_for'] = True
|
rel['wait_for'] = True
|
||||||
|
|||||||
@@ -123,11 +123,6 @@ class Renamer(Plugin):
|
|||||||
no_process = [to_folder]
|
no_process = [to_folder]
|
||||||
cat_list = fireEvent('category.all', single = True) or []
|
cat_list = fireEvent('category.all', single = True) or []
|
||||||
no_process.extend([item['destination'] for item in cat_list])
|
no_process.extend([item['destination'] for item in cat_list])
|
||||||
try:
|
|
||||||
if Env.setting('library', section = 'manage').strip():
|
|
||||||
no_process.extend([sp(manage_folder) for manage_folder in splitString(Env.setting('library', section = 'manage'), '::')])
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Check to see if the no_process folders are inside the "from" folder.
|
# Check to see if the no_process folders are inside the "from" folder.
|
||||||
if not os.path.isdir(base_folder) or not os.path.isdir(to_folder):
|
if not os.path.isdir(base_folder) or not os.path.isdir(to_folder):
|
||||||
@@ -202,14 +197,18 @@ class Renamer(Plugin):
|
|||||||
db = get_db()
|
db = get_db()
|
||||||
|
|
||||||
# Extend the download info with info stored in the downloaded release
|
# Extend the download info with info stored in the downloaded release
|
||||||
|
keep_original = self.moveTypeIsLinked()
|
||||||
|
is_torrent = False
|
||||||
if release_download:
|
if release_download:
|
||||||
release_download = self.extendReleaseDownload(release_download)
|
release_download = self.extendReleaseDownload(release_download)
|
||||||
|
is_torrent = self.downloadIsTorrent(release_download)
|
||||||
|
keep_original = True if is_torrent and self.conf('file_action') not in ['move'] else keep_original
|
||||||
|
|
||||||
# Unpack any archives
|
# Unpack any archives
|
||||||
extr_files = None
|
extr_files = None
|
||||||
if self.conf('unrar'):
|
if self.conf('unrar'):
|
||||||
folder, media_folder, files, extr_files = self.extractFiles(folder = folder, media_folder = media_folder, files = files,
|
folder, media_folder, files, extr_files = self.extractFiles(folder = folder, media_folder = media_folder, files = files,
|
||||||
cleanup = self.conf('cleanup') and not self.downloadIsTorrent(release_download))
|
cleanup = self.conf('cleanup') and not keep_original)
|
||||||
|
|
||||||
groups = fireEvent('scanner.scan', folder = folder if folder else base_folder,
|
groups = fireEvent('scanner.scan', folder = folder if folder else base_folder,
|
||||||
files = files, release_download = release_download, return_ignored = False, single = True) or []
|
files = files, release_download = release_download, return_ignored = False, single = True) or []
|
||||||
@@ -326,7 +325,7 @@ class Renamer(Plugin):
|
|||||||
if file_type is 'nfo' and not self.conf('rename_nfo'):
|
if file_type is 'nfo' and not self.conf('rename_nfo'):
|
||||||
log.debug('Skipping, renaming of %s disabled', file_type)
|
log.debug('Skipping, renaming of %s disabled', file_type)
|
||||||
for current_file in group['files'][file_type]:
|
for current_file in group['files'][file_type]:
|
||||||
if self.conf('cleanup') and (not self.downloadIsTorrent(release_download) or self.fileIsAdded(current_file, group)):
|
if self.conf('cleanup') and (not keep_original or self.fileIsAdded(current_file, group)):
|
||||||
remove_files.append(current_file)
|
remove_files.append(current_file)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -523,7 +522,7 @@ class Renamer(Plugin):
|
|||||||
|
|
||||||
# Mark media for dashboard
|
# Mark media for dashboard
|
||||||
if mark_as_recent:
|
if mark_as_recent:
|
||||||
fireEvent('media.tag', group['media'].get('_id'), 'recent', single = True)
|
fireEvent('media.tag', group['media'].get('_id'), 'recent', update_edited = True, single = True)
|
||||||
|
|
||||||
# Remove leftover files
|
# Remove leftover files
|
||||||
if not remove_leftovers: # Don't remove anything
|
if not remove_leftovers: # Don't remove anything
|
||||||
@@ -532,7 +531,7 @@ class Renamer(Plugin):
|
|||||||
log.debug('Removing leftover files')
|
log.debug('Removing leftover files')
|
||||||
for current_file in group['files']['leftover']:
|
for current_file in group['files']['leftover']:
|
||||||
if self.conf('cleanup') and not self.conf('move_leftover') and \
|
if self.conf('cleanup') and not self.conf('move_leftover') and \
|
||||||
(not self.downloadIsTorrent(release_download) or self.fileIsAdded(current_file, group)):
|
(not keep_original or self.fileIsAdded(current_file, group)):
|
||||||
remove_files.append(current_file)
|
remove_files.append(current_file)
|
||||||
|
|
||||||
# Remove files
|
# Remove files
|
||||||
@@ -579,7 +578,7 @@ class Renamer(Plugin):
|
|||||||
self.makeDir(os.path.dirname(dst))
|
self.makeDir(os.path.dirname(dst))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.moveFile(src, dst, forcemove = not self.downloadIsTorrent(release_download) or self.fileIsAdded(src, group))
|
self.moveFile(src, dst, use_default = not is_torrent or self.fileIsAdded(src, group))
|
||||||
group['renamed_files'].append(dst)
|
group['renamed_files'].append(dst)
|
||||||
except:
|
except:
|
||||||
log.error('Failed renaming the file "%s" : %s', (os.path.basename(src), traceback.format_exc()))
|
log.error('Failed renaming the file "%s" : %s', (os.path.basename(src), traceback.format_exc()))
|
||||||
@@ -595,7 +594,7 @@ class Renamer(Plugin):
|
|||||||
self.untagRelease(group = group, tag = 'failed_rename')
|
self.untagRelease(group = group, tag = 'failed_rename')
|
||||||
|
|
||||||
# Tag folder if it is in the 'from' folder and it will not be removed because it is a torrent
|
# Tag folder if it is in the 'from' folder and it will not be removed because it is a torrent
|
||||||
if self.movieInFromFolder(media_folder) and self.downloadIsTorrent(release_download):
|
if self.movieInFromFolder(media_folder) and keep_original:
|
||||||
self.tagRelease(group = group, tag = 'renamed_already')
|
self.tagRelease(group = group, tag = 'renamed_already')
|
||||||
|
|
||||||
# Remove matching releases
|
# Remove matching releases
|
||||||
@@ -606,7 +605,7 @@ class Renamer(Plugin):
|
|||||||
except:
|
except:
|
||||||
log.error('Failed removing %s: %s', (release, traceback.format_exc()))
|
log.error('Failed removing %s: %s', (release, traceback.format_exc()))
|
||||||
|
|
||||||
if group['dirname'] and group['parentdir'] and not self.downloadIsTorrent(release_download):
|
if group['dirname'] and group['parentdir'] and not keep_original:
|
||||||
if media_folder:
|
if media_folder:
|
||||||
# Delete the movie folder
|
# Delete the movie folder
|
||||||
group_folder = media_folder
|
group_folder = media_folder
|
||||||
@@ -768,10 +767,15 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def moveFile(self, old, dest, forcemove = False):
|
def moveFile(self, old, dest, use_default = False):
|
||||||
dest = sp(dest)
|
dest = sp(dest)
|
||||||
try:
|
try:
|
||||||
if forcemove or self.conf('file_action') not in ['copy', 'link']:
|
|
||||||
|
move_type = self.conf('file_action')
|
||||||
|
if use_default:
|
||||||
|
move_type = self.conf('default_file_action')
|
||||||
|
|
||||||
|
if move_type not in ['copy', 'link']:
|
||||||
try:
|
try:
|
||||||
shutil.move(old, dest)
|
shutil.move(old, dest)
|
||||||
except:
|
except:
|
||||||
@@ -780,16 +784,16 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
|||||||
os.unlink(old)
|
os.unlink(old)
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
elif self.conf('file_action') == 'copy':
|
elif move_type == 'copy':
|
||||||
shutil.copy(old, dest)
|
shutil.copy(old, dest)
|
||||||
elif self.conf('file_action') == 'link':
|
else:
|
||||||
# First try to hardlink
|
# First try to hardlink
|
||||||
try:
|
try:
|
||||||
log.debug('Hardlinking file "%s" to "%s"...', (old, dest))
|
log.debug('Hardlinking file "%s" to "%s"...', (old, dest))
|
||||||
link(old, dest)
|
link(old, dest)
|
||||||
except:
|
except:
|
||||||
# Try to simlink next
|
# Try to simlink next
|
||||||
log.debug('Couldn\'t hardlink file "%s" to "%s". Simlinking instead. Error: %s.', (old, dest, traceback.format_exc()))
|
log.debug('Couldn\'t hardlink file "%s" to "%s". Symlinking instead. Error: %s.', (old, dest, traceback.format_exc()))
|
||||||
shutil.copy(old, dest)
|
shutil.copy(old, dest)
|
||||||
try:
|
try:
|
||||||
symlink(dest, old + '.link')
|
symlink(dest, old + '.link')
|
||||||
@@ -803,7 +807,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
|||||||
if os.name == 'nt' and self.conf('ntfs_permission'):
|
if os.name == 'nt' and self.conf('ntfs_permission'):
|
||||||
os.popen('icacls "' + dest + '"* /reset /T')
|
os.popen('icacls "' + dest + '"* /reset /T')
|
||||||
except:
|
except:
|
||||||
log.error('Failed setting permissions for file: %s, %s', (dest, traceback.format_exc(1)))
|
log.debug('Failed setting permissions for file: %s, %s', (dest, traceback.format_exc(1)))
|
||||||
except:
|
except:
|
||||||
log.error('Couldn\'t move file "%s" to "%s": %s', (old, dest, traceback.format_exc()))
|
log.error('Couldn\'t move file "%s" to "%s": %s', (old, dest, traceback.format_exc()))
|
||||||
raise
|
raise
|
||||||
@@ -1089,6 +1093,9 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
|||||||
return False
|
return False
|
||||||
return src in group['before_rename']
|
return src in group['before_rename']
|
||||||
|
|
||||||
|
def moveTypeIsLinked(self):
|
||||||
|
return self.conf('default_file_action') in ['copy', 'link']
|
||||||
|
|
||||||
def statusInfoComplete(self, release_download):
|
def statusInfoComplete(self, release_download):
|
||||||
return release_download.get('id') and release_download.get('downloader') and release_download.get('folder')
|
return release_download.get('id') and release_download.get('downloader') and release_download.get('folder')
|
||||||
|
|
||||||
@@ -1140,14 +1147,20 @@ Remove it if you want it to be renamed (again, or at least let it try again)
|
|||||||
|
|
||||||
log.info('Archive %s found. Extracting...', os.path.basename(archive['file']))
|
log.info('Archive %s found. Extracting...', os.path.basename(archive['file']))
|
||||||
try:
|
try:
|
||||||
rar_handle = RarFile(archive['file'])
|
rar_handle = RarFile(archive['file'], custom_path = self.conf('unrar_path'))
|
||||||
extr_path = os.path.join(from_folder, os.path.relpath(os.path.dirname(archive['file']), folder))
|
extr_path = os.path.join(from_folder, os.path.relpath(os.path.dirname(archive['file']), folder))
|
||||||
self.makeDir(extr_path)
|
self.makeDir(extr_path)
|
||||||
for packedinfo in rar_handle.infolist():
|
for packedinfo in rar_handle.infolist():
|
||||||
if not packedinfo.isdir and not os.path.isfile(sp(os.path.join(extr_path, os.path.basename(packedinfo.filename)))):
|
extr_file_path = sp(os.path.join(extr_path, os.path.basename(packedinfo.filename)))
|
||||||
|
if not packedinfo.isdir and not os.path.isfile(extr_file_path):
|
||||||
log.debug('Extracting %s...', packedinfo.filename)
|
log.debug('Extracting %s...', packedinfo.filename)
|
||||||
rar_handle.extract(condition = [packedinfo.index], path = extr_path, withSubpath = False, overwrite = False)
|
rar_handle.extract(condition = [packedinfo.index], path = extr_path, withSubpath = False, overwrite = False)
|
||||||
extr_files.append(sp(os.path.join(extr_path, os.path.basename(packedinfo.filename))))
|
if self.conf('unrar_modify_date'):
|
||||||
|
try:
|
||||||
|
os.utime(extr_file_path, (os.path.getatime(archive['file']), os.path.getmtime(archive['file'])))
|
||||||
|
except:
|
||||||
|
log.error('Rar modify date enabled, but failed: %s', traceback.format_exc())
|
||||||
|
extr_files.append(extr_file_path)
|
||||||
del rar_handle
|
del rar_handle
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error('Failed to extract %s: %s %s', (archive['file'], e, traceback.format_exc()))
|
log.error('Failed to extract %s: %s %s', (archive['file'], e, traceback.format_exc()))
|
||||||
@@ -1282,6 +1295,18 @@ config = [{
|
|||||||
'description': 'Extract rar files if found.',
|
'description': 'Extract rar files if found.',
|
||||||
'default': False,
|
'default': False,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'advanced': True,
|
||||||
|
'name': 'unrar_path',
|
||||||
|
'description': 'Custom path to unrar bin',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'advanced': True,
|
||||||
|
'name': 'unrar_modify_date',
|
||||||
|
'type': 'bool',
|
||||||
|
'description': ('Set modify date of unrar-ed files to the rar-file\'s date.', 'This will allow XBMC to recognize extracted files as recently added even if the movie was released some time ago.'),
|
||||||
|
'default': False,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': 'cleanup',
|
'name': 'cleanup',
|
||||||
'type': 'bool',
|
'type': 'bool',
|
||||||
@@ -1332,14 +1357,23 @@ config = [{
|
|||||||
'label': 'Folder-Separator',
|
'label': 'Folder-Separator',
|
||||||
'description': ('Replace all the spaces with a character.', 'Example: ".", "-" (without quotes). Leave empty to use spaces.'),
|
'description': ('Replace all the spaces with a character.', 'Example: ".", "-" (without quotes). Leave empty to use spaces.'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'default_file_action',
|
||||||
|
'label': 'Default File Action',
|
||||||
|
'default': 'move',
|
||||||
|
'type': 'dropdown',
|
||||||
|
'values': [('Link', 'link'), ('Copy', 'copy'), ('Move', 'move')],
|
||||||
|
'description': ('<strong>Link</strong>, <strong>Copy</strong> or <strong>Move</strong> after download completed.',
|
||||||
|
'Link first tries <a href="http://en.wikipedia.org/wiki/Hard_link">hard link</a>, then <a href="http://en.wikipedia.org/wiki/Sym_link">sym link</a> and falls back to Copy.'),
|
||||||
|
'advanced': True,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': 'file_action',
|
'name': 'file_action',
|
||||||
'label': 'Torrent File Action',
|
'label': 'Torrent File Action',
|
||||||
'default': 'link',
|
'default': 'link',
|
||||||
'type': 'dropdown',
|
'type': 'dropdown',
|
||||||
'values': [('Link', 'link'), ('Copy', 'copy'), ('Move', 'move')],
|
'values': [('Link', 'link'), ('Copy', 'copy'), ('Move', 'move')],
|
||||||
'description': ('<strong>Link</strong>, <strong>Copy</strong> or <strong>Move</strong> after download completed.',
|
'description': 'See above. It is prefered to use link when downloading torrents as it will save you space, while still beeing able to seed.',
|
||||||
'Link first tries <a href="http://en.wikipedia.org/wiki/Hard_link">hard link</a>, then <a href="http://en.wikipedia.org/wiki/Sym_link">sym link</a> and falls back to Copy. It is perfered to use link when downloading torrents as it will save you space, while still beeing able to seed.'),
|
|
||||||
'advanced': True,
|
'advanced': True,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -553,7 +553,7 @@ class Scanner(Plugin):
|
|||||||
scan_result = []
|
scan_result = []
|
||||||
for p in paths:
|
for p in paths:
|
||||||
if not group['is_dvd']:
|
if not group['is_dvd']:
|
||||||
video = Video.from_path(toUnicode(p))
|
video = Video.from_path(sp(p))
|
||||||
video_result = [(video, video.scan())]
|
video_result = [(video, video.scan())]
|
||||||
scan_result.extend(video_result)
|
scan_result.extend(video_result)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
|
||||||
{% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'front', single = True) %}
|
{% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'front', single = True) %}
|
||||||
<link rel="stylesheet" href="{{ Env.get('web_base') }}{{ url }}" type="text/css">{% end %}
|
<link rel="stylesheet" href="{{ Env.get('web_base') }}{{ url }}" type="text/css">{% end %}
|
||||||
|
|||||||
@@ -4,22 +4,24 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
|
||||||
{% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'front', single = True) %}
|
{% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'front', single = True) %}
|
||||||
<link rel="stylesheet" href="{{ Env.get('web_base') }}{{ url }}" type="text/css">{% end %}
|
<link rel="stylesheet" href="{{ Env.get('web_base') }}{{ url }}" type="text/css">{% end %}
|
||||||
|
|
||||||
{% for url in fireEvent('clientscript.get_scripts', as_html = True, location = 'front', single = True) %}
|
{% for url in fireEvent('clientscript.get_scripts', as_html = True, location = 'front', single = True) %}
|
||||||
<script type="text/javascript" src="{{ Env.get('web_base') }}{{ url }}"></script>{% end %}
|
<script type="text/javascript" src="{{ Env.get('web_base') }}{{ url }}"></script>{% end %}
|
||||||
|
|
||||||
<link href="{{ Env.get('static_path') }}images/favicon.ico" rel="icon" type="image/x-icon" />
|
<link href="{{ Env.get('static_path') }}images/favicon.ico" rel="icon" type="image/x-icon" />
|
||||||
<link rel="apple-touch-icon" href="{{ Env.get('static_path') }}images/homescreen.png" />
|
<link rel="apple-touch-icon" href="{{ Env.get('static_path') }}images/homescreen.png" />
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
window.addEvent('domready', function(){
|
window.addEvent('domready', function(){
|
||||||
new Form.Check($('remember_me'));
|
new Form.Check($('remember_me'));
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<title>CouchPotato</title>
|
<title>CouchPotato</title>
|
||||||
@@ -35,4 +37,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+14
-14
@@ -21,7 +21,7 @@
|
|||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pyUnRAR2 is a ctypes based wrapper around the free UnRAR.dll.
|
pyUnRAR2 is a ctypes based wrapper around the free UnRAR.dll.
|
||||||
|
|
||||||
It is an modified version of Jimmy Retzlaff's pyUnRAR - more simple,
|
It is an modified version of Jimmy Retzlaff's pyUnRAR - more simple,
|
||||||
stable and foolproof.
|
stable and foolproof.
|
||||||
@@ -45,8 +45,8 @@ if in_windows:
|
|||||||
from windows import RarFileImplementation
|
from windows import RarFileImplementation
|
||||||
else:
|
else:
|
||||||
from unix import RarFileImplementation
|
from unix import RarFileImplementation
|
||||||
|
|
||||||
|
|
||||||
import fnmatch, time, weakref
|
import fnmatch, time, weakref
|
||||||
|
|
||||||
class RarInfo(object):
|
class RarInfo(object):
|
||||||
@@ -62,7 +62,7 @@ class RarInfo(object):
|
|||||||
isdir - True if the file is a directory
|
isdir - True if the file is a directory
|
||||||
size - size in bytes of the uncompressed file
|
size - size in bytes of the uncompressed file
|
||||||
comment - comment associated with the file
|
comment - comment associated with the file
|
||||||
|
|
||||||
Note - this is not currently intended to be a Python file-like object.
|
Note - this is not currently intended to be a Python file-like object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ class RarInfo(object):
|
|||||||
self.size = data['size']
|
self.size = data['size']
|
||||||
self.datetime = data['datetime']
|
self.datetime = data['datetime']
|
||||||
self.comment = data['comment']
|
self.comment = data['comment']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -86,7 +86,7 @@ class RarInfo(object):
|
|||||||
|
|
||||||
class RarFile(RarFileImplementation):
|
class RarFile(RarFileImplementation):
|
||||||
|
|
||||||
def __init__(self, archiveName, password=None):
|
def __init__(self, archiveName, password=None, custom_path = None):
|
||||||
"""Instantiate the archive.
|
"""Instantiate the archive.
|
||||||
|
|
||||||
archiveName is the name of the RAR file.
|
archiveName is the name of the RAR file.
|
||||||
@@ -99,7 +99,7 @@ class RarFile(RarFileImplementation):
|
|||||||
This is a test.
|
This is a test.
|
||||||
"""
|
"""
|
||||||
self.archiveName = archiveName
|
self.archiveName = archiveName
|
||||||
RarFileImplementation.init(self, password)
|
RarFileImplementation.init(self, password, custom_path)
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.destruct()
|
self.destruct()
|
||||||
@@ -130,31 +130,31 @@ class RarFile(RarFileImplementation):
|
|||||||
"""Read specific files from archive into memory.
|
"""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 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 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
|
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).
|
and returns boolean True (extract) or False (skip).
|
||||||
If "condition" is omitted, all files are returned.
|
If "condition" is omitted, all files are returned.
|
||||||
|
|
||||||
Returns list of tuples (RarInfo info, str contents)
|
Returns list of tuples (RarInfo info, str contents)
|
||||||
"""
|
"""
|
||||||
checker = condition2checker(condition)
|
checker = condition2checker(condition)
|
||||||
return RarFileImplementation.read_files(self, checker)
|
return RarFileImplementation.read_files(self, checker)
|
||||||
|
|
||||||
|
|
||||||
def extract(self, condition='*', path='.', withSubpath=True, overwrite=True):
|
def extract(self, condition='*', path='.', withSubpath=True, overwrite=True):
|
||||||
"""Extract specific files from archive to disk.
|
"""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 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 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
|
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).
|
and returns either boolean True (extract) or boolean False (skip).
|
||||||
DEPRECATED: If "condition" callback returns string (only supported for Windows) -
|
DEPRECATED: If "condition" callback returns string (only supported for Windows) -
|
||||||
that string will be used as a new name to save the file under.
|
that string will be used as a new name to save the file under.
|
||||||
If "condition" is omitted, all files are extracted.
|
If "condition" is omitted, all files are extracted.
|
||||||
|
|
||||||
"path" is a directory to extract to
|
"path" is a directory to extract to
|
||||||
"withSubpath" flag denotes whether files are extracted with their full path in the archive.
|
"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.
|
"overwrite" flag denotes whether extracted files will overwrite old ones. Defaults to true.
|
||||||
|
|
||||||
Returns list of RarInfos for extracted files."""
|
Returns list of RarInfos for extracted files."""
|
||||||
checker = condition2checker(condition)
|
checker = condition2checker(condition)
|
||||||
return RarFileImplementation.extract(self, checker, path, withSubpath, overwrite)
|
return RarFileImplementation.extract(self, checker, path, withSubpath, overwrite)
|
||||||
|
|||||||
+21
-9
@@ -21,25 +21,37 @@
|
|||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
# Unix version uses unrar command line executable
|
# Unix version uses unrar command line executable
|
||||||
|
import platform
|
||||||
|
import stat
|
||||||
import subprocess
|
import subprocess
|
||||||
import gc
|
import gc
|
||||||
|
import os
|
||||||
import os, os.path
|
import os.path
|
||||||
import time, re
|
import time
|
||||||
|
import re
|
||||||
|
|
||||||
from rar_exceptions import *
|
from rar_exceptions import *
|
||||||
|
|
||||||
|
|
||||||
class UnpackerNotInstalled(Exception): pass
|
class UnpackerNotInstalled(Exception): pass
|
||||||
|
|
||||||
rar_executable_cached = None
|
rar_executable_cached = None
|
||||||
rar_executable_version = None
|
rar_executable_version = None
|
||||||
|
|
||||||
def call_unrar(params):
|
osx_unrar = os.path.join(os.path.dirname(__file__), 'unrar')
|
||||||
|
if os.path.isfile(osx_unrar) and 'darwin' in platform.platform().lower():
|
||||||
|
try:
|
||||||
|
st = os.stat(osx_unrar)
|
||||||
|
os.chmod(osx_unrar, st.st_mode | stat.S_IEXEC)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def call_unrar(params, custom_path = None):
|
||||||
"Calls rar/unrar command line executable, returns stdout pipe"
|
"Calls rar/unrar command line executable, returns stdout pipe"
|
||||||
global rar_executable_cached
|
global rar_executable_cached
|
||||||
if rar_executable_cached is None:
|
if rar_executable_cached is None:
|
||||||
for command in ('unrar', 'rar', os.path.join(os.path.dirname(__file__), 'unrar')):
|
for command in (custom_path, 'unrar', 'rar', osx_unrar):
|
||||||
|
if not command: continue
|
||||||
try:
|
try:
|
||||||
subprocess.Popen([command], stdout = subprocess.PIPE)
|
subprocess.Popen([command], stdout = subprocess.PIPE)
|
||||||
rar_executable_cached = command
|
rar_executable_cached = command
|
||||||
@@ -59,10 +71,10 @@ def call_unrar(params):
|
|||||||
|
|
||||||
class RarFileImplementation(object):
|
class RarFileImplementation(object):
|
||||||
|
|
||||||
def init(self, password = None):
|
def init(self, password = None, custom_path = None):
|
||||||
global rar_executable_version
|
global rar_executable_version
|
||||||
self.password = password
|
self.password = password
|
||||||
|
self.custom_path = custom_path
|
||||||
|
|
||||||
stdoutdata, stderrdata = self.call('v', []).communicate()
|
stdoutdata, stderrdata = self.call('v', []).communicate()
|
||||||
|
|
||||||
@@ -118,7 +130,7 @@ class RarFileImplementation(object):
|
|||||||
def call(self, cmd, options = [], files = []):
|
def call(self, cmd, options = [], files = []):
|
||||||
options2 = options + ['p' + self.escaped_password()]
|
options2 = options + ['p' + self.escaped_password()]
|
||||||
soptions = ['-' + x for x in options2]
|
soptions = ['-' + x for x in options2]
|
||||||
return call_unrar([cmd] + soptions + ['--', self.archiveName] + files)
|
return call_unrar([cmd] + soptions + ['--', self.archiveName] + files, self.custom_path)
|
||||||
|
|
||||||
def infoiter(self):
|
def infoiter(self):
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -174,7 +174,7 @@ class PassiveReader:
|
|||||||
def __init__(self, usercallback = None):
|
def __init__(self, usercallback = None):
|
||||||
self.buf = []
|
self.buf = []
|
||||||
self.ucb = usercallback
|
self.ucb = usercallback
|
||||||
|
|
||||||
def _callback(self, msg, UserData, P1, P2):
|
def _callback(self, msg, UserData, P1, P2):
|
||||||
if msg == UCM_PROCESSDATA:
|
if msg == UCM_PROCESSDATA:
|
||||||
data = (ctypes.c_char*P2).from_address(P1).raw
|
data = (ctypes.c_char*P2).from_address(P1).raw
|
||||||
@@ -183,7 +183,7 @@ class PassiveReader:
|
|||||||
else:
|
else:
|
||||||
self.buf.append(data)
|
self.buf.append(data)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def get_result(self):
|
def get_result(self):
|
||||||
return ''.join(self.buf)
|
return ''.join(self.buf)
|
||||||
|
|
||||||
@@ -197,10 +197,10 @@ class RarInfoIterator(object):
|
|||||||
raise IncorrectRARPassword
|
raise IncorrectRARPassword
|
||||||
self.arc.lockStatus = "locked"
|
self.arc.lockStatus = "locked"
|
||||||
self.arc.needskip = False
|
self.arc.needskip = False
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
if self.index>0:
|
if self.index>0:
|
||||||
if self.arc.needskip:
|
if self.arc.needskip:
|
||||||
@@ -208,9 +208,9 @@ class RarInfoIterator(object):
|
|||||||
self.res = RARReadHeaderEx(self.arc._handle, ctypes.byref(self.headerData))
|
self.res = RARReadHeaderEx(self.arc._handle, ctypes.byref(self.headerData))
|
||||||
|
|
||||||
if self.res:
|
if self.res:
|
||||||
raise StopIteration
|
raise StopIteration
|
||||||
self.arc.needskip = True
|
self.arc.needskip = True
|
||||||
|
|
||||||
data = {}
|
data = {}
|
||||||
data['index'] = self.index
|
data['index'] = self.index
|
||||||
data['filename'] = self.headerData.FileName
|
data['filename'] = self.headerData.FileName
|
||||||
@@ -224,7 +224,7 @@ class RarInfoIterator(object):
|
|||||||
self.index += 1
|
self.index += 1
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.arc.lockStatus = "finished"
|
self.arc.lockStatus = "finished"
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ def generate_password_provider(password):
|
|||||||
|
|
||||||
class RarFileImplementation(object):
|
class RarFileImplementation(object):
|
||||||
|
|
||||||
def init(self, password=None):
|
def init(self, password=None, custom_path = None):
|
||||||
self.password = password
|
self.password = password
|
||||||
archiveData = RAROpenArchiveDataEx(ArcNameW=self.archiveName, OpenMode=RAR_OM_EXTRACT)
|
archiveData = RAROpenArchiveDataEx(ArcNameW=self.archiveName, OpenMode=RAR_OM_EXTRACT)
|
||||||
self._handle = RAROpenArchiveEx(ctypes.byref(archiveData))
|
self._handle = RAROpenArchiveEx(ctypes.byref(archiveData))
|
||||||
@@ -254,9 +254,9 @@ class RarFileImplementation(object):
|
|||||||
|
|
||||||
if password:
|
if password:
|
||||||
RARSetPassword(self._handle, password)
|
RARSetPassword(self._handle, password)
|
||||||
|
|
||||||
self.lockStatus = "ready"
|
self.lockStatus = "ready"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def destruct(self):
|
def destruct(self):
|
||||||
@@ -287,7 +287,7 @@ class RarFileImplementation(object):
|
|||||||
self.needskip = False
|
self.needskip = False
|
||||||
res.append((info, reader.get_result()))
|
res.append((info, reader.get_result()))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
def extract(self, checker, path, withSubpath, overwrite):
|
def extract(self, checker, path, withSubpath, overwrite):
|
||||||
res = []
|
res = []
|
||||||
@@ -300,7 +300,7 @@ class RarFileImplementation(object):
|
|||||||
fn = os.path.split(fn)[-1]
|
fn = os.path.split(fn)[-1]
|
||||||
target = os.path.join(path, fn)
|
target = os.path.join(path, fn)
|
||||||
else:
|
else:
|
||||||
raise DeprecationWarning, "Condition callbacks returning strings are deprecated and only supported in Windows"
|
raise DeprecationWarning, "Condition callbacks returning strings are deprecated and only supported in Windows"
|
||||||
target = checkres
|
target = checkres
|
||||||
if overwrite or (not os.path.exists(target)):
|
if overwrite or (not os.path.exists(target)):
|
||||||
tmpres = RARProcessFile(self._handle, RAR_EXTRACT, None, target)
|
tmpres = RARProcessFile(self._handle, RAR_EXTRACT, None, target)
|
||||||
|
|||||||
Reference in New Issue
Block a user