Rearranged archiving into different classes
git-svn-id: http://comictagger.googlecode.com/svn/trunk@9 6c5673fe-1810-88d6-992b-cd32ca31540c
This commit is contained in:
parent
7519e10858
commit
4cff7f3c76
491
comicarchive.py
491
comicarchive.py
@ -20,67 +20,238 @@ from genericmetadata import GenericMetadata
|
||||
from filenameparser import FileNameParser
|
||||
|
||||
|
||||
# This is a custom function for writing a comment to a zip file,
|
||||
# since the built-in one doesn't seem to work on Windows and Mac OS/X
|
||||
class ZipArchiver():
|
||||
|
||||
def __init__( self, path ):
|
||||
self.path = path
|
||||
|
||||
def getArchiveComment( self ):
|
||||
zf = zipfile.ZipFile( self.path, 'r' )
|
||||
comment = zf.comment
|
||||
zf.close()
|
||||
return comment
|
||||
|
||||
# Fortunately, the zip comment is at the end of the file, and it's
|
||||
# easy to manipulate. See this website for more info:
|
||||
# see: http://en.wikipedia.org/wiki/Zip_(file_format)#Structure
|
||||
def setArchiveComment( self, comment ):
|
||||
writeZipComment( self.path, comment )
|
||||
|
||||
def writeZipComment( filename, comment ):
|
||||
def readArchiveFile( self, archive_file ):
|
||||
zf = zipfile.ZipFile( self.path, 'r' )
|
||||
data = zf.read( archive_file )
|
||||
zf.close()
|
||||
return data
|
||||
|
||||
#get file size
|
||||
statinfo = os.stat(filename)
|
||||
file_length = statinfo.st_size
|
||||
def removeArchiveFile( self, archive_file ):
|
||||
self.rebuildZipFile( [ archive_file ] )
|
||||
|
||||
fo = open(filename, "r+b")
|
||||
def writeArchiveFile( self, archive_file, data ):
|
||||
# At the moment, no other option but to rebuild the whole
|
||||
# zip archive w/o the indicated file. Very sucky, but maybe
|
||||
# another solution can be found
|
||||
self.rebuildZipFile( [ archive_file ] )
|
||||
|
||||
#now just add the archive file as a new one
|
||||
zf = zipfile.ZipFile(self.path, mode='a', compression=zipfile.ZIP_DEFLATED )
|
||||
zf.writestr( archive_file, data )
|
||||
zf.close()
|
||||
|
||||
#the starting position, relative to EOF
|
||||
pos = -4
|
||||
def getArchiveFilenameList( self ):
|
||||
zf = zipfile.ZipFile( self.path, 'r' )
|
||||
namelist = zf.namelist()
|
||||
zf.close()
|
||||
return namelist
|
||||
|
||||
# zip helper func
|
||||
def rebuildZipFile( self, exclude_list ):
|
||||
|
||||
# TODO: use tempfile.mkstemp
|
||||
# this recompresses the zip archive, without the files in the exclude_list
|
||||
print "Rebuilding zip {0} without {1}".format( self.path, exclude_list )
|
||||
zin = zipfile.ZipFile (self.path, 'r')
|
||||
zout = zipfile.ZipFile ('tmpnew.zip', 'w')
|
||||
for item in zin.infolist():
|
||||
buffer = zin.read(item.filename)
|
||||
if ( item.filename not in exclude_list ):
|
||||
zout.writestr(item, buffer)
|
||||
|
||||
#preserve the old comment
|
||||
zout.comment = zin.comment
|
||||
|
||||
zout.close()
|
||||
zin.close()
|
||||
|
||||
# replace with the new file
|
||||
os.remove( self.path )
|
||||
os.rename( 'tmpnew.zip', self.path )
|
||||
|
||||
found = False
|
||||
value = bytearray()
|
||||
|
||||
# walk backwards to find the "End of Central Directory" record
|
||||
while ( not found ) and ( -pos != file_length ):
|
||||
# seek, relative to EOF
|
||||
fo.seek( pos, 2)
|
||||
def writeZipComment( self, filename, comment ):
|
||||
"""
|
||||
This is a custom function for writing a comment to a zip file,
|
||||
since the built-in one doesn't seem to work on Windows and Mac OS/X
|
||||
|
||||
value = fo.read( 4 )
|
||||
Fortunately, the zip comment is at the end of the file, and it's
|
||||
easy to manipulate. See this website for more info:
|
||||
see: http://en.wikipedia.org/wiki/Zip_(file_format)#Structure
|
||||
"""
|
||||
|
||||
#get file size
|
||||
statinfo = os.stat(filename)
|
||||
file_length = statinfo.st_size
|
||||
|
||||
fo = open(filename, "r+b")
|
||||
|
||||
#the starting position, relative to EOF
|
||||
pos = -4
|
||||
|
||||
found = False
|
||||
value = bytearray()
|
||||
|
||||
# walk backwards to find the "End of Central Directory" record
|
||||
while ( not found ) and ( -pos != file_length ):
|
||||
# seek, relative to EOF
|
||||
fo.seek( pos, 2)
|
||||
|
||||
value = fo.read( 4 )
|
||||
|
||||
#look for the end of central directory signature
|
||||
if bytearray(value) == bytearray([ 0x50, 0x4b, 0x05, 0x06 ]):
|
||||
found = True
|
||||
else:
|
||||
# not found, step back another byte
|
||||
pos = pos - 1
|
||||
#print pos,"{1} int: {0:x}".format(bytearray(value)[0], value)
|
||||
|
||||
if found:
|
||||
|
||||
# now skip forward 20 bytes to the comment length word
|
||||
pos += 20
|
||||
fo.seek( pos, 2)
|
||||
|
||||
# Pack the length of the comment string
|
||||
format = "H" # one 2-byte integer
|
||||
comment_length = struct.pack(format, len(comment)) # pack integer in a binary string
|
||||
|
||||
# write out the length
|
||||
fo.write( comment_length )
|
||||
fo.seek( pos+2, 2)
|
||||
|
||||
# write out the comment itself
|
||||
fo.write( comment )
|
||||
fo.truncate()
|
||||
fo.close()
|
||||
|
||||
#look for the end of central directory signature
|
||||
if bytearray(value) == bytearray([ 0x50, 0x4b, 0x05, 0x06 ]):
|
||||
found = True
|
||||
else:
|
||||
# not found, step back another byte
|
||||
pos = pos - 1
|
||||
#print pos,"{1} int: {0:x}".format(bytearray(value)[0], value)
|
||||
|
||||
if found:
|
||||
raise Exception('Failed to write comment to zip file!')
|
||||
|
||||
# now skip forward 20 bytes to the comment length word
|
||||
pos += 20
|
||||
fo.seek( pos, 2)
|
||||
|
||||
# Pack the length of the comment string
|
||||
format = "H" # one 2-byte integer
|
||||
comment_length = struct.pack(format, len(comment)) # pack integer in a binary string
|
||||
|
||||
# write out the length
|
||||
fo.write( comment_length )
|
||||
fo.seek( pos+2, 2)
|
||||
|
||||
# write out the comment itself
|
||||
fo.write( comment )
|
||||
fo.truncate()
|
||||
fo.close()
|
||||
|
||||
else:
|
||||
raise Exception('Failed to write comment to zip file!')
|
||||
|
||||
#------------------------------------------
|
||||
# RAR implementation
|
||||
|
||||
class RarArchiver():
|
||||
|
||||
def __init__( self, path ):
|
||||
self.path = path
|
||||
self.rar_exe_path = None
|
||||
|
||||
def getArchiveComment( self ):
|
||||
|
||||
rarc = UnRAR2.RarFile( self.path )
|
||||
return rarc.comment
|
||||
|
||||
def setArchiveComment( self, comment ):
|
||||
|
||||
if self.rar_exe_path is not None:
|
||||
# write comment to temp file
|
||||
tmp_fd, tmp_name = tempfile.mkstemp()
|
||||
f = os.fdopen(tmp_fd, 'w+b')
|
||||
f.write( comment )
|
||||
f.close()
|
||||
|
||||
# use external program to write comment to Rar archive
|
||||
call([self.rar_exe_path, 'c', '-z' + tmp_name, self.path])
|
||||
|
||||
os.remove( tmp_name)
|
||||
|
||||
def readArchiveFile( self, archive_file ):
|
||||
|
||||
entries = UnRAR2.RarFile( self.path ).read_files( archive_file )
|
||||
|
||||
#entries is a list of of tuples: ( rarinfo, filedata)
|
||||
if (len(entries) == 1):
|
||||
return entries[0][1]
|
||||
else:
|
||||
return ""
|
||||
|
||||
def writeArchiveFile( self, archive_file, data ):
|
||||
|
||||
if self.rar_exe_path is not None:
|
||||
|
||||
tmp_folder = tempfile.mkdtemp()
|
||||
|
||||
tmp_file = os.path.join( tmp_folder, archive_file )
|
||||
|
||||
f = open(tmp_file, 'w')
|
||||
f.write( data )
|
||||
f.close()
|
||||
|
||||
# use external program to write comment to Rar archive
|
||||
call([self.rar_exe_path, 'a', '-ep', self.path, tmp_file])
|
||||
|
||||
os.remove( tmp_file)
|
||||
os.rmdir( tmp_folder)
|
||||
|
||||
def removeArchiveFile( self, archive_file ):
|
||||
if self.rar_exe_path is not None:
|
||||
|
||||
# use external program to remove file from Rar archive
|
||||
call([self.rar_exe_path, 'd', self.path, archive_file])
|
||||
|
||||
def getArchiveFilenameList( self ):
|
||||
|
||||
rarc = UnRAR2.RarFile( self.path )
|
||||
|
||||
return [ item.filename for item in rarc.infolist() ]
|
||||
|
||||
#------------------------------------------
|
||||
# Folder implementation
|
||||
class FolderArchiver():
|
||||
|
||||
def __init__( self, path ):
|
||||
self.path = path
|
||||
|
||||
def getArchiveComment( self ):
|
||||
pass
|
||||
def setArchiveComment( self, comment ):
|
||||
pass
|
||||
def readArchiveFiler( self ):
|
||||
pass
|
||||
def writeArchiveFile( self, archive_file, data ):
|
||||
pass
|
||||
def removeArchiveFile( self, archive_file ):
|
||||
pass
|
||||
def getArchiveFilenameList( self ):
|
||||
pass
|
||||
|
||||
#------------------------------------------
|
||||
# Unknown implementation
|
||||
class UnknownArchiver():
|
||||
|
||||
def __init__( self, path ):
|
||||
self.path = path
|
||||
|
||||
def getArchiveComment( self ):
|
||||
return ""
|
||||
def setArchiveComment( self, comment ):
|
||||
return
|
||||
def readArchiveFilen( self ):
|
||||
return ""
|
||||
def writeArchiveFile( self, archive_file, data ):
|
||||
return
|
||||
def removeArchiveFile( self, archive_file ):
|
||||
return
|
||||
def getArchiveFilenameList( self ):
|
||||
return []
|
||||
|
||||
#------------------------------------------------------------------
|
||||
class ComicArchive:
|
||||
|
||||
class ArchiveType:
|
||||
@ -89,47 +260,25 @@ class ComicArchive:
|
||||
def __init__( self, path ):
|
||||
self.path = path
|
||||
self.ci_xml_filename = 'ComicInfo.xml'
|
||||
self.rar_exe_path = None
|
||||
|
||||
if self.zipTest():
|
||||
self.archive_type = self.ArchiveType.Zip
|
||||
self.getArchiveComment = self.getArchiveComment_zip
|
||||
self.setArchiveComment = self.setArchiveComment_zip
|
||||
self.readArchiveFile = self.readArchiveFile_zip
|
||||
self.writeArchiveFile = self.writeArchiveFile_zip
|
||||
self.removeArchiveFile = self.removeArchiveFile_zip
|
||||
self.getArchiveFilenameList = self.getArchiveFilenameList_zip
|
||||
self.archiver = ZipArchiver( self.path )
|
||||
|
||||
elif self.rarTest():
|
||||
self.archive_type = self.ArchiveType.Rar
|
||||
self.getArchiveComment = self.getArchiveComment_rar
|
||||
self.setArchiveComment = self.setArchiveComment_rar
|
||||
self.readArchiveFile = self.readArchiveFile_rar
|
||||
self.writeArchiveFile = self.writeArchiveFile_rar
|
||||
self.removeArchiveFile = self.removeArchiveFile_rar
|
||||
self.getArchiveFilenameList = self.getArchiveFilenameList_rar
|
||||
|
||||
self.archiver = RarArchiver( self.path )
|
||||
|
||||
elif os.path.isdir( self.path ):
|
||||
self.archive_type = self.ArchiveType.Folder
|
||||
self.getArchiveComment = self.getArchiveComment_folder
|
||||
self.setArchiveComment = self.setArchiveComment_folder
|
||||
self.readArchiveFile = self.readArchiveFile_folder
|
||||
self.writeArchiveFile = self.writeArchiveFile_folder
|
||||
self.removeArchiveFile = self.removeArchiveFile_folder
|
||||
self.getArchiveFilenameList = self.getArchiveFilenameList_folder
|
||||
|
||||
self.archiver = FolderArchiver( self.path )
|
||||
else:
|
||||
self.archive_type = self.ArchiveType.Unknown
|
||||
self.getArchiveComment = self.getArchiveComment_unknown
|
||||
self.setArchiveComment = self.setArchiveComment_unknown
|
||||
self.readArchiveFile = self.readArchiveFile_unknown
|
||||
self.writeArchiveFile = self.writeArchiveFile_unknown
|
||||
self.removeArchiveFile = self.removeArchiveFile_unknown
|
||||
self.getArchiveFilenameList = self.getArchiveFilenameList_unknown
|
||||
|
||||
self.archiver = UnknownArchiver( self.path )
|
||||
|
||||
def setExternalRarProgram( self, rar_exe_path ):
|
||||
self.rar_exe_path = rar_exe_path
|
||||
if self.isRar():
|
||||
self.archiver.rar_exe_path = rar_exe_path
|
||||
|
||||
def zipTest( self ):
|
||||
return zipfile.is_zipfile( self.path )
|
||||
@ -156,7 +305,7 @@ class ComicArchive:
|
||||
if self.archive_type == self.ArchiveType.Unknown :
|
||||
return False
|
||||
|
||||
elif self.isRar() and self.rar_exe_path is None:
|
||||
elif self.isRar() and self.archiver.rar_exe_path is None:
|
||||
return False
|
||||
|
||||
elif not os.access(self.path, os.W_OK):
|
||||
@ -171,10 +320,10 @@ class ComicArchive:
|
||||
|
||||
if (
|
||||
( ( ( self.isZip() ) and
|
||||
( ext in [ '.zip', '.cbz' ] ))
|
||||
( ext.lower() in [ '.zip', '.cbz' ] ))
|
||||
or
|
||||
(( self.isRar() ) and
|
||||
( ext in [ '.rar', '.cbr' ] ))
|
||||
( ext.lower() in [ '.rar', '.cbr' ] ))
|
||||
or
|
||||
( self.isFolder() ) )
|
||||
|
||||
@ -223,7 +372,7 @@ class ComicArchive:
|
||||
return None
|
||||
|
||||
# get the list file names in the archive, and sort
|
||||
files = self.getArchiveFilenameList()
|
||||
files = self.archiver.getArchiveFilenameList()
|
||||
files.sort()
|
||||
|
||||
# find the first image file, assume it's the cover
|
||||
@ -231,7 +380,7 @@ class ComicArchive:
|
||||
if ( name[-4:].lower() in [ ".jpg", "jpeg", ".png" ] ):
|
||||
break
|
||||
|
||||
image_data = self.readArchiveFile( name )
|
||||
image_data = self.archiver.readArchiveFile( name )
|
||||
|
||||
return image_data
|
||||
|
||||
@ -239,7 +388,7 @@ class ComicArchive:
|
||||
|
||||
count = 0
|
||||
|
||||
for item in self.getArchiveFilenameList():
|
||||
for item in self.archiver.getArchiveFilenameList():
|
||||
if ( item[-4:].lower() in [ ".jpg", "jpeg", ".png" ] ):
|
||||
count += 1
|
||||
|
||||
@ -250,7 +399,7 @@ class ComicArchive:
|
||||
if ( not self.hasCBI() ):
|
||||
return GenericMetadata()
|
||||
|
||||
cbi_string = self.getArchiveComment()
|
||||
cbi_string = self.archiver.getArchiveComment()
|
||||
|
||||
metadata = ComicBookInfo().metadataFromString( cbi_string )
|
||||
return metadata
|
||||
@ -258,7 +407,7 @@ class ComicArchive:
|
||||
def writeCBI( self, metadata ):
|
||||
|
||||
cbi_string = ComicBookInfo().stringFromMetadata( metadata )
|
||||
self.setArchiveComment( cbi_string )
|
||||
self.archiver.setArchiveComment( cbi_string )
|
||||
|
||||
def removeCBI( self ):
|
||||
self.setArchiveComment( "" )
|
||||
@ -269,7 +418,7 @@ class ComicArchive:
|
||||
print self.path, "doesn't has ComicInfo.xml data!"
|
||||
return GenericMetadata()
|
||||
|
||||
cix_string = self.readArchiveFile( self.ci_xml_filename )
|
||||
cix_string = self.archiver.readArchiveFile( self.ci_xml_filename )
|
||||
|
||||
metadata = ComicInfoXml().metadataFromString( cix_string )
|
||||
return metadata
|
||||
@ -278,16 +427,16 @@ class ComicArchive:
|
||||
|
||||
if metadata is not None:
|
||||
cix_string = ComicInfoXml().stringFromMetadata( metadata )
|
||||
self.writeArchiveFile( self.ci_xml_filename, cix_string )
|
||||
self.archiver.writeArchiveFile( self.ci_xml_filename, cix_string )
|
||||
|
||||
def removeCIX( self ):
|
||||
|
||||
self.removeArchiveFile( self.ci_xml_filename )
|
||||
self.archiver.removeArchiveFile( self.ci_xml_filename )
|
||||
|
||||
def hasCIX(self):
|
||||
if not self.seemsToBeAComicArchive():
|
||||
return False
|
||||
elif self.ci_xml_filename in self.getArchiveFilenameList():
|
||||
elif self.ci_xml_filename in self.archiver.getArchiveFilenameList():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@ -297,7 +446,7 @@ class ComicArchive:
|
||||
if ( not ( self.isZip() or self.isRar()) or not self.seemsToBeAComicArchive() ):
|
||||
return False
|
||||
|
||||
comment = self.getArchiveComment()
|
||||
comment = self.archiver.getArchiveComment()
|
||||
return ComicBookInfo().validateString( comment )
|
||||
|
||||
def metadataFromFilename( self ):
|
||||
@ -319,167 +468,3 @@ class ComicArchive:
|
||||
metadata.isEmpty = False
|
||||
|
||||
return metadata
|
||||
|
||||
#---------------
|
||||
# Zip implementation
|
||||
#---------------
|
||||
|
||||
def getArchiveComment_zip( self ):
|
||||
zf = zipfile.ZipFile( self.path, 'r' )
|
||||
comment = zf.comment
|
||||
zf.close()
|
||||
return comment
|
||||
|
||||
def setArchiveComment_zip( self, comment ):
|
||||
writeZipComment( self.path, comment )
|
||||
|
||||
def readArchiveFile_zip( self, archive_file ):
|
||||
zf = zipfile.ZipFile( self.path, 'r' )
|
||||
data = zf.read( archive_file )
|
||||
zf.close()
|
||||
return data
|
||||
|
||||
def removeArchiveFile_zip( self, archive_file ):
|
||||
self.rebuildZipFile( [ archive_file ] )
|
||||
|
||||
def writeArchiveFile_zip( self, archive_file, data ):
|
||||
# At the moment, no other option but to rebuild the whole
|
||||
# zip archive w/o the indicated file. Very sucky, but maybe
|
||||
# another solution can be found
|
||||
self.rebuildZipFile( [ archive_file ] )
|
||||
|
||||
#now just add the archive file as a new one
|
||||
zf = zipfile.ZipFile(self.path, mode='a', compression=zipfile.ZIP_DEFLATED )
|
||||
zf.writestr( archive_file, data )
|
||||
zf.close()
|
||||
|
||||
def getArchiveFilenameList_zip( self ):
|
||||
zf = zipfile.ZipFile( self.path, 'r' )
|
||||
namelist = zf.namelist()
|
||||
zf.close()
|
||||
return namelist
|
||||
|
||||
# zip helper func
|
||||
def rebuildZipFile( self, exclude_list ):
|
||||
|
||||
# TODO: use tempfile.mkstemp
|
||||
# this recompresses the zip archive, without the files in the exclude_list
|
||||
print "Rebuilding zip {0} without {1}".format( self.path, exclude_list )
|
||||
zin = zipfile.ZipFile (self.path, 'r')
|
||||
zout = zipfile.ZipFile ('tmpnew.zip', 'w')
|
||||
for item in zin.infolist():
|
||||
buffer = zin.read(item.filename)
|
||||
if ( item.filename not in exclude_list ):
|
||||
zout.writestr(item, buffer)
|
||||
|
||||
#preserve the old comment
|
||||
zout.comment = zin.comment
|
||||
|
||||
zout.close()
|
||||
zin.close()
|
||||
|
||||
# replace with the new file
|
||||
os.remove( self.path )
|
||||
os.rename( 'tmpnew.zip', self.path )
|
||||
|
||||
#---------------
|
||||
# RAR implementation
|
||||
#---------------
|
||||
|
||||
def getArchiveComment_rar( self ):
|
||||
|
||||
rarc = UnRAR2.RarFile( self.path )
|
||||
return rarc.comment
|
||||
|
||||
def setArchiveComment_rar( self, comment ):
|
||||
|
||||
if self.rar_exe_path is not None:
|
||||
# write comment to temp file
|
||||
tmp_fd, tmp_name = tempfile.mkstemp()
|
||||
f = os.fdopen(tmp_fd, 'w+b')
|
||||
f.write( comment )
|
||||
f.close()
|
||||
|
||||
# use external program to write comment to Rar archive
|
||||
call([self.rar_exe_path, 'c', '-z' + tmp_name, self.path])
|
||||
|
||||
os.remove( tmp_name)
|
||||
|
||||
def readArchiveFile_rar( self, archive_file ):
|
||||
|
||||
entries = UnRAR2.RarFile( self.path ).read_files( archive_file )
|
||||
|
||||
#entries is a list of of tuples: ( rarinfo, filedata)
|
||||
if (len(entries) == 1):
|
||||
return entries[0][1]
|
||||
else:
|
||||
return ""
|
||||
|
||||
def writeArchiveFile_rar( self, archive_file, data ):
|
||||
|
||||
if self.rar_exe_path is not None:
|
||||
|
||||
tmp_folder = tempfile.mkdtemp()
|
||||
|
||||
tmp_file = os.path.join( tmp_folder, archive_file )
|
||||
|
||||
f = open(tmp_file, 'w')
|
||||
f.write( data )
|
||||
f.close()
|
||||
|
||||
# use external program to write comment to Rar archive
|
||||
call([self.rar_exe_path, 'a', '-ep', self.path, tmp_file])
|
||||
|
||||
os.remove( tmp_file)
|
||||
os.rmdir( tmp_folder)
|
||||
|
||||
|
||||
|
||||
def removeArchiveFile_rar( self, archive_file ):
|
||||
if self.rar_exe_path is not None:
|
||||
|
||||
# use external program to remove file from Rar archive
|
||||
call([self.rar_exe_path, 'd', self.path, archive_file])
|
||||
|
||||
|
||||
def getArchiveFilenameList_rar( self ):
|
||||
|
||||
rarc = UnRAR2.RarFile( self.path )
|
||||
|
||||
return [ item.filename for item in rarc.infolist() ]
|
||||
|
||||
|
||||
#---------------
|
||||
# Folder implementation
|
||||
#---------------
|
||||
|
||||
def getArchiveComment_folder( self ):
|
||||
pass
|
||||
def setArchiveComment_folder( self, comment ):
|
||||
pass
|
||||
def readArchiveFile_folder( self ):
|
||||
pass
|
||||
def writeArchiveFile_folder( self, archive_file, data ):
|
||||
pass
|
||||
def removeArchiveFile_folder( self, archive_file ):
|
||||
pass
|
||||
def getArchiveFilenameList_folder( self ):
|
||||
pass
|
||||
|
||||
#---------------
|
||||
# Unknown implementation
|
||||
#---------------
|
||||
|
||||
def getArchiveComment_unknown( self ):
|
||||
return ""
|
||||
def setArchiveComment_unknown( self, comment ):
|
||||
return
|
||||
def readArchiveFile_unknown( self ):
|
||||
return ""
|
||||
def writeArchiveFile_unknown( self, archive_file, data ):
|
||||
return
|
||||
def removeArchiveFile_unknown( self, archive_file ):
|
||||
return
|
||||
def getArchiveFilenameList_unknown( self ):
|
||||
return []
|
||||
|
Loading…
Reference in New Issue
Block a user