def __init__(self): wx.EvtHandler.__init__(self) SafeInvocation.__init__(self) utility.queue = self utility.actionhandler = ActionHandler() self.ratemanager = RateManager() self.addtorrents = AddTorrents() self.timers = {} self.doneflag = Event() self.totals = { 'up' : 0.0, 'down' : 0.0, 'connections': 0 } self.totals_kb = { 'up': 0.0, 'down': 0.0 } self.volume = {'up': utility.config.Read('uploadvolume', "int"), 'down': utility.config.Read('downloadvolume', "int") } self.whenidle = 0 # 0=Nothing, 1=Close, 2=Shutdown self.lastDirScan = 0 self.lastScrape = 0 self.UpdateRunningTorrentCounters()
class Scheduler(SafeInvocation, wx.EvtHandler): """ Determine which torrents need to run, update global stats, and deal with loading, moving, and removing torrents. """ def __init__(self): wx.EvtHandler.__init__(self) SafeInvocation.__init__(self) utility.queue = self utility.actionhandler = ActionHandler() self.ratemanager = RateManager() self.addtorrents = AddTorrents() self.timers = {} self.doneflag = Event() self.totals = { 'up' : 0.0, 'down' : 0.0, 'connections': 0 } self.totals_kb = { 'up': 0.0, 'down': 0.0 } self.volume = {'up': utility.config.Read('uploadvolume', "int"), 'down': utility.config.Read('downloadvolume', "int") } self.whenidle = 0 # 0=Nothing, 1=Close, 2=Shutdown self.lastDirScan = 0 self.lastScrape = 0 self.UpdateRunningTorrentCounters() def postInitTasks(self): # Read old list from torrent.lst #################################### self.addtorrents.readTorrentList() # Wait until after creating the list and adding torrents # to start CyclicalTasks in the scheduler self._CyclicalTasks() def _CalculateTorrentCounters(self): """ Update the counters for torrents in a single unified place """ torrents_active = utility.torrents["active"].keys() paused = {} seeding = {} downloading = {} for torrent in torrents_active: # Torrent is active if (torrent.status.value == STATUS_HASHCHECK): activevalues = [ STATUS_ACTIVE, STATUS_PAUSE, STATUS_SUPERSEED ] # Torrent is doing a hash check # (Count towards counters if it was active before the the check, # otherwise don't) if not torrent.actions.oldstatus in activevalues: continue if torrent.status.value == STATUS_PAUSE: paused[torrent] = 1 if torrent.status.completed: seeding[torrent] = 1 else: downloading[torrent] = 1 utility.torrents["paused"] = paused utility.torrents["seeding"] = seeding utility.torrents["downloading"] = downloading def getProcCount(self): return len(utility.torrents[self.getActiveGroup()]) def getActiveGroup(self): groups = ("downloading", "seeding", "active") consideractive = utility.config.Read('consideractive', "int") consideractive = max(min(consideractive, 2), 0) return groups[consideractive] def UpdateRunningTorrentCounters(self): self._CalculateTorrentCounters() def _getDownUpConnections(self): """ Ask UD/DL speed of all threads """ totalupload = 0.0 totaldownload = 0.0 for torrent in utility.torrents["active"].keys(): if torrent.status.value != STATUS_PAUSE: totalupload += torrent.getColumnValue(COL_ULSPEED) totaldownload += torrent.getColumnValue(COL_DLSPEED) self.totals['up'] = totalupload self.totals_kb['up'] = (totalupload / 1024.0) self.totals['down'] = totaldownload self.totals_kb['down'] = (totaldownload / 1024.0) def getSpeed(self, dir): maxrate = self.ratemanager.MaxRate(dir) if maxrate == 0: return utility.speed_format(self.totals[dir], truncate = 1) speed = utility.size_format(self.totals[dir], truncate = 1, stopearly = "KB", applylabel = False) ratecap = utility.speed_format((maxrate * 1024), truncate = 0, stopearly = "KB") return speed + " / " + ratecap def _updateTrayAndStatusBar(self): self.invokeLater(self._onUpdateTrayAndStatusBar) def _onUpdateTrayAndStatusBar(self): uploadspeed = self.getSpeed("up") downloadspeed = self.getSpeed("down") try: # update value in minimize icon ########################################### if utility.frame.tbicon and utility.frame.tbicon.IsIconInstalled(): icontext = product_name + "\n\n" + \ _('DL:') + " " + downloadspeed + "\n" + \ _('UL:') + " " + uploadspeed + " " utility.frame.tbicon.setIcon(icontext) # update in status bar ########################################## if hasattr(utility.frame, "StatusBar") and utility.frame.StatusBar.IsShown(): utility.frame.StatusBar.updateFields() # update speed graph ########################################## utility.window.details.updateGraph(self.totals_kb['up'], self.totals_kb['down']) except wx.PyDeadObjectError: pass def _CyclicalTasks(self): self._getDownUpConnections() self._updateTrayAndStatusBar() self.ratemanager.RunTasks() try: # Run postponed deleting events while utility.window.postponedevents: ev = utility.window.postponedevents.pop(0) #print "POSTPONED EVENT : ", ev[0] # Handle multiple arguments funcname = ev.pop(0) funcname(*ev) utility.window.list.Enable() except wx.PyDeadObjectError: pass # Run Directory Scanner freq = utility.config.Read('scandirfreq', "int") if utility.config.Read('scandiractive', "boolean") and (self.lastDirScan == 0 or clock() - self.lastDirScan > freq): self.invokeLater(self._ScanDir) self.lastDirScan = clock() # Scrape Queued Torrents if utility.config.Read('scrapequeued', "boolean") and (self.lastScrape == 0 or clock() - self.lastScrape > 300): self.invokeLater(self._ScrapeQueuedTorrents) self.lastScrape = clock() # Try invoking the scheduler # (just in case we need to start more stuff: # should return almost immediately otherwise) self.invokeLater(self._Scheduler) # Start Timer ########################################## self.timers['frequent'] = Timer(2, self._CyclicalTasks) self.timers['frequent'].start() def updateAndInvoke(self, updateCounters = True, invokeLater = True): if updateCounters: # Update counter for running torrents self.UpdateRunningTorrentCounters() # Only invoke the scheduler if we're not shutting down if invokeLater: self.invokeLater(self._Scheduler) self.invokeLater(self.checkIdleForClosing) def _updateTorrentList(self): torrentconfig = utility.torrentconfig maxindex = len(utility.torrents["all"]) items = utility.torrentconfig.Items() # Delete all the items beyond the torrents that exist if len(items) > maxindex: for index, src in items: try: if int(index) >= maxindex: torrentconfig.DeleteEntry(index) except: # (Got an index that wasn't a number?) torrentconfig.DeleteEntry(index) for torrent in utility.torrents["all"]: torrent.torrentconfig.writeSrc(False) torrentconfig.Flush() def getInactiveTorrents(self, numtorrents, completeOnly = False, incompleteOnly = False): if numtorrents < 0: numtorrents = 0 torrents_inactive = utility.torrents["inactive"].keys() # Find which torrents are queued: if completeOnly: inactivetorrents = [torrent for torrent in torrents_inactive if (torrent.status.completed and torrent.status.value == STATUS_QUEUE)] elif incompleteOnly: inactivetorrents = [torrent for torrent in torrents_inactive if (not torrent.status.completed and torrent.status.value == STATUS_QUEUE)] else: inactivetorrents = [torrent for torrent in torrents_inactive if (torrent.status.value == STATUS_QUEUE)] inactivelength = len(inactivetorrents) if inactivelength > numtorrents: # Sort by listindex inactivetorrents.sort(key = attrgetter('listindex')) # Sort by seeding/downloading if utility.config.Read('preferuncompleted', "boolean"): inactivetorrents.sort(key = lambda x:x.status.completed) # Sort by priority inactivetorrents.sort(key = attrgetter('prio')) # Slice off the number of torrents we need to start inactivetorrents = inactivetorrents[0:numtorrents] return inactivetorrents def getActiveTorrents(self, numtorrents): if numtorrents < 0: numtorrents = 0 activetorrents = [torrent for torrent in utility.torrents["all"] if torrent.status.isActive()] activelength = len(activetorrents) if activelength > numtorrents: # Sort by listindex activetorrents.sort(key = attrgetter('listindex')) # Sort by seeding/downloading if utility.config.Read('preferuncompleted', "boolean"): activetorrents.sort(key = lambda x:x.status.completed) # Sort by priority activetorrents.sort(key = attrgetter('prio')) # Worst first activetorrents.reverse() # Slice off the number of torrents we need activetorrents = activetorrents[0:numtorrents] return activetorrents def _ScanDir(self): self.scandir = utility.config.Read('scandir') if not utility.config.Read('setdefaultfolder', "boolean"): error = _("Could not start Directory Scanner, you must set a default download folder") elif not os.path.isdir(self.scandir): error = _("Could not start Directory Scanner, the scan folder is not a directory") elif self.scandir == os.path.join(utility.getConfigPath(), 'torrent'): error = _("Could not start Directory Scanner, you can't use the default torrent directory") else: error = None if error: wx.LogError(error) utility.config.Write('scandiractive', "0") utility.actions[ACTION_DIRSCANNER].updateButton() return scanned = [entry for entry in os.listdir(self.scandir) if os.path.isfile(os.path.join(self.scandir, entry)) and os.access(os.path.join(self.scandir, entry), os.R_OK) and not entry.startswith(".") and (entry.endswith(".torrent") or entry.endswith(".txt") or os.path.getsize(os.path.join(self.scandir, entry)) < 1024*25)] if scanned: scanneddir = os.path.join(self.scandir, 'scanned') baddir = os.path.join(self.scandir, 'bad') for file in scanned: src = os.path.join(self.scandir, file) try: dump1, msg, dump2 = utility.queue.addtorrents.AddTorrentFromFile(src, dotTorrentDuplicate = True, caller = "web") except: continue if msg == _('OK'): if utility.config.Read('scandirmovetor', "boolean"): moveto = scanneddir else: moveto = None wx.LogMessage('Directory Scanner: %s loaded' % (src)) elif file.endswith(".torrent"): moveto = baddir else: continue # Move file if moveto is None: try: os.remove(src) except: pass continue elif not os.path.exists(moveto): os.makedirs(moveto) elif os.path.isfile(moveto): continue dest = findUniqueFileName(os.path.join(moveto, file), 'scan') if dest is None: continue try: os.rename(src, dest) except: pass def _ScrapeQueuedTorrents(self): for torrent in utility.torrents["all"]: if torrent.status.value == STATUS_QUEUE: # Scrape queued torrents every hour torrent.actions.scrape(interval = 60) def _Scheduler(self): """ Find new processes to start """ if self.doneflag.isSet(): return self.doneflag.set() # Max upload volume maxuploadvolume = utility.config.Read('maxuploadvolume', "int") uploadvolume = self.volume['up'] / 1048576.0 if maxuploadvolume > 0 and uploadvolume >= maxuploadvolume: toqueue = [torrent for torrent in utility.torrents["all"] if torrent.status.isActive()] utility.actionhandler.procQUEUE(toqueue) self.UpdateRunningTorrentCounters() self.doneflag.clear() return # Max download volume maxdownloadvolume = utility.config.Read('maxdownloadvolume', "int") downloadvolume = self.volume['down'] / 1048576.0 if maxdownloadvolume > 0 and downloadvolume >= maxdownloadvolume: toqueue = [torrent for torrent in utility.torrents["all"] if torrent.status.isActive() and not torrent.status.completed] utility.actionhandler.procQUEUE(toqueue) self.UpdateRunningTorrentCounters() self.doneflag.clear() return inactivestarted = 0 # Start all completed torrents if we only consider downloading torrents as active if self.getActiveGroup() == "downloading": # Get all inactive torrents that are complete inactivetorrents = self.getInactiveTorrents(len(utility.torrents["inactive"]), completeOnly = True) # Start them if they're not already started for torrent in inactivetorrents: if torrent.actions.resume(): inactivestarted += 1 if inactivestarted > 0: self.UpdateRunningTorrentCounters() # Start all incompleted torrents if we only consider seeding torrents as active elif self.getActiveGroup() == "seeding": # Get all inactive torrents that are incomplete inactivetorrents = self.getInactiveTorrents(len(utility.torrents["inactive"]), incompleteOnly = True) # Start them if they're not already started for torrent in inactivetorrents: if torrent.actions.resume(): inactivestarted += 1 if inactivestarted > 0: self.UpdateRunningTorrentCounters() numsimdownload = utility.config.Read('numsimdownload', "int") # Max number of torrents to start torrentstostart = numsimdownload - self.getProcCount() if torrentstostart > 0: inactivestarted = 0 # Start torrents inactivetorrents = self.getInactiveTorrents(torrentstostart) for torrent in inactivetorrents: if torrent.actions.resume(): inactivestarted += 1 torrentstostart = torrentstostart - inactivestarted if inactivestarted > 0: self.UpdateRunningTorrentCounters() self.doneflag.clear() def checkIdleForClosing(self): if self.whenidle and not utility.torrents["active"]: # Wait 60 seconds before closing messages = (0, _("Closing Client"), _("Closing Client And Shutting Computer Down")) delacdlg = wx.ProgressDialog(messages[self.whenidle], _('The client is idle and will close soon'), maximum = 240, parent = utility.frame, style = wx.PD_CAN_ABORT | wx.PD_AUTO_HIDE) keepgoing = True count = 0 while keepgoing and count < 240: count += 1 wx.MilliSleep(250) keepgoing = delacdlg.Update(count) if type(keepgoing) is tuple: keepgoing = keepgoing[0] delacdlg.Destroy() if keepgoing: # Close self.invokeLater(utility.frame.OnCloseWindow, kwargs = {'silent':True, 'shutdown':self.whenidle==2}) else: # Cancel Closing self.whenidle = 0 utility.actions[ACTION_ONIDLE].CheckNeeded() def changeABCParams(self): for torrent in utility.torrents["all"]: #Local doesn't need to affect with change ABC Params torrent.connection.resetUploadParams() self.updateAndInvoke() def clearAllCompleted(self, removelist = None): """ Clear all completed torrents from the list Passing in a list of torrents to remove + move allows for a torrent to auto-clear itself when completed """ if removelist is None: removelist = [torrent for torrent in utility.torrents["inactive"].keys() if torrent.status.isDoneUploading()] # See if we need to move the completed torrents # before we remove them from the list if utility.config.Read('movecompleted', "boolean") and utility.config.Read('defaultmovedir') and \ utility.config.Read('movecompletedonclear', "boolean"): utility.actionhandler.procMOVE(removelist) # Remove the torrents utility.actionhandler.procREMOVE(removelist) def clearScheduler(self): # Stop frequent timer try: if self.timers['frequent'] is not None: self.timers['frequent'].cancel() except: pass torrents_inactive = utility.torrents["inactive"].keys() # Call shutdown on inactive torrents # (controller.stop will take care of the rest) for torrent in torrents_inactive: torrent.shutdown() # Stop all active torrents utility.controller.stop() # Store volume utility.config.Write('uploadvolume', self.volume['up'], "int") utility.config.Write('downloadvolume', self.volume['down'], "int") # Stop DHT if utility.dht: utility.dht.close() # Update the torrent list self._updateTorrentList() def getTorrent(self, index = -1, info_hash = None): """ Look for a torrent in the list, either by its index or by its infohash """ # Find the hash by the index if index >= 0 and index <= len(utility.torrents['all']): inAllIndex = utility.window.list.GetItemData(index) return utility.torrents["all"][inAllIndex] # Can't find it by index and the hash is none # We're out of luck if info_hash is None: return None # Look for the hash value for torrent in utility.torrents["all"]: if torrent.infohash == info_hash: return torrent def sortList(self, colid = 0, reverse = False): utility.torrents["all"].sort(key = lambda x: x.getColumnValue(colid, -1.0), reverse = reverse) self.updateListIndex() def updateListIndex(self, startindex = 0, endindex = None): if utility.activeGroup != "all": return # Can't update indexes for things that aren't in the list anymore if startindex >= len(utility.torrents["all"]): return if startindex < 0: startindex = 0 if endindex is None or endindex >= len(utility.torrents["all"]): endindex = len(utility.torrents["all"]) - 1 for i in range(startindex, endindex + 1): torrent = utility.torrents["all"][i] torrent.changeListIndex(i) torrent.status.dontupdate = False torrent.updateColumns(force = True) torrent.torrentconfig.writeSrc(False) utility.torrentconfig.Flush() utility.window.list.updateDetails() ArtManager.Get().MakeAlternateList(utility.window.list) def filterGroups(self, group): for torrent in utility.torrents["all"]: torrent.status.dontupdate = True utility.window.list.setNumberOfItems(len(utility.torrents[group])) newindex = 0 for torrent in utility.torrents[group]: torrent.changeListIndex(newindex) torrent.status.dontupdate = False torrent.updateColumns(force = True) newindex += 1 utility.activeGroup = group print group ArtManager.Get().MakeAlternateList(utility.window.list) def MoveItems(self, listtomove, direction = 1): listtomove.sort() if direction == 1: listtomove.reverse() startoffset = -1 endoffset = 0 else: direction = -1 startoffset = 0 endoffset = 1 newloc = [] for index in listtomove: if (direction == 1) and (index == len(utility.torrents["all"]) - 1): newloc.append(index) elif (direction == -1) and (index == 0): newloc.append(index) elif newloc.count(index + direction) != 0 : newloc.append(index) else: torrent = utility.torrents["all"].pop(index) utility.torrents["all"].insert(index + direction, torrent) newloc.append(index + direction) if newloc: newloc.sort() start = newloc[0] + startoffset end = newloc[-1] + endoffset self.updateListIndex(startindex = start, endindex = end) return newloc def MoveItemsTop(self, selected): for index in selected: if index != 0: # First Item can't move up anymore torrent = utility.torrents["all"].pop(index) utility.torrents["all"].insert(0, torrent) if selected: self.updateListIndex(startindex = 0, endindex = selected[0]) return True def MoveItemsBottom(self, selected): for index in selected: if index < len(utility.torrents["all"]) - 1: torrent = utility.torrents["all"].pop(index) utility.torrents["all"].append(torrent) if selected: self.updateListIndex(startindex = selected[0]) return True def addTorrentFromFileCallback(self, *args, **kwargs): self.invokeLater(self.addtorrents.AddTorrentFromFile, args, kwargs) def addTorrentFromMetainfoCallback(self, *args, **kwargs): self.invokeLater(self.addtorrents.AddTorrentFromMetainfo, args, kwargs)