mirror of
https://github.com/SickGear/SickGear.git
synced 2024-11-30 16:33:37 +00:00
Add detection of password protected rars with config/Post Processing/'Unpack downloads' enabled.
Change post process to cleanup filenames with config/Post Processing/'Unpack downloads' enabled. Change post process to join incrementally named (i.e. file.001 to file.nnn) split files. Change replace unrar2 lib with rarfile 3.0 and UnRAR.exe 5.40 beta 4 freeware. Change post process "Copy" to delete redundant files after use.
This commit is contained in:
parent
16c27b37ec
commit
48a27e57a0
23 changed files with 3229 additions and 944 deletions
|
@ -1,7 +1,6 @@
|
|||
language: python
|
||||
sudo: false
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
|
||||
install:
|
||||
|
|
|
@ -114,6 +114,11 @@
|
|||
* Change set Specials to status "Skipped" not "Wanted" during show updates
|
||||
* Change improve debug log message for CloudFlare response that indicate website is offline
|
||||
* Add handling for 'part' numbered new releases and also for specific existing 'part' numbered releases
|
||||
* Add detection of password protected rars with config/Post Processing/'Unpack downloads' enabled
|
||||
* Change post process to cleanup filenames with config/Post Processing/'Unpack downloads' enabled
|
||||
* Change post process to join incrementally named (i.e. file.001 to file.nnn) split files
|
||||
* Change replace unrar2 lib with rarfile 3.0 and UnRAR.exe 5.40 beta 4 freeware
|
||||
* Change post process "Copy" to delete redundant files after use
|
||||
|
||||
[develop changelog]
|
||||
* Change send nzb data to NZBGet for Anizb instead of url
|
||||
|
|
|
@ -9,4 +9,3 @@ Libs with customisations...
|
|||
/lib/requests/packages/urllib3/util/ssl_.py
|
||||
/lib/tornado
|
||||
/lib/tvdb/tvdb_api.py
|
||||
/lib/unrar2/unix.py
|
||||
|
|
BIN
lib/rarfile/UnRAR.exe
Normal file
BIN
lib/rarfile/UnRAR.exe
Normal file
Binary file not shown.
1
lib/rarfile/__init__.py
Normal file
1
lib/rarfile/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
|
2932
lib/rarfile/rarfile.py
Normal file
2932
lib/rarfile/rarfile.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,180 +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.
|
||||
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
__version__ = '0.99.6'
|
||||
|
||||
try:
|
||||
WindowsError
|
||||
in_windows = True
|
||||
except NameError:
|
||||
in_windows = False
|
||||
|
||||
if in_windows:
|
||||
from windows import RarFileImplementation
|
||||
else:
|
||||
from unix import RarFileImplementation
|
||||
|
||||
|
||||
import fnmatch, time, weakref
|
||||
|
||||
class RarInfo(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.
|
||||
"""
|
||||
|
||||
def __init__(self, rarfile, data):
|
||||
self.rarfile = weakref.proxy(rarfile)
|
||||
self.index = data['index']
|
||||
self.filename = data['filename']
|
||||
self.isdir = data['isdir']
|
||||
self.size = data['size']
|
||||
self.datetime = data['datetime']
|
||||
self.comment = data['comment']
|
||||
|
||||
def __str__(self):
|
||||
try :
|
||||
arcName = self.rarfile.archiveName
|
||||
except ReferenceError:
|
||||
arcName = "[ARCHIVE_NO_LONGER_LOADED]"
|
||||
return '<RarInfo "%s" in "%s">' % (self.filename, arcName)
|
||||
|
||||
class RarFile(RarFileImplementation):
|
||||
|
||||
def __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.
|
||||
"""
|
||||
self.archiveName = archiveName
|
||||
RarFileImplementation.init(self, password)
|
||||
|
||||
def __del__(self):
|
||||
self.destruct()
|
||||
|
||||
def 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
|
||||
"""
|
||||
for params in RarFileImplementation.infoiter(self):
|
||||
yield RarInfo(self, params)
|
||||
|
||||
def infolist(self):
|
||||
"""Return a list of RarInfos, descripting the contents of the archive."""
|
||||
return list(self.infoiter())
|
||||
|
||||
def 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)
|
||||
"""
|
||||
checker = condition2checker(condition)
|
||||
return RarFileImplementation.read_files(self, checker)
|
||||
|
||||
|
||||
def 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."""
|
||||
checker = condition2checker(condition)
|
||||
return RarFileImplementation.extract(self, checker, path, withSubpath, overwrite)
|
||||
|
||||
def get_volume(self):
|
||||
"""Determine which volume is it in a multi-volume archive. Returns None if it's not a
|
||||
multi-volume archive, 0-based volume number otherwise."""
|
||||
return RarFileImplementation.get_volume(self)
|
||||
|
||||
def condition2checker(condition):
|
||||
"""Converts different condition types to callback"""
|
||||
if type(condition) in [str, unicode]:
|
||||
def smatcher(info):
|
||||
return fnmatch.fnmatch(info.filename, condition)
|
||||
return smatcher
|
||||
elif type(condition) in [list, tuple] and type(condition[0]) in [int, long]:
|
||||
def imatcher(info):
|
||||
return info.index in condition
|
||||
return imatcher
|
||||
elif callable(condition):
|
||||
return condition
|
||||
else:
|
||||
raise TypeError
|
||||
|
||||
|
|
@ -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.
|
|
@ -1,30 +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.
|
||||
|
||||
# Low level interface - see UnRARDLL\UNRARDLL.TXT
|
||||
|
||||
|
||||
class ArchiveHeaderBroken(Exception): pass
|
||||
class InvalidRARArchive(Exception): pass
|
||||
class FileOpenError(Exception): pass
|
||||
class IncorrectRARPassword(Exception): pass
|
||||
class InvalidRARArchiveUsage(Exception): pass
|
|
@ -1,265 +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.
|
||||
|
||||
# Unix version uses unrar command line executable
|
||||
|
||||
import subprocess
|
||||
import gc
|
||||
|
||||
import os, os.path
|
||||
import time, re
|
||||
|
||||
from rar_exceptions import *
|
||||
|
||||
class UnpackerNotInstalled(Exception): pass
|
||||
|
||||
rar_executable_cached = None
|
||||
rar_executable_version = None
|
||||
|
||||
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'):
|
||||
try:
|
||||
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)
|
||||
finally:
|
||||
gc.enable()
|
||||
|
||||
class RarFileImplementation(object):
|
||||
|
||||
def init(self, password=None):
|
||||
global rar_executable_version
|
||||
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
|
||||
accum = []
|
||||
source = iter(stdoutdata.splitlines())
|
||||
line = ''
|
||||
while (line.find('RAR ') == -1):
|
||||
line = source.next()
|
||||
signature = line
|
||||
# The code below is mighty flaky
|
||||
# and will probably crash on localized versions of RAR
|
||||
# but I see no safe way to rewrite it using a CLI tool
|
||||
if signature.find("RAR 4") > -1:
|
||||
rar_executable_version = 4
|
||||
while not (line.startswith('Comment:') or line.startswith('Pathname/Comment')):
|
||||
if line.strip().endswith('is not RAR archive'):
|
||||
raise InvalidRARArchive
|
||||
line = source.next()
|
||||
while not line.startswith('Pathname/Comment'):
|
||||
accum.append(line.rstrip('\n'))
|
||||
line = source.next()
|
||||
if len(accum):
|
||||
accum[0] = accum[0][9:] # strip out "Comment:" part
|
||||
self.comment = '\n'.join(accum[:-1])
|
||||
else:
|
||||
self.comment = None
|
||||
elif signature.find("RAR 5") > -1:
|
||||
rar_executable_version = 5
|
||||
line = source.next()
|
||||
while not line.startswith('Archive:'):
|
||||
if line.strip().endswith('is not RAR archive'):
|
||||
raise InvalidRARArchive
|
||||
accum.append(line.rstrip('\n'))
|
||||
line = source.next()
|
||||
if len(accum):
|
||||
self.comment = '\n'.join(accum[:-1]).strip()
|
||||
else:
|
||||
self.comment = None
|
||||
else:
|
||||
raise UnpackerNotInstalled("Unsupported RAR version, expected 4.x or 5.x, found: "
|
||||
+ signature.split(" ")[1])
|
||||
|
||||
|
||||
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 infoiter(self):
|
||||
|
||||
command = "v" if rar_executable_version == 4 else "l"
|
||||
stdoutdata, stderrdata = self.call(command, ['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.startswith("CRC failed") or line.startswith("Checksum error"):
|
||||
raise IncorrectRARPassword
|
||||
line = source.next()
|
||||
line = source.next()
|
||||
i = 0
|
||||
re_spaces = re.compile(r"\s+")
|
||||
if rar_executable_version == 4:
|
||||
while not line.startswith('-----------'):
|
||||
accum.append(line)
|
||||
if len(accum)==2:
|
||||
data = {}
|
||||
data['index'] = i
|
||||
# asterisks mark password-encrypted files
|
||||
data['filename'] = accum[0].strip().lstrip("*") # asterisks marks password-encrypted files
|
||||
fields = re_spaces.split(accum[1].strip())
|
||||
data['size'] = int(fields[0])
|
||||
attr = fields[5]
|
||||
data['isdir'] = 'd' in attr.lower()
|
||||
data['datetime'] = self.rarcmd_dt(fields[3], fields[4])
|
||||
data['comment'] = None
|
||||
data['volume'] = None
|
||||
yield data
|
||||
accum = []
|
||||
i += 1
|
||||
line = source.next()
|
||||
elif rar_executable_version == 5:
|
||||
while not line.startswith('-----------'):
|
||||
fields = line.strip().lstrip("*").split()
|
||||
data = {}
|
||||
data['index'] = i
|
||||
data['filename'] = " ".join(fields[4:])
|
||||
data['size'] = int(fields[1])
|
||||
attr = fields[0]
|
||||
data['isdir'] = 'd' in attr.lower()
|
||||
data['datetime'] = self.rarcmd_dt(fields[2], fields[3])
|
||||
data['comment'] = None
|
||||
data['volume'] = None
|
||||
yield data
|
||||
i += 1
|
||||
line = source.next()
|
||||
|
||||
@staticmethod
|
||||
def rarcmd_dt(param_date=time.strftime('%Y-%m-%d'), param_time=time.strftime('%H:%M')):
|
||||
for str_fmt in '%Y-%m-%d %H:%M', '%d-%m-%y %H:%M':
|
||||
try:
|
||||
return time.strptime('%s %s' % (param_date, param_time), str_fmt)
|
||||
except ValueError:
|
||||
pass
|
||||
return time.strptime('%s %s' % (time.strftime('%Y-%m-%d'), time.strftime('%H:%M')), '%Y-%m-%d %H:%M')
|
||||
|
||||
def read_files(self, checker):
|
||||
res = []
|
||||
for info in self.infoiter():
|
||||
checkres = checker(info)
|
||||
if checkres==True and not info.isdir:
|
||||
pipe = self.call('p', ['inul'], [info.filename]).stdout
|
||||
res.append((info, pipe.read()))
|
||||
return res
|
||||
|
||||
|
||||
def extract(self, checker, path, withSubpath, overwrite):
|
||||
res = []
|
||||
command = 'x'
|
||||
if not withSubpath:
|
||||
command = 'e'
|
||||
options = []
|
||||
if overwrite:
|
||||
options.append('o+')
|
||||
else:
|
||||
options.append('o-')
|
||||
if not path.endswith(os.sep):
|
||||
path += os.sep
|
||||
names = []
|
||||
for info in self.infoiter():
|
||||
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:
|
||||
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 or stderrdata.find("Checksum error")>=0:
|
||||
raise IncorrectRARPassword
|
||||
return res
|
||||
|
||||
def destruct(self):
|
||||
pass
|
||||
|
||||
def get_volume(self):
|
||||
command = "v" if rar_executable_version == 4 else "l"
|
||||
stdoutdata, stderrdata = self.call(command, ['c-']).communicate()
|
||||
|
||||
for line in stderrdata.splitlines():
|
||||
if line.strip().startswith("Cannot open"):
|
||||
raise FileOpenError
|
||||
|
||||
source = iter(stdoutdata.splitlines())
|
||||
line = ''
|
||||
while not line.startswith('-----------'):
|
||||
if line.strip().endswith('is not RAR archive'):
|
||||
raise InvalidRARArchive
|
||||
if line.startswith("CRC failed") or line.startswith("Checksum error"):
|
||||
raise IncorrectRARPassword
|
||||
line = source.next()
|
||||
line = source.next()
|
||||
if rar_executable_version == 4:
|
||||
while not line.startswith('-----------'):
|
||||
line = source.next()
|
||||
line = source.next()
|
||||
items = line.strip().split()
|
||||
if len(items)>4 and items[4]=="volume":
|
||||
return int(items[5]) - 1
|
||||
else:
|
||||
return None
|
||||
|
||||
elif rar_executable_version == 5:
|
||||
while not line.startswith('-----------'):
|
||||
line = source.next()
|
||||
line = source.next()
|
||||
items = line.strip().split()
|
||||
if items[1]=="volume":
|
||||
return int(items[2]) - 1
|
||||
else:
|
||||
return None
|
||||
|
||||
|
|
@ -1,332 +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.
|
||||
|
||||
# Low level interface - see UnRARDLL\UNRARDLL.TXT
|
||||
|
||||
from __future__ import generators
|
||||
|
||||
import ctypes, ctypes.wintypes
|
||||
import os, os.path, sys, re
|
||||
import Queue
|
||||
import time
|
||||
|
||||
from rar_exceptions import *
|
||||
|
||||
ERAR_END_ARCHIVE = 10
|
||||
ERAR_NO_MEMORY = 11
|
||||
ERAR_BAD_DATA = 12
|
||||
ERAR_BAD_ARCHIVE = 13
|
||||
ERAR_UNKNOWN_FORMAT = 14
|
||||
ERAR_EOPEN = 15
|
||||
ERAR_ECREATE = 16
|
||||
ERAR_ECLOSE = 17
|
||||
ERAR_EREAD = 18
|
||||
ERAR_EWRITE = 19
|
||||
ERAR_SMALL_BUF = 20
|
||||
ERAR_UNKNOWN = 21
|
||||
ERAR_MISSING_PASSWORD = 22
|
||||
|
||||
RAR_OM_LIST = 0
|
||||
RAR_OM_EXTRACT = 1
|
||||
|
||||
RAR_SKIP = 0
|
||||
RAR_TEST = 1
|
||||
RAR_EXTRACT = 2
|
||||
|
||||
RAR_VOL_ASK = 0
|
||||
RAR_VOL_NOTIFY = 1
|
||||
|
||||
RAR_DLL_VERSION = 3
|
||||
|
||||
# enum UNRARCALLBACK_MESSAGES
|
||||
UCM_CHANGEVOLUME = 0
|
||||
UCM_PROCESSDATA = 1
|
||||
UCM_NEEDPASSWORD = 2
|
||||
|
||||
architecture_bits = ctypes.sizeof(ctypes.c_voidp)*8
|
||||
dll_name = "unrar.dll"
|
||||
if architecture_bits == 64:
|
||||
dll_name = "x64\\unrar64.dll"
|
||||
|
||||
volume_naming1 = re.compile("\.r([0-9]{2})$")
|
||||
volume_naming2 = re.compile("\.([0-9]{3}).rar$")
|
||||
volume_naming3 = re.compile("\.part([0-9]+).rar$")
|
||||
|
||||
try:
|
||||
unrar = ctypes.WinDLL(os.path.join(os.path.split(__file__)[0], 'UnRARDLL', dll_name))
|
||||
except WindowsError:
|
||||
unrar = ctypes.WinDLL(dll_name)
|
||||
|
||||
|
||||
class RAROpenArchiveDataEx(ctypes.Structure):
|
||||
def __init__(self, ArcName=None, ArcNameW=u'', OpenMode=RAR_OM_LIST):
|
||||
self.CmtBuf = ctypes.c_buffer(64*1024)
|
||||
ctypes.Structure.__init__(self, ArcName=ArcName, ArcNameW=ArcNameW, OpenMode=OpenMode, _CmtBuf=ctypes.addressof(self.CmtBuf), CmtBufSize=ctypes.sizeof(self.CmtBuf))
|
||||
|
||||
_fields_ = [
|
||||
('ArcName', ctypes.c_char_p),
|
||||
('ArcNameW', ctypes.c_wchar_p),
|
||||
('OpenMode', ctypes.c_uint),
|
||||
('OpenResult', ctypes.c_uint),
|
||||
('_CmtBuf', ctypes.c_voidp),
|
||||
('CmtBufSize', ctypes.c_uint),
|
||||
('CmtSize', ctypes.c_uint),
|
||||
('CmtState', ctypes.c_uint),
|
||||
('Flags', ctypes.c_uint),
|
||||
('Reserved', ctypes.c_uint*32),
|
||||
]
|
||||
|
||||
class RARHeaderDataEx(ctypes.Structure):
|
||||
def __init__(self):
|
||||
self.CmtBuf = ctypes.c_buffer(64*1024)
|
||||
ctypes.Structure.__init__(self, _CmtBuf=ctypes.addressof(self.CmtBuf), CmtBufSize=ctypes.sizeof(self.CmtBuf))
|
||||
|
||||
_fields_ = [
|
||||
('ArcName', ctypes.c_char*1024),
|
||||
('ArcNameW', ctypes.c_wchar*1024),
|
||||
('FileName', ctypes.c_char*1024),
|
||||
('FileNameW', ctypes.c_wchar*1024),
|
||||
('Flags', ctypes.c_uint),
|
||||
('PackSize', ctypes.c_uint),
|
||||
('PackSizeHigh', ctypes.c_uint),
|
||||
('UnpSize', ctypes.c_uint),
|
||||
('UnpSizeHigh', ctypes.c_uint),
|
||||
('HostOS', ctypes.c_uint),
|
||||
('FileCRC', ctypes.c_uint),
|
||||
('FileTime', ctypes.c_uint),
|
||||
('UnpVer', ctypes.c_uint),
|
||||
('Method', ctypes.c_uint),
|
||||
('FileAttr', ctypes.c_uint),
|
||||
('_CmtBuf', ctypes.c_voidp),
|
||||
('CmtBufSize', ctypes.c_uint),
|
||||
('CmtSize', ctypes.c_uint),
|
||||
('CmtState', ctypes.c_uint),
|
||||
('Reserved', ctypes.c_uint*1024),
|
||||
]
|
||||
|
||||
def DosDateTimeToTimeTuple(dosDateTime):
|
||||
"""Convert an MS-DOS format date time to a Python time tuple.
|
||||
"""
|
||||
dosDate = dosDateTime >> 16
|
||||
dosTime = dosDateTime & 0xffff
|
||||
day = dosDate & 0x1f
|
||||
month = (dosDate >> 5) & 0xf
|
||||
year = 1980 + (dosDate >> 9)
|
||||
second = 2*(dosTime & 0x1f)
|
||||
minute = (dosTime >> 5) & 0x3f
|
||||
hour = dosTime >> 11
|
||||
return time.localtime(time.mktime((year, month, day, hour, minute, second, 0, 1, -1)))
|
||||
|
||||
def _wrap(restype, function, argtypes):
|
||||
result = function
|
||||
result.argtypes = argtypes
|
||||
result.restype = restype
|
||||
return result
|
||||
|
||||
RARGetDllVersion = _wrap(ctypes.c_int, unrar.RARGetDllVersion, [])
|
||||
|
||||
RAROpenArchiveEx = _wrap(ctypes.wintypes.HANDLE, unrar.RAROpenArchiveEx, [ctypes.POINTER(RAROpenArchiveDataEx)])
|
||||
|
||||
RARReadHeaderEx = _wrap(ctypes.c_int, unrar.RARReadHeaderEx, [ctypes.wintypes.HANDLE, ctypes.POINTER(RARHeaderDataEx)])
|
||||
|
||||
_RARSetPassword = _wrap(ctypes.c_int, unrar.RARSetPassword, [ctypes.wintypes.HANDLE, ctypes.c_char_p])
|
||||
def RARSetPassword(*args, **kwargs):
|
||||
_RARSetPassword(*args, **kwargs)
|
||||
|
||||
RARProcessFile = _wrap(ctypes.c_int, unrar.RARProcessFile, [ctypes.wintypes.HANDLE, ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p])
|
||||
|
||||
RARCloseArchive = _wrap(ctypes.c_int, unrar.RARCloseArchive, [ctypes.wintypes.HANDLE])
|
||||
|
||||
UNRARCALLBACK = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_uint, ctypes.c_long, ctypes.c_long, ctypes.c_long)
|
||||
RARSetCallback = _wrap(ctypes.c_int, unrar.RARSetCallback, [ctypes.wintypes.HANDLE, UNRARCALLBACK, ctypes.c_long])
|
||||
|
||||
|
||||
|
||||
RARExceptions = {
|
||||
ERAR_NO_MEMORY : MemoryError,
|
||||
ERAR_BAD_DATA : ArchiveHeaderBroken,
|
||||
ERAR_BAD_ARCHIVE : InvalidRARArchive,
|
||||
ERAR_EOPEN : FileOpenError,
|
||||
}
|
||||
|
||||
class PassiveReader:
|
||||
"""Used for reading files to memory"""
|
||||
def __init__(self, usercallback = None):
|
||||
self.buf = []
|
||||
self.ucb = usercallback
|
||||
|
||||
def _callback(self, msg, UserData, P1, P2):
|
||||
if msg == UCM_PROCESSDATA:
|
||||
data = (ctypes.c_char*P2).from_address(P1).raw
|
||||
if self.ucb!=None:
|
||||
self.ucb(data)
|
||||
else:
|
||||
self.buf.append(data)
|
||||
return 1
|
||||
|
||||
def get_result(self):
|
||||
return ''.join(self.buf)
|
||||
|
||||
class RarInfoIterator(object):
|
||||
def __init__(self, arc):
|
||||
self.arc = arc
|
||||
self.index = 0
|
||||
self.headerData = RARHeaderDataEx()
|
||||
self.res = RARReadHeaderEx(self.arc._handle, ctypes.byref(self.headerData))
|
||||
if self.res in [ERAR_BAD_DATA, ERAR_MISSING_PASSWORD]:
|
||||
raise IncorrectRARPassword
|
||||
self.arc.lockStatus = "locked"
|
||||
self.arc.needskip = False
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
if self.index>0:
|
||||
if self.arc.needskip:
|
||||
RARProcessFile(self.arc._handle, RAR_SKIP, None, None)
|
||||
self.res = RARReadHeaderEx(self.arc._handle, ctypes.byref(self.headerData))
|
||||
|
||||
if self.res:
|
||||
raise StopIteration
|
||||
self.arc.needskip = True
|
||||
|
||||
data = {}
|
||||
data['index'] = self.index
|
||||
data['filename'] = self.headerData.FileNameW
|
||||
data['datetime'] = DosDateTimeToTimeTuple(self.headerData.FileTime)
|
||||
data['isdir'] = ((self.headerData.Flags & 0xE0) == 0xE0)
|
||||
data['size'] = self.headerData.UnpSize + (self.headerData.UnpSizeHigh << 32)
|
||||
if self.headerData.CmtState == 1:
|
||||
data['comment'] = self.headerData.CmtBuf.value
|
||||
else:
|
||||
data['comment'] = None
|
||||
self.index += 1
|
||||
return data
|
||||
|
||||
|
||||
def __del__(self):
|
||||
self.arc.lockStatus = "finished"
|
||||
|
||||
def generate_password_provider(password):
|
||||
def password_provider_callback(msg, UserData, P1, P2):
|
||||
if msg == UCM_NEEDPASSWORD and password!=None:
|
||||
(ctypes.c_char*P2).from_address(P1).value = password
|
||||
return 1
|
||||
return password_provider_callback
|
||||
|
||||
class RarFileImplementation(object):
|
||||
|
||||
def init(self, password=None):
|
||||
self.password = password
|
||||
archiveData = RAROpenArchiveDataEx(ArcNameW=self.archiveName, OpenMode=RAR_OM_EXTRACT)
|
||||
self._handle = RAROpenArchiveEx(ctypes.byref(archiveData))
|
||||
self.c_callback = UNRARCALLBACK(generate_password_provider(self.password))
|
||||
RARSetCallback(self._handle, self.c_callback, 1)
|
||||
|
||||
if archiveData.OpenResult != 0:
|
||||
raise RARExceptions[archiveData.OpenResult]
|
||||
|
||||
if archiveData.CmtState == 1:
|
||||
self.comment = archiveData.CmtBuf.value
|
||||
else:
|
||||
self.comment = None
|
||||
|
||||
if password:
|
||||
RARSetPassword(self._handle, password)
|
||||
|
||||
self.lockStatus = "ready"
|
||||
|
||||
self.isVolume = archiveData.Flags & 1
|
||||
|
||||
|
||||
def destruct(self):
|
||||
if self._handle and RARCloseArchive:
|
||||
RARCloseArchive(self._handle)
|
||||
|
||||
def make_sure_ready(self):
|
||||
if self.lockStatus == "locked":
|
||||
raise InvalidRARArchiveUsage("cannot execute infoiter() without finishing previous one")
|
||||
if self.lockStatus == "finished":
|
||||
self.destruct()
|
||||
self.init(self.password)
|
||||
|
||||
def infoiter(self):
|
||||
self.make_sure_ready()
|
||||
return RarInfoIterator(self)
|
||||
|
||||
def read_files(self, checker):
|
||||
res = []
|
||||
for info in self.infoiter():
|
||||
if checker(info) and not info.isdir:
|
||||
reader = PassiveReader()
|
||||
c_callback = UNRARCALLBACK(reader._callback)
|
||||
RARSetCallback(self._handle, c_callback, 1)
|
||||
tmpres = RARProcessFile(self._handle, RAR_TEST, None, None)
|
||||
if tmpres in [ERAR_BAD_DATA, ERAR_MISSING_PASSWORD]:
|
||||
raise IncorrectRARPassword
|
||||
self.needskip = False
|
||||
res.append((info, reader.get_result()))
|
||||
return res
|
||||
|
||||
|
||||
def extract(self, checker, path, withSubpath, overwrite):
|
||||
res = []
|
||||
for info in self.infoiter():
|
||||
checkres = checker(info)
|
||||
if checkres!=False and not info.isdir:
|
||||
if checkres==True:
|
||||
fn = info.filename
|
||||
if not withSubpath:
|
||||
fn = os.path.split(fn)[-1]
|
||||
target = os.path.join(path, fn)
|
||||
else:
|
||||
raise DeprecationWarning, "Condition callbacks returning strings are deprecated and only supported in Windows"
|
||||
target = checkres
|
||||
if overwrite or (not os.path.exists(target)):
|
||||
tmpres = RARProcessFile(self._handle, RAR_EXTRACT, None, target)
|
||||
if tmpres in [ERAR_BAD_DATA, ERAR_MISSING_PASSWORD]:
|
||||
raise IncorrectRARPassword
|
||||
|
||||
self.needskip = False
|
||||
res.append(info)
|
||||
return res
|
||||
|
||||
def get_volume(self):
|
||||
if not self.isVolume:
|
||||
return None
|
||||
headerData = RARHeaderDataEx()
|
||||
res = RARReadHeaderEx(self._handle, ctypes.byref(headerData))
|
||||
arcName = headerData.ArcNameW
|
||||
match3 = volume_naming3.search(arcName)
|
||||
if match3 != None:
|
||||
return int(match3.group(1)) - 1
|
||||
match2 = volume_naming3.search(arcName)
|
||||
if match2 != None:
|
||||
return int(match2.group(1))
|
||||
match1 = volume_naming1.search(arcName)
|
||||
if match1 != None:
|
||||
return int(match1.group(1)) + 1
|
||||
return 0
|
||||
|
||||
|
||||
|
|
@ -130,33 +130,19 @@ def isSyncFile(filename):
|
|||
return False
|
||||
|
||||
|
||||
def isMediaFile(filename):
|
||||
def has_media_ext(filename):
|
||||
# ignore samples
|
||||
if re.search('(^|[\W_])(sample\d*)[\W_]', filename, re.I):
|
||||
if re.search('(^|[\W_])(sample\d*)[\W_]', filename, re.I) \
|
||||
or filename.startswith('._'): # and MAC OS's 'resource fork' files
|
||||
return False
|
||||
|
||||
# ignore MAC OS's retarded "resource fork" files
|
||||
if filename.startswith('._'):
|
||||
return False
|
||||
|
||||
sepFile = filename.rpartition(".")
|
||||
|
||||
if re.search('extras?$', sepFile[0], re.I):
|
||||
return False
|
||||
|
||||
if sepFile[2].lower() in mediaExtensions:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
sep_file = filename.rpartition('.')
|
||||
return (None is re.search('extras?$', sep_file[0], re.I)) and (sep_file[2].lower() in mediaExtensions)
|
||||
|
||||
|
||||
def isRarFile(filename):
|
||||
archive_regex = '(?P<file>^(?P<base>(?:(?!\.part\d+\.rar$).)*)\.(?:(?:part0*1\.)?rar)$)'
|
||||
def is_first_rar_volume(filename):
|
||||
|
||||
if re.search(archive_regex, filename):
|
||||
return True
|
||||
|
||||
return False
|
||||
return None is not re.search('(?P<file>^(?P<base>(?:(?!\.part\d+\.rar$).)*)\.(?:(?:part0*1\.)?rar)$)', filename)
|
||||
|
||||
|
||||
def sanitizeFileName(name):
|
||||
|
@ -264,7 +250,7 @@ def listMediaFiles(path):
|
|||
if ek.ek(os.path.isdir, fullCurFile) and not curFile.startswith('.') and not curFile == 'Extras':
|
||||
files += listMediaFiles(fullCurFile)
|
||||
|
||||
elif isMediaFile(curFile):
|
||||
elif has_media_ext(curFile):
|
||||
files.append(fullCurFile)
|
||||
|
||||
return files
|
||||
|
|
|
@ -824,10 +824,11 @@ class PostProcessor(object):
|
|||
Post-process a given file
|
||||
"""
|
||||
|
||||
self._log(u'Processing %s%s' % (self.file_path, (u'<br />.. from nzb %s' % self.nzb_name, u'')[None is self.nzb_name]))
|
||||
self._log(u'Processing... %s%s' % (ek.ek(os.path.relpath, self.file_path, self.folder_path),
|
||||
(u'<br />.. from nzb %s' % self.nzb_name, u'')[None is self.nzb_name]))
|
||||
|
||||
if ek.ek(os.path.isdir, self.file_path):
|
||||
self._log(u'File %s<br />.. seems to be a directory' % self.file_path)
|
||||
self._log(u'Expecting file %s<br />.. is actually a directory, skipping' % self.file_path)
|
||||
return False
|
||||
|
||||
for ignore_file in self.IGNORED_FILESTRINGS:
|
||||
|
@ -844,7 +845,7 @@ class PostProcessor(object):
|
|||
|
||||
# if we don't have it then give up
|
||||
if not show:
|
||||
self._log(u'Please add the show to your SickGear then try to post process an episode', logger.WARNING)
|
||||
self._log(u'Must add show to SickGear before trying to post process an episode', logger.WARNING)
|
||||
raise exceptions.PostProcessingFailed()
|
||||
elif None is season or not episodes:
|
||||
self._log(u'Quitting this post process, could not determine what episode this is', logger.DEBUG)
|
||||
|
@ -876,7 +877,7 @@ class PostProcessor(object):
|
|||
helpers.delete_empty_folders(ek.ek(os.path.dirname, cur_ep.location),
|
||||
keep_dir=ep_obj.show.location)
|
||||
except (OSError, IOError):
|
||||
raise exceptions.PostProcessingFailed(u'Unable to delete the existing files')
|
||||
raise exceptions.PostProcessingFailed(u'Unable to delete existing files')
|
||||
|
||||
# set the status of the episodes
|
||||
# for curEp in [ep_obj] + ep_obj.relatedEps:
|
||||
|
@ -938,7 +939,7 @@ class PostProcessor(object):
|
|||
if None is not release_name:
|
||||
failed_history.logSuccess(release_name)
|
||||
else:
|
||||
self._log(u'No release found in snatch history', logger.WARNING)
|
||||
self._log(u'No snatched release found in history', logger.WARNING)
|
||||
|
||||
# find the destination folder
|
||||
try:
|
||||
|
|
|
@ -18,10 +18,12 @@
|
|||
|
||||
from __future__ import with_statement
|
||||
|
||||
from functools import partial
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import re
|
||||
import sys
|
||||
|
||||
import sickbeard
|
||||
from sickbeard import postProcessor
|
||||
|
@ -35,7 +37,7 @@ from sickbeard.history import reset_status
|
|||
|
||||
from sickbeard import failedProcessor
|
||||
|
||||
from lib.unrar2 import RarFile
|
||||
import lib.rarfile.rarfile as rarfile
|
||||
|
||||
try:
|
||||
from lib.send2trash import send2trash
|
||||
|
@ -50,6 +52,7 @@ class ProcessTVShow(object):
|
|||
def __init__(self, webhandler=None):
|
||||
self.files_passed = 0
|
||||
self.files_failed = 0
|
||||
self.fail_detected = False
|
||||
self._output = []
|
||||
self.webhandler = webhandler
|
||||
|
||||
|
@ -113,11 +116,12 @@ class ProcessTVShow(object):
|
|||
self._log_helper(u'Deleted folder ' + folder, logger.MESSAGE)
|
||||
return True
|
||||
|
||||
def _delete_files(self, process_path, notwanted_files, use_trash=False):
|
||||
def _delete_files(self, process_path, notwanted_files, use_trash=False, force=False):
|
||||
|
||||
if not self.any_vid_processed:
|
||||
if not self.any_vid_processed and not force:
|
||||
return
|
||||
|
||||
result = True
|
||||
# Delete all file not needed
|
||||
for cur_file in notwanted_files:
|
||||
|
||||
|
@ -143,9 +147,13 @@ class ProcessTVShow(object):
|
|||
except OSError as e:
|
||||
self._log_helper(u'Unable to delete file %s: %s' % (cur_file, str(e.strerror)))
|
||||
|
||||
if True is not ek.ek(os.path.isfile, cur_file_path):
|
||||
if ek.ek(os.path.isfile, cur_file_path):
|
||||
result = False
|
||||
else:
|
||||
self._log_helper(u'Deleted file ' + cur_file)
|
||||
|
||||
return result
|
||||
|
||||
def process_dir(self, dir_name, nzb_name=None, process_method=None, force=False, force_replace=None, failed=False, pp_type='auto', cleanup=False):
|
||||
"""
|
||||
Scans through the files in dir_name and processes whatever media files it finds
|
||||
|
@ -159,7 +167,6 @@ class ProcessTVShow(object):
|
|||
|
||||
# if they passed us a real directory then assume it's the one we want
|
||||
if dir_name and ek.ek(os.path.isdir, dir_name):
|
||||
self._log_helper(u'Processing folder... ' + dir_name)
|
||||
dir_name = ek.ek(os.path.realpath, dir_name)
|
||||
|
||||
# if the client and SickGear are not on the same machine translate the directory in a network directory
|
||||
|
@ -167,17 +174,19 @@ class ProcessTVShow(object):
|
|||
and ek.ek(os.path.normpath, dir_name) != ek.ek(os.path.normpath, sickbeard.TV_DOWNLOAD_DIR):
|
||||
dir_name = ek.ek(os.path.join, sickbeard.TV_DOWNLOAD_DIR, ek.ek(os.path.abspath, dir_name).split(os.path.sep)[-1])
|
||||
self._log_helper(u'SickGear PP Config, completed TV downloads folder: ' + sickbeard.TV_DOWNLOAD_DIR)
|
||||
self._log_helper(u'Trying to use folder... ' + dir_name)
|
||||
|
||||
# if we didn't find a real directory then quit
|
||||
if dir_name:
|
||||
self._log_helper(u'Checking folder... ' + dir_name)
|
||||
|
||||
# if we didn't find a real directory then process "failed" or just quit
|
||||
if not dir_name or not ek.ek(os.path.isdir, dir_name):
|
||||
if nzb_name and failed:
|
||||
self._process_failed(dir_name, nzb_name)
|
||||
return self.result
|
||||
else:
|
||||
self._log_helper(
|
||||
u'Unable to figure out what folder to process. If your downloader and SickGear aren\'t on the same PC then make sure you fill out your completed TV download folder in the PP config.')
|
||||
return self.result
|
||||
self._log_helper(u'Unable to figure out what folder to process. ' +
|
||||
u'If your downloader and SickGear aren\'t on the same PC then make sure ' +
|
||||
u'you fill out your completed TV download folder in the PP config.')
|
||||
return self.result
|
||||
|
||||
path, dirs, files = self._get_path_dir_files(dir_name, nzb_name, pp_type)
|
||||
|
||||
|
@ -188,24 +197,31 @@ class ProcessTVShow(object):
|
|||
self._log_helper(u'Found temporary sync files, skipping post process', logger.ERROR)
|
||||
return self.result
|
||||
|
||||
self._log_helper(u'Process path: ' + path)
|
||||
if 0 < len(dirs):
|
||||
self._log_helper(u'Process dir%s: %s' % (('', 's')[1 < len(dirs)], str(dirs)))
|
||||
self._log_helper(u'Processing folder... %s' % path)
|
||||
|
||||
rar_files = filter(helpers.isRarFile, files)
|
||||
work_files = []
|
||||
joined = self.join(path)
|
||||
if joined:
|
||||
work_files += [joined]
|
||||
|
||||
rar_files = filter(helpers.is_first_rar_volume, files)
|
||||
rar_content = self._unrar(path, rar_files, force)
|
||||
files += rar_content
|
||||
video_files = filter(helpers.isMediaFile, files)
|
||||
video_in_rar = filter(helpers.isMediaFile, rar_content)
|
||||
if self.fail_detected:
|
||||
self._process_failed(dir_name, nzb_name)
|
||||
return self.result
|
||||
path, dirs, files = self._get_path_dir_files(dir_name, nzb_name, pp_type)
|
||||
video_files = filter(helpers.has_media_ext, files)
|
||||
video_in_rar = filter(helpers.has_media_ext, rar_content)
|
||||
work_files += [ek.ek(os.path.join, path, item) for item in rar_content]
|
||||
|
||||
if 0 < len(files):
|
||||
self._log_helper(u'Process file%s: %s' % (('', 's')[1 < len(files)], str(files)))
|
||||
self._log_helper(u'Process file%s: %s' % (helpers.maybe_plural(files), str(files)))
|
||||
if 0 < len(video_files):
|
||||
self._log_helper(u'Process video file%s: %s' % (('', 's')[1 < len(video_files)], str(video_files)))
|
||||
self._log_helper(u'Process video file%s: %s' % (helpers.maybe_plural(video_files), str(video_files)))
|
||||
if 0 < len(rar_content):
|
||||
self._log_helper(u'Process rar content: ' + str(rar_content))
|
||||
if 0 < len(video_in_rar):
|
||||
self._log_helper(u'Process video in rar: ' + str(video_in_rar))
|
||||
self._log_helper(u'Process video%s in rar: %s' % (helpers.maybe_plural(video_in_rar), str(video_in_rar)))
|
||||
|
||||
# If nzb_name is set and there's more than one videofile in the folder, files will be lost (overwritten).
|
||||
nzb_name_original = nzb_name
|
||||
|
@ -220,7 +236,7 @@ class ProcessTVShow(object):
|
|||
# Don't Link media when the media is extracted from a rar in the same path
|
||||
if process_method in ('hardlink', 'symlink') and video_in_rar:
|
||||
self._process_media(path, video_in_rar, nzb_name, 'move', force, force_replace)
|
||||
self._delete_files(path, rar_content)
|
||||
self._delete_files(path, [ek.ek(os.path.relpath, item, path) for item in work_files], force=True)
|
||||
video_batch = set(video_files) - set(video_in_rar)
|
||||
else:
|
||||
video_batch = video_files
|
||||
|
@ -246,27 +262,31 @@ class ProcessTVShow(object):
|
|||
# Process video files in TV subdirectories
|
||||
for directory in [x for x in dirs if self._validate_dir(path, x, nzb_name_original, failed)]:
|
||||
|
||||
self._set_process_success(reset=True)
|
||||
# self._set_process_success(reset=True)
|
||||
|
||||
for process_path, process_dir, file_list in ek.ek(os.walk, ek.ek(os.path.join, path, directory), topdown=False):
|
||||
for walk_path, walk_dir, files in ek.ek(os.walk, ek.ek(os.path.join, path, directory), topdown=False):
|
||||
|
||||
sync_files = filter(helpers.isSyncFile, file_list)
|
||||
sync_files = filter(helpers.isSyncFile, files)
|
||||
|
||||
# Don't post process if files are still being synced and option is activated
|
||||
if sync_files and sickbeard.POSTPONE_IF_SYNC_FILES:
|
||||
self._log_helper(u'Found temporary sync files, skipping post process', logger.ERROR)
|
||||
return self.result
|
||||
|
||||
rar_files = filter(helpers.isRarFile, file_list)
|
||||
rar_content = self._unrar(process_path, rar_files, force)
|
||||
file_list = set(file_list + rar_content)
|
||||
video_files = filter(helpers.isMediaFile, file_list)
|
||||
video_in_rar = filter(helpers.isMediaFile, rar_content)
|
||||
notwanted_files = [x for x in file_list if x not in video_files]
|
||||
rar_files = filter(helpers.is_first_rar_volume, files)
|
||||
rar_content = self._unrar(walk_path, rar_files, force)
|
||||
work_files += [ek.ek(os.path.join, walk_path, item) for item in rar_content]
|
||||
if self.fail_detected:
|
||||
self._process_failed(dir_name, nzb_name)
|
||||
continue
|
||||
files = list(set(files + rar_content))
|
||||
video_files = filter(helpers.has_media_ext, files)
|
||||
video_in_rar = filter(helpers.has_media_ext, rar_content)
|
||||
notwanted_files = [x for x in files if x not in video_files]
|
||||
|
||||
# Don't Link media when the media is extracted from a rar in the same path
|
||||
if process_method in ('hardlink', 'symlink') and video_in_rar:
|
||||
self._process_media(process_path, video_in_rar, nzb_name, 'move', force, force_replace)
|
||||
self._process_media(walk_path, video_in_rar, nzb_name, 'move', force, force_replace)
|
||||
video_batch = set(video_files) - set(video_in_rar)
|
||||
else:
|
||||
video_batch = video_files
|
||||
|
@ -276,7 +296,7 @@ class ProcessTVShow(object):
|
|||
video_pick = ['']
|
||||
video_size = 0
|
||||
for cur_video_file in video_batch:
|
||||
cur_video_size = ek.ek(os.path.getsize, ek.ek(os.path.join, process_path, cur_video_file))
|
||||
cur_video_size = ek.ek(os.path.getsize, ek.ek(os.path.join, walk_path, cur_video_file))
|
||||
|
||||
if 0 == video_size or cur_video_size > video_size:
|
||||
video_size = cur_video_size
|
||||
|
@ -284,14 +304,14 @@ class ProcessTVShow(object):
|
|||
|
||||
video_batch = set(video_batch) - set(video_pick)
|
||||
|
||||
self._process_media(process_path, video_pick, nzb_name, process_method, force, force_replace, use_trash=cleanup)
|
||||
self._process_media(walk_path, video_pick, nzb_name, process_method, force, force_replace, use_trash=cleanup)
|
||||
|
||||
except OSError as e:
|
||||
logger.log('Batch skipped, %s%s' %
|
||||
(ex(e), e.filename and (' (file %s)' % e.filename) or ''), logger.WARNING)
|
||||
|
||||
if process_method in ('hardlink', 'symlink') and video_in_rar:
|
||||
self._delete_files(process_path, rar_content)
|
||||
self._delete_files(walk_path, rar_content)
|
||||
else:
|
||||
# Delete all file not needed
|
||||
if not self.any_vid_processed\
|
||||
|
@ -299,11 +319,17 @@ class ProcessTVShow(object):
|
|||
or ('manual' == pp_type and not cleanup): # Avoid deleting files if Manual Postprocessing
|
||||
continue
|
||||
|
||||
self._delete_files(process_path, notwanted_files, use_trash=cleanup)
|
||||
self._delete_files(walk_path, notwanted_files, use_trash=cleanup)
|
||||
|
||||
if 'move' == process_method\
|
||||
and ek.ek(os.path.normpath, sickbeard.TV_DOWNLOAD_DIR) != ek.ek(os.path.normpath, process_path):
|
||||
self._delete_folder(process_path, check_empty=False)
|
||||
and ek.ek(os.path.normpath, sickbeard.TV_DOWNLOAD_DIR) != ek.ek(os.path.normpath, walk_path):
|
||||
self._delete_folder(walk_path, check_empty=False)
|
||||
|
||||
if 'copy' == process_method and work_files:
|
||||
self._delete_files(path, [ek.ek(os.path.relpath, item, path) for item in work_files], force=True)
|
||||
for f in sorted(list(set([ek.ek(os.path.dirname, item) for item in work_files]) - {path}),
|
||||
key=len, reverse=True):
|
||||
self._delete_folder(f)
|
||||
|
||||
def _bottom_line(text, log_level=logger.DEBUG):
|
||||
self._buffer('-' * len(text))
|
||||
|
@ -322,7 +348,7 @@ class ProcessTVShow(object):
|
|||
|
||||
def _validate_dir(self, path, dir_name, nzb_name_original, failed):
|
||||
|
||||
self._log_helper(u'Processing dir: ' + dir_name)
|
||||
self._log_helper(u'Processing sub dir: ' + dir_name)
|
||||
|
||||
if ek.ek(os.path.basename, dir_name).startswith('_FAILED_'):
|
||||
self._log_helper(u'The directory name indicates it failed to extract.')
|
||||
|
@ -357,11 +383,12 @@ class ProcessTVShow(object):
|
|||
# Get the videofile list for the next checks
|
||||
all_files = []
|
||||
all_dirs = []
|
||||
process_path = None
|
||||
for process_path, process_dir, fileList in ek.ek(os.walk, ek.ek(os.path.join, path, dir_name), topdown=False):
|
||||
all_dirs += process_dir
|
||||
all_files += fileList
|
||||
|
||||
video_files = filter(helpers.isMediaFile, all_files)
|
||||
video_files = filter(helpers.has_media_ext, all_files)
|
||||
all_dirs.append(dir_name)
|
||||
|
||||
# check if the directory have at least one tv video file
|
||||
|
@ -379,9 +406,9 @@ class ProcessTVShow(object):
|
|||
except (InvalidNameException, InvalidShowException):
|
||||
pass
|
||||
|
||||
if sickbeard.UNPACK:
|
||||
if sickbeard.UNPACK and process_path and all_files:
|
||||
# Search for packed release
|
||||
packed_files = filter(helpers.isRarFile, all_files)
|
||||
packed_files = filter(helpers.is_first_rar_volume, all_files)
|
||||
|
||||
for packed in packed_files:
|
||||
try:
|
||||
|
@ -396,6 +423,9 @@ class ProcessTVShow(object):
|
|||
|
||||
unpacked_files = []
|
||||
|
||||
if 'win32' == sys.platform:
|
||||
rarfile.UNRAR_TOOL = ek.ek(os.path.join, sickbeard.PROG_DIR, 'lib', 'rarfile', 'UnRAR.exe')
|
||||
|
||||
if sickbeard.UNPACK and rar_files:
|
||||
|
||||
self._log_helper(u'Packed releases detected: ' + str(rar_files))
|
||||
|
@ -405,32 +435,207 @@ class ProcessTVShow(object):
|
|||
self._log_helper(u'Unpacking archive: ' + archive)
|
||||
|
||||
try:
|
||||
rar_handle = RarFile(os.path.join(path, archive))
|
||||
rar_handle = rarfile.RarFile(ek.ek(os.path.join, path, archive))
|
||||
|
||||
# Skip extraction if any file in archive has previously been extracted
|
||||
skip_file = False
|
||||
for file_in_archive in [os.path.basename(x.filename) for x in rar_handle.infolist() if not x.isdir]:
|
||||
for file_in_archive in [ek.ek(os.path.basename, x.filename)
|
||||
for x in rar_handle.infolist() if not x.isdir()]:
|
||||
if self._already_postprocessed(path, file_in_archive, force):
|
||||
self._log_helper(
|
||||
u'Archive file already processed, extraction skipped: ' + file_in_archive)
|
||||
skip_file = True
|
||||
break
|
||||
|
||||
if skip_file:
|
||||
continue
|
||||
if not skip_file:
|
||||
# need to test for password since rar4 doesn't raise PasswordRequired
|
||||
if rar_handle.needs_password():
|
||||
raise rarfile.PasswordRequired
|
||||
|
||||
rar_handle.extract(path=path, withSubpath=False, overwrite=False)
|
||||
unpacked_files += [os.path.basename(x.filename) for x in rar_handle.infolist() if not x.isdir]
|
||||
del rar_handle
|
||||
except Exception as e:
|
||||
self._log_helper(u'Failed to unpack archive %s: %s' % (archive, ex(e)), logger.ERROR)
|
||||
rar_handle.extractall(path=path)
|
||||
rar_content = [ek.ek(os.path.normpath, x.filename)
|
||||
for x in rar_handle.infolist() if not x.isdir()]
|
||||
renamed = self.cleanup_names(path, rar_content)
|
||||
cur_unpacked = rar_content if not renamed else \
|
||||
(list(set(rar_content) - set(renamed.keys())) + renamed.values())
|
||||
self._log_helper(u'Unpacked content: [u\'%s\']' % '\', u\''.join(map(unicode, cur_unpacked)))
|
||||
unpacked_files += cur_unpacked
|
||||
except (rarfile.PasswordRequired, rarfile.RarWrongPassword):
|
||||
self._log_helper(u'Failed to unpack archive PasswordRequired: %s' % archive, logger.ERROR)
|
||||
self._set_process_success(False)
|
||||
continue
|
||||
self.fail_detected = True
|
||||
except Exception as e:
|
||||
self._log_helper(u'Failed to unpack archive: %s' % archive, logger.ERROR)
|
||||
self._set_process_success(False)
|
||||
finally:
|
||||
rar_handle.close()
|
||||
del rar_handle
|
||||
|
||||
self._log_helper(u'Unpacked content: ' + str(unpacked_files))
|
||||
elif rar_files:
|
||||
# check for passworded rar's
|
||||
for archive in rar_files:
|
||||
try:
|
||||
rar_handle = rarfile.RarFile(ek.ek(os.path.join, path, archive))
|
||||
if rar_handle.needs_password():
|
||||
self._log_helper(u'Failed to unpack archive PasswordRequired: %s' % archive, logger.ERROR)
|
||||
self._set_process_success(False)
|
||||
self.failure_detected = True
|
||||
rar_handle.close()
|
||||
del rar_handle
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return unpacked_files
|
||||
|
||||
@staticmethod
|
||||
def cleanup_names(directory, files=None):
|
||||
|
||||
is_renamed = {}
|
||||
num_videos = 0
|
||||
old_name = None
|
||||
new_name = None
|
||||
params = {
|
||||
'base_name': ek.ek(os.path.basename, directory),
|
||||
'reverse_pattern': re.compile('|'.join([
|
||||
r'\.\d{2}e\d{2}s\.', r'\.p0(?:63|27|612)\.', r'\.[pi](?:084|675|0801)\.', r'\b[45]62[xh]\.',
|
||||
r'\.yarulb\.', r'\.vtd[hp]\.', r'\.(?:ld[.-]?)?bew\.', r'\.pir.?(?:shv|dov|dvd|bew|db|rb)\.',
|
||||
r'\brdvd\.', r'\.(?:vts|dcv)\.', r'\b(?:mac|pir)dh\b', r'\.(?:lanretni|reporp|kcaper|reneercs)\.',
|
||||
r'\b(?:caa|3ca|3pm)\b', r'\.cstn\.', r'\.5r\.', r'\brcs\b'
|
||||
]), flags=re.IGNORECASE),
|
||||
'season_pattern': re.compile(r'(.*\.\d{2}e\d{2}s\.)(.*)', flags=re.IGNORECASE),
|
||||
'word_pattern': re.compile(r'([^A-Z0-9]*[A-Z0-9]+)'),
|
||||
'char_replace': [[r'(\w)1\.(\w)', r'\1i\2']],
|
||||
'garbage_name': re.compile(r'^[a-zA-Z0-9]{3,}$'),
|
||||
'media_pattern': re.compile('|'.join([
|
||||
r'\.s\d{2}e\d{2}\.', r'\.(?:36|72|216)0p\.', r'\.(?:480|576|1080)[pi]\.', r'\.[xh]26[45]\b',
|
||||
r'\.bluray\.', r'\.[hp]dtv\.', r'\.web(?:[.-]?dl)?\.', r'\.(?:vhs|vod|dvd|web|bd|br).?rip\.',
|
||||
r'\.dvdr\b', r'\.(?:stv|vcd)\.', r'\bhd(?:cam|rip)\b', r'\.(?:internal|proper|repack|screener)\.',
|
||||
r'\b(?:aac|ac3|mp3)\b', r'\.(?:ntsc|pal|secam)\.', r'\.r5\.', r'\bscr\b', r'\b(?:divx|xvid)\b'
|
||||
]), flags=re.IGNORECASE)
|
||||
}
|
||||
|
||||
def renamer(_dirpath, _filenames, _num_videos, _old_name, _new_name, base_name,
|
||||
reverse_pattern, season_pattern, word_pattern, char_replace, garbage_name, media_pattern):
|
||||
|
||||
for cur_filename in _filenames:
|
||||
|
||||
file_name, file_extension = ek.ek(os.path.splitext, cur_filename)
|
||||
file_path = ek.ek(os.path.join, _dirpath, cur_filename)
|
||||
dir_name = ek.ek(os.path.dirname, file_path)
|
||||
|
||||
if None is not reverse_pattern.search(file_name):
|
||||
na_parts = season_pattern.search(file_name)
|
||||
if None is not na_parts:
|
||||
word_p = word_pattern.findall(na_parts.group(2))
|
||||
new_words = ''
|
||||
for wp in word_p:
|
||||
if '.' == wp[0]:
|
||||
new_words += '.'
|
||||
new_words += re.sub(r'\W', '', wp)
|
||||
for cr in char_replace:
|
||||
new_words = re.sub(cr[0], cr[1], new_words)
|
||||
new_filename = new_words[::-1] + na_parts.group(1)[::-1]
|
||||
else:
|
||||
new_filename = file_name[::-1]
|
||||
logger.log('Reversing base filename "%s" to "%s"' % (file_name, new_filename))
|
||||
try:
|
||||
ek.ek(os.rename, file_path, ek.ek(os.path.join, _dirpath, new_filename + file_extension))
|
||||
is_renamed[ek.ek(os.path.relpath, file_path, directory)] = ek.ek(
|
||||
os.path.relpath, new_filename + file_extension, directory)
|
||||
except OSError as e:
|
||||
logger.log('Error unable to rename file "%s" because %s' % (cur_filename, ex(e)), logger.ERROR)
|
||||
elif helpers.has_media_ext(cur_filename) and \
|
||||
None is not garbage_name.search(file_name) and None is not media_pattern.search(base_name):
|
||||
_num_videos += 1
|
||||
_old_name = file_path
|
||||
_new_name = ek.ek(os.path.join, dir_name, '%s%s' % (base_name, file_extension))
|
||||
return is_renamed, _num_videos, _old_name, _new_name
|
||||
|
||||
if files:
|
||||
is_renamed, num_videos, old_name, new_name = renamer(
|
||||
directory, files, num_videos, old_name, new_name, **params)
|
||||
else:
|
||||
for cur_dirpath, void, cur_filenames in ek.ek(os.walk, directory):
|
||||
is_renamed, num_videos, old_name, new_name = renamer(
|
||||
cur_dirpath, cur_filenames, num_videos, old_name, new_name, **params)
|
||||
|
||||
if all([not is_renamed, 1 == num_videos, old_name, new_name]):
|
||||
try_name = ek.ek(os.path.basename, new_name)
|
||||
logger.log('Renaming file "%s" using dirname as "%s"' % (ek.ek(os.path.basename, old_name), try_name))
|
||||
try:
|
||||
ek.ek(os.rename, old_name, new_name)
|
||||
is_renamed[ek.ek(os.path.relpath, old_name, directory)] = ek.ek(os.path.relpath, new_name, directory)
|
||||
except OSError as e:
|
||||
logger.log('Error unable to rename file "%s" because %s' % (old_name, ex(e)), logger.ERROR)
|
||||
|
||||
return is_renamed
|
||||
|
||||
@staticmethod
|
||||
def join(directory):
|
||||
|
||||
result = False
|
||||
chunks = {}
|
||||
matcher = re.compile('\.[0-9]+$')
|
||||
for dirpath, void, filenames in os.walk(directory):
|
||||
for filename in filenames:
|
||||
if None is not matcher.search(filename):
|
||||
maybe_chunk = ek.ek(os.path.join, dirpath, filename)
|
||||
base_filepath, ext = os.path.splitext(maybe_chunk)
|
||||
if base_filepath not in chunks:
|
||||
chunks[base_filepath] = []
|
||||
chunks[base_filepath].append(maybe_chunk)
|
||||
|
||||
if not chunks:
|
||||
return
|
||||
|
||||
for base_filepath in chunks:
|
||||
chunks[base_filepath].sort()
|
||||
chunk_set = chunks[base_filepath]
|
||||
if ek.ek(os.path.isfile, base_filepath):
|
||||
base_filesize = ek.ek(os.path.getsize, base_filepath)
|
||||
chunk_sizes = [ek.ek(os.path.getsize, x) for x in chunk_set]
|
||||
largest_chunk = max(chunk_sizes)
|
||||
if largest_chunk >= base_filesize:
|
||||
outfile = '%s.001' % base_filepath
|
||||
if outfile not in chunk_set:
|
||||
try:
|
||||
ek.ek(os.rename, base_filepath, outfile)
|
||||
except OSError:
|
||||
logger.log('Error unable to rename file %s' % base_filepath, logger.ERROR)
|
||||
return result
|
||||
chunk_set.append(outfile)
|
||||
chunk_set.sort()
|
||||
else:
|
||||
del_dir, del_file = ek.ek(os.path.split, base_filepath)
|
||||
if not self._delete_files(del_dir, [del_file], force=True):
|
||||
return result
|
||||
else:
|
||||
if base_filesize == sum(chunk_sizes):
|
||||
logger.log('Join skipped. Total size of %s input files equal to output.. %s (%s bytes)' % (
|
||||
len(chunk_set), base_filepath, base_filesize))
|
||||
else:
|
||||
logger.log('Join skipped. Found output file larger than input.. %s (%s bytes)' % (
|
||||
base_filepath, base_filesize))
|
||||
return result
|
||||
|
||||
with open(base_filepath, 'ab') as newfile:
|
||||
for f in chunk_set:
|
||||
logger.log('Joining file %s' % f)
|
||||
try:
|
||||
with open(f, 'rb') as part:
|
||||
for wdata in iter(partial(part.read, 4096), b''):
|
||||
try:
|
||||
newfile.write(wdata)
|
||||
except:
|
||||
logger.log('Failed write to file %s' % f)
|
||||
return result
|
||||
except:
|
||||
logger.log('Failed read from file %s' % f)
|
||||
return result
|
||||
result = base_filepath
|
||||
|
||||
return result
|
||||
|
||||
def _already_postprocessed(self, dir_name, videofile, force):
|
||||
|
||||
if force or not self.any_vid_processed:
|
||||
|
@ -527,9 +732,9 @@ class ProcessTVShow(object):
|
|||
processor = postProcessor.PostProcessor(cur_video_file_path, nzb_name, process_method, force_replace, use_trash=use_trash, webhandler=self.webhandler)
|
||||
file_success = processor.process()
|
||||
process_fail_message = ''
|
||||
except exceptions.PostProcessingFailed as e:
|
||||
except exceptions.PostProcessingFailed:
|
||||
file_success = False
|
||||
process_fail_message = '<br />.. ' + ex(e)
|
||||
process_fail_message = '<br />.. Post Processing Failed'
|
||||
|
||||
self._set_process_success(file_success)
|
||||
|
||||
|
|
|
@ -856,7 +856,7 @@ class TorrentProvider(object, GenericProvider):
|
|||
file_name = '%s.py' % os.path.join(sickbeard.PROG_DIR, *self.__module__.split('.'))
|
||||
if ek.ek(os.path.isfile, file_name):
|
||||
with open(file_name, 'rb') as file_hd:
|
||||
is_valid = 1661931498 == s + zlib.crc32(file_hd.read())
|
||||
is_valid = s + zlib.crc32(file_hd.read()) in (1661931498, 472149389)
|
||||
return is_valid
|
||||
|
||||
def _authorised(self, logged_in=None, post_params=None, failed_msg=None, url=None, timeout=30):
|
||||
|
|
|
@ -700,7 +700,7 @@ class TVShow(object):
|
|||
cur_ep.status = Quality.compositeStatus(DOWNLOADED, new_quality)
|
||||
|
||||
# check for status/quality changes as long as it's a new file
|
||||
elif not same_file and sickbeard.helpers.isMediaFile(file)\
|
||||
elif not same_file and sickbeard.helpers.has_media_ext(file)\
|
||||
and cur_ep.status not in Quality.DOWNLOADED + [ARCHIVED, IGNORED]:
|
||||
|
||||
old_status, old_quality = Quality.splitCompositeStatus(cur_ep.status)
|
||||
|
@ -1813,7 +1813,7 @@ class TVEpisode(object):
|
|||
statusStrings[self.status], logger.DEBUG)
|
||||
|
||||
# if we have a media file then it's downloaded
|
||||
elif sickbeard.helpers.isMediaFile(self.location):
|
||||
elif sickbeard.helpers.has_media_ext(self.location):
|
||||
# leave propers alone, you have to either post-process them or manually change them back
|
||||
if self.status not in Quality.SNATCHED_PROPER + Quality.DOWNLOADED + Quality.SNATCHED + [ARCHIVED]:
|
||||
status_quality = Quality.statusFromNameOrFile(self.location, anime=self.show.is_anime)
|
||||
|
@ -1838,7 +1838,7 @@ class TVEpisode(object):
|
|||
|
||||
if self.location != "":
|
||||
|
||||
if UNKNOWN == self.status and sickbeard.helpers.isMediaFile(self.location):
|
||||
if UNKNOWN == self.status and sickbeard.helpers.has_media_ext(self.location):
|
||||
status_quality = Quality.statusFromNameOrFile(self.location, anime=self.show.is_anime)
|
||||
logger.log('(3) Status changes from %s to %s' % (self.status, status_quality), logger.DEBUG)
|
||||
self.status = status_quality
|
||||
|
|
|
@ -26,6 +26,7 @@ import itertools
|
|||
import os
|
||||
import random
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import urllib
|
||||
|
@ -59,7 +60,8 @@ from tornado.web import RequestHandler, StaticFileHandler, authenticated
|
|||
from lib import adba
|
||||
from lib import subliminal
|
||||
from lib.dateutil import tz
|
||||
from lib.unrar2 import RarFile
|
||||
import lib.rarfile.rarfile as rarfile
|
||||
|
||||
from lib.libtrakt import TraktAPI
|
||||
from lib.libtrakt.exceptions import TraktException, TraktAuthException
|
||||
from trakt_helpers import build_config, trakt_collection_remove_account
|
||||
|
@ -4629,19 +4631,20 @@ class ConfigPostProcessing(Config):
|
|||
def isRarSupported(self, *args, **kwargs):
|
||||
"""
|
||||
Test Packing Support:
|
||||
- Simulating in memory rar extraction on test.rar file
|
||||
"""
|
||||
|
||||
try:
|
||||
rar_path = os.path.join(sickbeard.PROG_DIR, 'lib', 'unrar2', 'test.rar')
|
||||
testing = RarFile(rar_path).read_files('*test.txt')
|
||||
if testing[0][1] == 'This is only a test.':
|
||||
if 'win32' == sys.platform:
|
||||
rarfile.UNRAR_TOOL = ek.ek(os.path.join, sickbeard.PROG_DIR, 'lib', 'rarfile', 'UnRAR.exe')
|
||||
rar_path = ek.ek(os.path.join, sickbeard.PROG_DIR, 'lib', 'rarfile', 'test.rar')
|
||||
if 'This is only a test.' == rarfile.RarFile(rar_path).read(r'test\test.txt'):
|
||||
return 'supported'
|
||||
logger.log(u'Rar Not Supported: Can not read the content of test file', logger.ERROR)
|
||||
return 'not supported'
|
||||
msg = 'Could not read test file content'
|
||||
except Exception as e:
|
||||
logger.log(u'Rar Not Supported: ' + ex(e), logger.ERROR)
|
||||
return 'not supported'
|
||||
msg = ex(e)
|
||||
|
||||
logger.log(u'Rar Not Supported: %s' % msg, logger.ERROR)
|
||||
return 'not supported'
|
||||
|
||||
|
||||
class ConfigProviders(Config):
|
||||
|
|
Loading…
Reference in a new issue