def __init__(self, src = None, dest = None, forceasklocation = False, caller = ""): self.src = src # List values self.list = utility.window.list self.listbitmap = 0 # Meta data if self.src.startswith(MAGNET_PREFIX): self.metainfo = FakeMetaData(self.src) self.infohash = getHash(self.src, asHex = True) self.src = self.src[len(MAGNET_PREFIX):] self.info = self.metainfo['info'] self.rawinfo = "" self.hasMetadata = False else: self.metainfo = self.getResponse(force=True) if self.metainfo: self.infohash = sha1(bencode(self.metainfo['info'])).hexdigest() self.info = self.metainfo['info'] self.rawinfo = bencode(self.metainfo['info']) self.hasMetadata = True if self.metainfo is None: return # Tracker List if 'announce-list' in self.metainfo: self.originalTrackers = self.metainfo['announce-list'] elif 'announce' in self.metainfo: self.originalTrackers = [[self.metainfo['announce']]] else: self.originalTrackers = [] self.trackerlist = self.originalTrackers # Torrent Handlers self.torrentconfig = TorrentConfig(self) self.status = TorrentStatus(self) self.actions = TorrentActions(self) self.dialogs = TorrentDialogs(self) self.connection = TorrentConnections(self) # Info section self.InitializeInfo() self.files.setupDest(dest, forceasklocation, caller)
class Torrent(object): """ Stores information about a torrent and keeps track of its status """ def __init__(self, src = None, dest = None, forceasklocation = False, caller = ""): self.src = src # List values self.list = utility.window.list self.listbitmap = 0 # Meta data if self.src.startswith(MAGNET_PREFIX): self.metainfo = FakeMetaData(self.src) self.infohash = getHash(self.src, asHex = True) self.src = self.src[len(MAGNET_PREFIX):] self.info = self.metainfo['info'] self.rawinfo = "" self.hasMetadata = False else: self.metainfo = self.getResponse(force=True) if self.metainfo: self.infohash = sha1(bencode(self.metainfo['info'])).hexdigest() self.info = self.metainfo['info'] self.rawinfo = bencode(self.metainfo['info']) self.hasMetadata = True if self.metainfo is None: return # Tracker List if 'announce-list' in self.metainfo: self.originalTrackers = self.metainfo['announce-list'] elif 'announce' in self.metainfo: self.originalTrackers = [[self.metainfo['announce']]] else: self.originalTrackers = [] self.trackerlist = self.originalTrackers # Torrent Handlers self.torrentconfig = TorrentConfig(self) self.status = TorrentStatus(self) self.actions = TorrentActions(self) self.dialogs = TorrentDialogs(self) self.connection = TorrentConnections(self) # Info section self.InitializeInfo() self.files.setupDest(dest, forceasklocation, caller) def InitializeInfo(self, info = None): if info is not None: torrentName = self.info['name'] self.rawinfo = info self.info = self.metainfo['info'] = bdecode(info) # Meta data fixing: valid names, unicode names self.info['name'] = fixInvalidName(self.metainfo['info']['name']) if 'files' in self.info: for index in range(len(self.info['files'])): temppath = self.info['files'][index]['path'] self.info['files'][index]['path'] = [fixInvalidName(part) for part in temppath] self.metainfo = self.makeunicode(dict(self.metainfo)) # Torrent Parameters self.addedTime = 0 self.completedTime = 0 if info is None: # only apply once self.files = TorrentFiles(self) self.private = bool(self.info.get('private')) self.title = None self.prio = 2 self.message = "" self.totalpeers = "?" self.totalseeds = "?" else: self.files.__init__(self) self.private = bool(self.info.get('private')) self.writeSrc(torrentName) self.hasMetadata = True self.torrentconfig.writeSrc() self.updateColumns(force = True) self.files.updateRealSize() def HasMetadata(self): return self.hasMetadata def writeSrc(self, torrentName): torrentsrc = os.path.join(utility.getConfigPath(), "torrent", torrentName) h = open(torrentsrc, 'wb') h.write(bencode(self.metainfo)) h.close() self.src = torrentsrc return self.src def makeunicode(self, metadata): """ Insures unicode support """ # Change torrent name to unicode if 'name.utf-8' in metadata['info']: namekey = 'name.utf-8' else: namekey = 'name' if 'encoding' in metadata: encoding = metadata['encoding'] try: metadata['info'][namekey] = metadata['info'][namekey].decode(encoding) except: metadata['info'][namekey] = forceunicode(metadata['info'][namekey]) else: metadata['info'][namekey] = forceunicode(metadata['info'][namekey]) if namekey != 'name': metadata['info']['name'] = metadata['info'][namekey] # Change all files to unicode if 'files' in self.info: numfiles = len(self.info['files']) if 'path.utf-8' in self.info['files'][0]: pathkey = 'path.utf-8' else: pathkey = 'path' for i in range(numfiles): for j in range(len(self.info['files'][i]['path'])): self.info['files'][i]['path'][j] = forceunicode(self.info['files'][i][pathkey][j]) return metadata def updatetrackerlist(self): # if single tracker if len(self.trackerlist) == 1 and len(self.trackerlist[0]) == 1: self.metainfo['announce'] = self.trackerlist[0][0] if 'announce-list' in self.metainfo: del self.metainfo['announce-list'] # if Multi tracker elif len(self.trackerlist) > 0: self.metainfo['announce-list'] = self.trackerlist if 'announce' in self.metainfo: self.metainfo['announce'] = self.trackerlist[0][0] # if no tracker, else: if 'announce' in self.metainfo: del self.metainfo['announce'] if 'announce-list' in self.metainfo: del self.metainfo['announce-list'] #self.trackerlist = self.originalTrackers # ignore it and use the original tracker list if self.status.isActive(checking = False): self.connection.engine.dow.ChangeTrackerlist(self.trackerlist) self.updateColumns([COL_TRACKER]) def getTracker(self): if self.status.isActive(checking = False) and \ self.connection.engine.curtracker: return self.connection.engine.curtracker if self.trackerlist: return self.trackerlist[0][0] return "" def getMagnetLink(self): if not self.hasMetadata: return MAGNET_PREFIX + self.src magnet = MAGNET_PREFIX + HashToMagnet(self.infohash, asHex = True) title = self.getTitle(kind = "original") if title: magnet += "&dn=" + quote(title.encode("utf-8")) tracker = self.getTracker() if tracker: magnet += "&tr=" + quote(tracker.encode("utf-8")) return magnet def postInitTasks(self): """ Tasks to perform when first starting adding this torrent to the display """ # Add a new item to the list self.listindex = self.list.GetItemCount() self.list.InsertStringItem(self.listindex, "") self.list.SetItemData(self.listindex, len(utility.torrents['all'])) utility.torrents["all"].append(self) utility.torrents["inactive"][self] = 1 # Read extra information about the torrent self.torrentconfig.readAll() # Set added time if needed if self.addedTime == 0: self.addedTime = time() # Allow updates self.status.dontupdate = False # Add Status info in List self.updateColumns(force = True) # Update the size to reflect torrents with pieces set to "download never" self.files.updateRealSize() # Do a quick check to see if it's finished self.status.isDoneUploading() def changeListIndex(self, listindex): self.listindex = listindex self.list.SetItemData(self.listindex, utility.torrents["all"].index(self)) def getColumnValue(self, colid = None, default = 0.0): """ As opposed to getColumnText, this will get numbers in their raw form for doing comparisons default is used when getting values for sorting comparisons (this way an empty string can be treated as less than 0.0) """ if colid is None: colid = COL_TITLE value = None activetorrent = self.status.isActive(checking = False, pause = False) try: if colid == COL_PROGRESS: # Progress if self.status.isActive(pause = False): progress = self.connection.engine.progress else: progress = self.files.progress value = progress elif colid == COL_PRIO: # Priority value = self.prio elif colid == COL_SIZE: # Size if self.hasMetadata: value = self.files.floattotalsize else: value = self.files.getSize() elif colid == COL_DLSPEED: # DL Speed if activetorrent and not self.status.completed: value = self.connection.engine.rate['down'] elif colid == COL_ULSPEED: # UL Speed if activetorrent: value = self.connection.engine.rate['up'] elif colid == COL_RATIO: # %U/D Size if self.files.downsize == 0.0 : if self.files.floattotalsize != 0.0: value = ((self.files.upsize/self.files.floattotalsize) * 100) else: value = 100 else: value = ((self.files.upsize/self.files.downsize) * 100) elif colid == COL_DONESIZE: # Done Size value = self.files.sizeDone elif colid == COL_DLSIZE: # Download Size value = self.files.downsize elif colid == COL_ULSIZE: # Upload Size value = self.files.upsize elif colid == COL_ETA: # ETA if activetorrent: if self.status.completed: if self.connection.getTargetSeedingTime() == 0 and \ self.connection.getSeedOption('uploadratio') == "0": value = 999999999999999 else: value = self.connection.seedingtimeleft elif self.connection.engine.eta is not None: value = self.connection.engine.eta elif colid == COL_RESOURCES: # Seeds/Peers if activetorrent: value = self.connection.engine.numconnections elif colid == COL_ADDED_ON: # Added On value = self.addedTime elif colid == COL_COMPLETED_ON: # Completed On value = self.completedTime elif colid == COL_SEEDING_TIME: if self.status.completed: value = self.connection.seedingtime else: value = self.getColumnText(colid) try: value = value.upper() except: pass except: value = self.getColumnText(colid) try: value = value.upper() except: pass if value is None or value == "": return default return value def getColumnText(self, colid, force = False): """ Get the text representation of a given column's data (used for display) """ text = None activetorrent = self.status.isActive(checking = False, pause = False) try: if colid == COL_TITLE: # Title if self.title is None: text = self.files.filename else: text = self.title elif colid == COL_PROGRESS: # Progress if utility.config.Read("progress_bars", "int") != 0 and not force: return "" progress = self.files.progress if self.status.isActive(pause = False): progress = self.connection.engine.progress # Truncate the progress value rather than round down # (will show 99.9% for incomplete torrents rather than 100.0%) progress = int(progress * 10)/10.0 text = ('%.1f' % progress) + "%" elif colid == COL_BTSTATUS: # BT Status text = self.status.getStatusText() elif colid == COL_PRIO: # Priority priorities = [ _('highest'), _('high'), _('normal'), _('low'), _('lowest') ] text = priorities[self.prio] elif colid == COL_SIZE: # Size if not self.hasMetadata: text = utility.size_format(self.files.getSize()) elif self.files.floattotalsize != self.files.realsize: # Some file pieces are set to "download never" label = utility.size_format(self.files.floattotalsize, textonly = True) realsizetext = utility.size_format(self.files.realsize, truncate = 1, stopearly = label, applylabel = False) totalsizetext = utility.size_format(self.files.floattotalsize, truncate = 1) text = realsizetext + "/" + totalsizetext else: text = utility.size_format(self.files.floattotalsize) elif (colid == COL_DLSPEED and activetorrent and not self.status.completed): # DL Speed value = self.connection.engine.rate['down'] text = utility.speed_format(value) elif colid == COL_ULSPEED and activetorrent: # UL Speed value = self.connection.engine.rate['up'] text = utility.speed_format(value) elif colid == COL_RATIO: # %U/D Size if self.files.downsize == 0.0 : if self.files.floattotalsize != 0.0: ratio = ((self.files.upsize/self.files.floattotalsize) * 100) else: ratio = 100 else: ratio = ((self.files.upsize/self.files.downsize) * 100) text = '%.1f' % (ratio) + "%" elif colid == COL_MESSAGE: # Error Message text = self.message elif colid == COL_DONESIZE: # Done Size if self.hasMetadata: text = utility.size_format(self.files.sizeDone) else: text = utility.size_format(self.files.sizeDone_metadataless) elif colid == COL_DLSIZE: # Download Size text = utility.size_format(self.files.downsize) elif colid == COL_ULSIZE: # Upload Size text = utility.size_format(self.files.upsize) elif colid == COL_ETA and activetorrent: # ETA if self.status.completed: if self.connection.getTargetSeedingTime() == 0 and \ self.connection.getSeedOption('uploadratio') == "0": text = "(oo)" else: value = self.connection.seedingtimeleft text = "(" + utility.eta_value(value) + ")" elif self.connection.engine.eta is not None: value = self.connection.engine.eta text = utility.eta_value(value) elif colid == COL_RESOURCES: # Seeds/Peers if activetorrent: seeds = ('%d' % self.connection.engine.numseeds) peers = ('%d' % self.connection.engine.numpeers) else: seeds = "0" peers = "0" text = seeds + "/" + peers + " [" + str(self.totalseeds) + "/" + str(self.totalpeers) + "]" elif colid == COL_TRACKER: # Tracker text = self.getTracker() elif colid == COL_ADDED_ON: # Added On text = "" if self.addedTime: text = strftime("%x %X", localtime(self.addedTime)) elif colid == COL_COMPLETED_ON: # Completed On text = "" if self.completedTime: text = strftime("%x %X", localtime(self.completedTime)) elif colid == COL_SEEDING_TIME: # Seeding time if self.status.completed: text = utility.eta_value(self.connection.seedingtime) else: text = "" except: exception_val = format_exc() if colid != COL_MESSAGE: self.changeMessage(exception_val, msg_type = "error") else: sys.stderr.write(exception_val) if text is None: text = "" return text def changeMessage(self, message = "", msg_type = "clear"): # Clear the error message if msg_type == "clear": self.message = "" self.updateColumns([COL_MESSAGE]) return if not message: return message = forceunicode(message) now = time() if msg_type == "error" or msg_type == "status": self.message = strftime('%H:%M', localtime(now)) + " - " + message self.updateColumns([COL_MESSAGE]) if msg_type == "error": wx.LogError(self.getTitle() + " - " + message) else: wx.LogMessage(self.getTitle() + " - " + message) def updateColumns(self, columnlist = None, force = False): """ Update multiple columns in the display if columnlist is None, update all columns (only visible columns will be updated) """ if columnlist is None: columnlist = range(self.list.columns.minid, self.list.columns.maxid) try: for colid in columnlist: # Don't do anything if shutting down or minimized if self.status.dontupdate or not utility.frame.GUIupdate: return # Only update if this column is currently shown rank = self.list.columns.getRankfromID(colid) if (rank == -1): continue text = self.getColumnText(colid) self.list.setString(self.listindex, rank, text, force) if COL_BTSTATUS in columnlist: self.updateStatusIcon(force) if COL_PROGRESS in columnlist: self.updateProgressBar() except wx.PyDeadObjectError: pass def updateProgressBar(self): if 0 <= self.listindex < len(self.list.progressBars): self.list.progressBars[self.listindex].SetValue(self.getColumnValue(COL_PROGRESS)) def updateStatusIcon(self, force = False): """ Update Status Icon """ value = self.status.value bmp = 0 if self.status.isActive(): if value == STATUS_PAUSE: bmp = ICON_PAUSED elif value == STATUS_SUPERSEED: bmp = ICON_SUPERSEED elif self.status.isCheckingOrAllocating(): bmp = ICON_CHECKING elif self.connection.engine is not None: status = self.status.getStatusText() if status == _("working"): bmp = ICON_WORKING elif status == _("seeding"): bmp = ICON_SEEDING elif status == _("connecting"): bmp = ICON_CONNECTING elif value == STATUS_QUEUE: if self.status.completed: bmp = ICON_COMPLETED_QUEUE else: bmp = ICON_QUEUE elif value == STATUS_FINISHED or self.status.completed: bmp = ICON_COMPLETED elif value == STATUS_STOP: bmp = ICON_STOPPED if self.listbitmap != bmp or force: self.listbitmap = bmp self.list.SetItemImage(self.listindex, bmp) def updateSingleItemStatus(self): """ Update the fields that change frequently for active torrents """ # Do check to see if we're done uploading self.status.isDoneUploading() self.updateColumns([COL_PROGRESS, COL_BTSTATUS, COL_ETA, COL_DLSPEED, COL_ULSPEED, COL_ULSIZE, COL_DONESIZE, COL_RESOURCES]) def getResponse(self, force = False): """ Get metainfo for the torrent """ if not force and self.status.isActive(): #active process metainfo = self.connection.engine.dow.getResponse() else: #not active process metainfo = get_metainfo(self.src) return metainfo def makeInactive(self, update = True): self.files.updateProgress() if self.status.value == STATUS_HASHCHECK: self.status.updateStatus(self.actions.oldstatus) elif self.status.value == STATUS_STOP: pass elif self.connection.engine is not None: # Ensure that this part only gets called once self.status.updateStatus(STATUS_QUEUE) # Write out to config self.torrentconfig.writeAll() if update: self.updateSingleItemStatus() def getTitle(self, kind = "current"): if kind == "original": title = self.metainfo['info']['name'] elif kind == "torrent": torrentfilename = os.path.split(self.src)[1] torrentfilename = torrentfilename[:torrentfilename.rfind('.torrent')] title = torrentfilename elif kind == "dest": if self.files.isFile(): destloc = self.files.dest else: destloc = self.files.getProcDest(pathonly = True, checkexists = False) title = os.path.split(destloc)[1] else: # current title = self.getColumnText(COL_TITLE) return title def changeTitle(self, title): if title == self.files.filename: self.title = None else: self.title = title self.torrentconfig.writeNameParams() self.updateColumns([COL_TITLE]) details = utility.window.details if details.getTorrent() == self: try: details.updateGeneral() except wx.PyDeadObjectError: pass def changePriority(self, prio): """ Change the priority for the torrent """ if prio > 4: prio = 4 if prio < 0: prio = 0 self.prio = prio self.updateColumns([COL_PRIO]) self.torrentconfig.writePriority() def shutdown(self): """ Things to do when shutting down a torrent """ # Set shutdown flag to true self.status.dontupdate = True self.torrentconfig.writeAll() self.connection.stopEngine() del utility.torrents["inactive"][self] def getDetailsString(self): """ Return a string with minimal info """ message = _("Status: ") + self.getColumnText(COL_BTSTATUS) + "\n" message += _("Progress: ") + self.getColumnText(COL_PROGRESS, True) + "\n" message += _("Ratio: ") + self.getColumnText(COL_RATIO) if self.status.isActive(checking = False, pause = False): message += '\n' + _("Upload: %s at %s") % (self.getColumnText(COL_ULSIZE), self.getColumnText(COL_ULSPEED)) if not self.status.completed: message += '\n' + _("Download: %s of %s at %s") % (self.getColumnText(COL_DONESIZE), self.getColumnText(COL_SIZE), self.getColumnText(COL_DLSPEED)) if self.getColumnText(COL_ETA): message += "\n" + _("ETA: ") + self.getColumnText(COL_ETA) if self.message: message += "\n" + self.message return message def getInfo(self, fieldlist = None): """ Get information about the torrent to return to the webservice """ # Default to returning all fields if fieldlist is None: fieldlist = range(self.list.columns.minid, self.list.columns.maxid) try : retmsg = "" for colid in fieldlist: retmsg += self.getColumnText(colid) + "|" retmsg += self.infohash + "\n" return retmsg except: # Should never get to this point return "|" * len(fieldlist) + "\n" def updateScrapeData(self, newpeer, newseed, message = "", messagetype = "status"): """ Update the torrent with new scrape information """ self.actions.lastgetscrape = time() self.totalpeers = newpeer self.totalseeds = newseed self.updateColumns([COL_RESOURCES]) detailwin = utility.window.details if detailwin.getTorrent() == self and detailwin.IsShown() and detailwin.networkPage.IsShown(): detailwin.updateNetwork('transfer') if message != "": if message == _('Scraping'): msgtype = "status" elif message == _('Scraping Done'): msgtype = "status" message += ": %s seeds %s peers" % (self.totalseeds, self.totalpeers) else: msgtype = "error" self.changeMessage(message, msgtype) # Auto Start self.autoStartCheck() def autoStartCheck(self): try: totalseeds = int(self.totalseeds) except: return False if not utility.config.Read('autostart_scrape', "boolean"): return False if self.status.value == STATUS_QUEUE: if self.status.completed and totalseeds < utility.config.Read('autostart_scrape_value', "int"): if self.actions.resume(): self.status.autostarted = True elif self.status.isActive(checking = False) and self.status.autostarted: if totalseeds >= utility.config.Read('autostart_scrape_value', "int"): self.actions.queue() self.status.autostarted = False