import sys
import wx
import os
from cStringIO import StringIO
from threading import Thread,currentThread
from time import sleep
from traceback import print_exc,print_stack
from webbrowser import open_new
from Dialogs.dupfiledialog import DupFileDialog
from Utility.constants import *#IGNORE:W0611
fromSwapper.unicodebin2unicode
################################################################
#
# Class: TorrentFiles
#
# Keep track of the files associated with a torrent
#
################################################################
class TorrentFiles:
def __init__(self, torrent):
self.torrent = torrent
self.utility = torrent.utility
namekey = self.torrent.namekey
self.filename = self.torrent.info[namekey]
# Array to store file priorities
# Just using a placeholder of 1 (Normal) for now
if self.isFile():
numfiles = 1
else:
numfiles = len(self.torrent.info['files'])
# Change all files to unicode
if self.torrent.info['files'][0].has_key('path.utf-8'):
pathkey = 'path.utf-8'
else:
pathkey = 'path'
for i in range(numfiles):
for j in range(len(self.torrent.info['files'][i]['path'])):
self.torrent.info['files'][i]['path'][j] = bin2unicode(self.torrent.info['files'][i][pathkey][j])
self.filepriorities = [1] * numfiles
self.floattotalsize = float(self.getSize())
self.realsize = self.floattotalsize
# This one is to store the download progress ; if it's not stored, the progress
# of an inactive torrent would stay only in the display of the list, and so it would
# be lost if the GUI wouldn't display the column "progress". In this case it couldn't
# be saved in the torrent.lst file.
self.progress = 0.0
self.downsize = 0.0
self.upsize = 0.0
# Progress of individual files within torrent
self.fileprogress = [""] * numfiles
self.dest = None
# self.skipcheck = False
def setupDest(self, dest, forceasklocation, caller):
self.dest = dest
# Try reading the config file
if self.dest is None:
self.dest = self.torrent.torrentconfig.Read("dest")
# Treat an empty string for dest the same as
# not having one defined
if not self.dest:
self.dest = None
# For new torrents, get the destination where to save the torrent
if self.dest is None or forceasklocation:
self.getDestination(forceasklocation, caller)
# Treat an empty string for dest the same as
# not having one defined
if not self.dest:
self.dest = None
def onOpenDest(self, event = None, index = 0):
return self.onOpenFileDest(index, pathonly = True)
def onOpenFileDest(self, event = None, index = 0, pathonly = False):
dest = self.getSingleFileDest(index, pathonly, checkexists = False)
# Check to make sure that what we're trying to get exists
if dest is None or not os.access(dest, os.R_OK):
if dest is None:
dest = "None"
# Error : file not found
dialog = wx.MessageDialog(None,
dest + '\n\n' + self.utility.lang.get('filenotfound'),
self.utility.lang.get('error'),
wx.ICON_ERROR)
dialog.ShowModal()
dialog.Destroy()
return False
# A file is completed if it either is a single file flagged as completed,
# or is a file within a multi-file torrent flagged as "Done"
# (i.e.: file is in-place)
if self.isFile():
completed = self.torrent.status.completed
else:
completed = (self.fileprogress[index] == self.utility.lang.get('done'))
# Don't need to check if the torrent is complete if we're only
# opening the path
if not pathonly and not completed:
#Display Warning file is not complete yet
dialog = wx.MessageDialog(None,
self.torrent.getColumnText(COL_TITLE) + '\n\n'+ self.utility.lang.get('warningopenfile'),
self.utility.lang.get('warning'),
wx.YES_NO|wx.ICON_EXCLAMATION)
result = dialog.ShowModal()
dialog.Destroy()
if result != wx.ID_YES:
return False
try:
if sys.platform == 'darwin':
dest = 'file://%s' % dest
Thread(target = open_new(str(dest))).start()
except:
pass
return True
def changeProcDest(self, dest, rentorrent = False):
self.dest = dest
self.torrent.updateColumns([COL_DEST])
self.torrent.torrentconfig.writeBasicInfo()
if rentorrent:
# Update torrent name
self.torrent.changeTitle(os.path.split(dest)[1])
details = self.torrent.dialogs.details
if details is not None:
try:
oldlabel = details.fileInfoPanel.opendirbtn.GetLabel()
newlabel = self.torrent.files.getProcDest(pathonly = True, checkexists = False)
if oldlabel != newlabel:
details.fileInfoPanel.opendirbtn.SetLabel(newlabel)
# Need to call "Layout" since the new text
# may require a larger button to fit
details.fileInfoPanel.Layout()
details.updateTorrentName()
except wx.PyDeadObjectError:
pass
def move(self, dest = None):
if dest is None:
dest = self.utility.config.Read('defaultmovedir')
if not os.access(dest, os.F_OK):
try:
os.makedirs(dest)
except:
return False
#Wait thread a little bit for returning resource
##################################################
sleep(0.5)
if self.isFile():
self.moveSingleFile(dest)
else:
self.moveDir(dest)
self.changeProcDest(os.path.join(dest, self.filename))
return True
def moveSingleFile(self, dest):
if not self.isFile():
self.moveDir(dest)
return
filename = os.path.split(self.dest)[1]
source = os.path.split(self.dest)[0]
size = int(self.torrent.info['length'])
self.moveFiles({filename: size}, source, dest)
def moveFiles(self, filearray, source, dest):
dummyname = os.path.join(os.path.split(self.dest)[0], 'dummy')
try:
file(dummyname, 'w').close()
except:
pass
overwrite = "ask"
for filename in filearray:
oldloc = os.path.join(source, filename)
newloc = os.path.join(dest, filename)
size = filearray[filename]
done = False
firsttime = True
while not done:
try:
# File exists
if os.access(oldloc, os.R_OK):
copyfile = True
# Something already exists where we're trying to copy:
if os.access(newloc, os.F_OK):
# Default to "No"
result = -1
if overwrite == "ask":
single = len(filearray) > 1
dialog = DupFileDialog(self.torrent, filename, single)
result = dialog.ShowModal()
dialog.Destroy()
if result == 2:
overwrite = "yes"
elif result == -2:
overwrite = "no"
if overwrite == "yes" or result > 0:
os.remove(newloc)
elif overwrite == "no" or result < 0:
copyfile = False
if copyfile:
os.renames(oldloc, newloc)
done = True
except:
# There's a very special case for a file with a null size referenced in the torrent
# but not retrieved just because of this null size : It can't be renamed so we
# just skip it.
if size == 0:
done = True
else:
#retry >_<;
if firsttime:
firsttime = False
sleep(0.1)
else:
done = True
data = StringIO()
print_exc(file = data)
dialog = wx.MessageDialog(None, self.utility.lang.get('errormovefile') + "\n" + data.getvalue(), self.utility.lang.get('error'), wx.ICON_ERROR)
dialog.ShowModal()
dialog.Destroy()
try:
os.remove(dummyname)
except:
pass
def moveDir(self, dest):
if self.isFile():
self.moveSingleFile(dest)
return
destname = self.getProcDest()
if destname is None:
return
filearray = {}
movename = os.path.join(dest, self.filename)
for f in self.torrent.info['files']:
for item in f['path']:
size = int(f['length'])
filearray[item] = size
self.moveFiles(filearray, destname, movename)
self.utility.RemoveEmptyDir(destname, True)
def removeFiles(self):
destination = self.getProcDest()
if destination is None:
return
# Remove File
##################################################
done = False
firsttime = True
while not done:
#Wait thread a little bit for returning resource
##################################################
sleep(0.5)
try:
if self.isFile():
#remove file
if os.access(destination, os.F_OK):
os.remove(destination)
else:
# Only delete files from this torrent
# (should be safer this way)
subdirs = 0
for x in self.torrent.info['files']:
filename = destination
subdirs = max(subdirs, len(x['path']) - 1)
for i in x['path']:
filename = os.path.join(filename, i)
if os.access(filename, os.F_OK):
os.remove(filename)
self.utility.RemoveEmptyDir(destination, (subdirs > 0))
done = True
except:
#retry >_<;
if firsttime:
firsttime = False
sleep(0.1)
else:
done = True
data = StringIO()
print_exc(file = data)
dialog = wx.MessageDialog(None, self.utility.lang.get('errordeletefile') + "\n" + data.getvalue(), self.utility.lang.get('error'), wx.ICON_ERROR)
dialog.ShowModal()
dialog.Destroy()
#TODO: change db
def getDest(self):
return self.dest
# Specify where to save the torrent
def getDestination(self, forceasklocation = False, caller = ""):
# Set destination location that will be used in next set destination dialog
# No default directory (or default directory can't be found)
defaultfolder = self.utility.config.Read('defaultfolder')
if not os.access(defaultfolder, os.F_OK):
try:
os.makedirs(defaultfolder)
except:
forceasklocation = True
if ((not self.utility.config.Read('setdefaultfolder', "boolean") or forceasklocation)
and (caller != "web")):
success, dest = self.torrent.dialogs.setDestination()
if not success:
try:
os.remove(dest)
except:
pass
else:
if not 'length' in self.torrent.info: #multi file torrent
self.dest = os.path.join(dest, self.filename)
else: #1 file for this torrent
self.dest = dest
else:
self.dest = os.path.join(self.utility.config.Read('defaultfolder'), self.filename)
def getProcDest(self, pathonly = False, checkexists = True):
# Set it to self.dest (should be fine for files)
dest = self.dest
# In the case of a multifile torrent, see where we're saving
if not self.isFile():
## see if we're saving to a subdirectory or not
existing = 0
if os.path.exists(dest):
if not os.path.isdir(dest):
dest = None
if os.listdir(dest): # if it's not empty
for x in self.torrent.info['files']:
if os.path.exists(os.path.join(dest, x['path'][0])):
existing = 1
if not existing:
dest = os.path.join(dest, self.filename)
elif pathonly:
# Strip out just the path for a regular torrent
dest = os.path.dirname(self.dest)
if checkexists and dest is not None and not os.access(dest, os.F_OK):
return None
return dest
# Used for getting the path for a file in a multi-file torrent
def getSingleFileDest(self, index = 0, pathonly = False, checkexists = True):
if self.isFile():
return self.getProcDest(pathonly, checkexists)
# This isn't a valid file
if index > len(self.torrent.info['files']):
return None
fileinfo = self.torrent.info['files'][index]
dest = self.getProcDest(pathonly = True, checkexists = False)
for item in fileinfo['path']:
dest = os.path.join(dest, item)
if pathonly:
dest = os.path.dirname(dest)
if checkexists and dest is not None and not os.access(dest, os.F_OK):
return None
return dest
def isFile(self):
return 'length' in self.torrent.info
#
# Get the total size of all files in the torrent
#
# If realsize is True, only return the total size
# of files that aren't set to "download never"
#
def getSize(self, realsize = False):
if self.isFile(): #1 file for this torrent
file_length = self.torrent.info['length']
else: # Directory torrent
file_length = 0
count = 0
for x in self.torrent.info['files']:
# If returning the real size, don't include files
# set to "download never"
if not realsize or self.filepriorities[count] != -1:
file_length += x['length']
count += 1
return file_length
def updateRealSize(self):
self.realsize = self.getSize(realsize = True)
self.torrent.updateColumns([COL_SIZE])
# Set the priorities for all of the files in a multi-file torrent
def setFilePriorities(self, priority_array = None):
if priority_array is not None:
self.filepriorities = priority_array
self.torrent.torrentconfig.writeFilePriorities()
self.updateRealSize()
self.updateFileProgress()
engine = self.torrent.connection.engine
if len(self.filepriorities) > 1 and engine is not None and engine.dow is not None:
engine.dow.fileselector.set_priorities(self.filepriorities)
def getFilePrioritiesAsString(self):
notdefault = False
text = ""
if len(self.filepriorities) > 1:
for entry in self.filepriorities:
if entry != 1:
notdefault = True
text += ('%d,' % entry)
# Remove the trailing ","
text = text[:-1]
return notdefault, text
def updateProgress(self):
if currentThread().getName() != "MainThread":
print "TorrentFiles: updateProgress thread",currentThread()
print "NOT MAIN THREAD"
print_stack()
# update the download progress
if self.torrent.status.isActive():
engine = self.torrent.connection.engine
self.downsize = engine.downsize['old'] + engine.downsize['new']
self.upsize = engine.upsize['old'] + engine.upsize['new']
if self.torrent.status.isActive(checking = False, pause = False):
self.progress = engine.progress
if self.isFile():
details = self.torrent.dialogs.details
if details is not None:
details.fileInfoPanel.updateColumns([FILEINFO_PROGRESS])
def updateFileProgress(self, statistics = None):
if self.isFile():
return
if currentThread().getName() != "MainThread":
print "TorrentFiles: updateFileProgress thread",currentThread()
print "NOT MAIN THREAD"
print_stack()
# Clear progress for all files that are set to never download
for i in range(len(self.filepriorities)):
priority = self.filepriorities[i]
if priority == -1:
self.fileprogress[i] = ''
if statistics is not None and statistics.filelistupdated.isSet():
for i in range(len(statistics.filecomplete)):
progress = None
if self.filepriorities[i] == -1:
# Not download this file
progress = ''
elif statistics.fileinplace[i]:
# File is done
progress = self.utility.lang.get('done')
elif statistics.filecomplete[i]:
# File is at complete, but not done
progress = "100%"
else:
# File isn't complete yet
frac = statistics.fileamtdone[i]
if frac:
progress = '%d%%' % (frac*100)
else:
progress = ''
if progress is None:
progress = ''
self.fileprogress[i] = progress
statistics.filelistupdated.clear()
details = self.torrent.dialogs.details
if details is not None:
details.fileInfoPanel.updateColumns([FILEINFO_SIZE, FILEINFO_PROGRESS])
#
# See how much more space is allocated to this torrent
#
def getSpaceAllocated(self):
allocated = 0L
if self.isFile():
if os.path.exists(self.dest):
allocated = os.path.getsize(self.dest)
else:
count = 0
for f in self.torrent.info['files']:
# Don't include space taken by disabled files
if self.filepriorities[count] != -1:
filename = self.getProcDest()
for item in f['path']:
filename = os.path.join(filename, item)
if os.path.exists(filename):
allocated += os.path.getsize(filename)
count += 1
return allocated
#
# See how much space is needed by this torrent
#
def getSpaceNeeded(self, realsize = True):
# Shouldn't need any more space if the file is complete
if self.torrent.status.completed:
return 0L
# See how much space the torrent needs vs. how much is already allocated
space = self.getSize(realsize = realsize) - self.getSpaceAllocated()
if space < 0:
space = 0L
return space
|