class Utility: """ # Generic "glue" class that contains commonly used helper # functions and helps to keep track of objects """ def __init__(self, path): self.path = path self.browsers = [] self.browser = "" self.quitting = False self.configdir = None self.dir_root = self.setupConfigPath() self.MakeDir("torrent") self.MakeDir("database") self.MakeDir("tracker") self.MakeDir("ipfilter") self.MoveOldFiles() # Set Lang self.lang = Lang(self) # Reg Checker self.regchecker = RegChecker(self) # Keep track of torrents self.setupTorrentList() self.torrents = { "all": [], "active": {}, "inactive": {}, "paused": {}, "seeding": {}, "downloading": {} } self.activeGroup = "all" # Keep track of all the "ManagedList" objects in use self.lists = {} self.imagelist = None self.images = {} # Set Config self.config = self.setupConfig() self.webconfig = self.setupWebConfig() self.makerconfig = self.setupTorrentMakerConfig() self.lastdir = { "save" : self.config.Read('defaultfolder'), "open" : "", "log": "" } self.configdir.deleteOldCacheData(self.config.Read('expire_cache_data', "int")) # Install Open New command if os.name == "posix" and sys.platform != "darwin": self.install_open_new() def MoveOldFiles(self): oldpath = self.getPath() newpath = self.getConfigPath() files = ["torrent.list", "lh.conf", "maker.conf", "search.conf", "torrent"] for name in files: oldname = os.path.join(oldpath, name) if existsAndIsReadable(oldname): newname = os.path.join(newpath, name) try: move(oldname, newname) except: pass def getLastDir(self, operation = "save"): lastdir = self.lastdir[operation] if operation == "save": if not os.access(lastdir, os.F_OK): lastdir = self.config.Read('defaultfolder') if not os.access(lastdir, os.F_OK): lastdir = "" return lastdir def MakeDir(self, strDir): path = os.path.join(self.getConfigPath(), strDir) pathexists = os.access(path, os.F_OK) # If the database directory doesn't exist, create it now if not pathexists: os.mkdir(path) def getDatabaseDir(self): return os.path.join(self.getConfigPath(), "database") def setupConfigPath(self): """ Sets the config path at %appdata% """ self.configdir = ConfigDir(local = wx.GetApp().local) return forceunicode(self.configdir.dir_root) def setupConfig(self): """ Main config """ if (sys.platform[:3] == 'win'): # Default directory for windows defaultdir = "C:\\Downloads" else: # Default directory for unix defaultdir = os.path.join(os.path.expanduser('~'), "Downloads") defaults = { # General 'lang': str(wx.LANGUAGE_DEFAULT), 'confirmonclose': '1', 'checkforupdates': '1', 'ipfilter': '1', 'filelogger': '0', 'rsstimer': '1800', 'rsstimerstatus': '1', 'hotkey': '0', 'hotkeymod': '0', 'hotkeykeycode': '0', 'hotkeywxkeycode': '0', 'associate' : '1', 'mintray_balloontips': '0', 'homepage': home_page, # Bandwidth 'maxupload': '5', 'maxuploadrate': '0', 'maxdownloadrate': '0', 'maxseeduploadrate': '0', 'numsimdownload': '2', 'uploadtime': '0', 'uploadratio': '150', 'maxuploadvolume': '0', 'maxdownloadvolume': '0', 'uploadvolume': '0', 'downloadvolume': '0', # Disk 'movecompleted': '0', 'setdefaultfolder': '0', 'defaultfolder': defaultdir, 'scandir': '', 'scandirfreq': '300', 'scandirmovetor': '1', 'defrentorwithdest': '1', 'diskfullthreshold': '0', 'buffer_write' : '4', 'buffer_read' : '1', 'auto_flush' : '0', 'addlabeltomovedir': '0', # Security 'kickban': '1', 'notsameip': '1', 'scrape': '1', 'scrapequeued': '1', # BitTornado 'dht': '1', 'tracker': '0', 'pex_allowed': '0', 'expire_cache_data': '10', 'minport': "49152", 'maxport': "65535", 'useport': "-1", 'ipv6': '0', 'ipv6_binds_v4': '1', 'min_peers': '20', 'max_initiate': '40', 'alloc_type': 'normal', 'alloc_rate': '2', 'max_files_open': '50', 'max_connections': '60', # no need for more than that 'lock_files': '1', 'lock_while_reading': '0', 'double_check': '1', 'triple_check': '0', 'crypto_mode': str(int(CRYPTO_OK)), 'display_interval': '0.8', 'peercache': '60', # Queue 'consideractive': '2', 'preferuncompleted': '0', 'timeouttracker': '15', 'timeoutdownload': '30', 'timeoutupload': '1', 'defaultpriority': '2', 'defaultstatus': '1', 'autostart_upload': '0', 'autostart_upload_threshold': '10', 'autostart_upload_delay': '300', 'autostart_download': '0', 'autostart_download_threshold': '0', 'autostart_download_delay': '300', 'autostart_scrape': '0', 'autostart_scrape_value': '1', # Constant 'fastresume': '1', 'removetorrent': '1', 'savecolumnwidth': '1', # Network 'useportrange': '0', 'upnp_nat_access': '0', 'natpmp': '0', 'trackeraddress': '', # View 'peers_list_sort_colid': '-1', 'peers_list_sort_flag': '0', 'listfont': '', 'allowmenubitmaps': '1', 'progress_bars': '0', 'show_statusbar': '1', 'show_toolbar': '1', 'show_inspector': '1', 'tb_size': '1', 'tb_style': '2', 'mintray': str(int(wx.Platform == "__WXMSW__")), 'maximized': '0', 'window_x': '', 'window_y': '', 'window_width': '792', 'window_height':'550', 'splitter_pos': '500', 'rss_column0_rank': '0', 'rss_column1_rank': '1', 'rss_column2_rank': '2', 'rss_column0_width': '335', 'rss_column1_width': '225', 'rss_column2_width': '200', 'statusbar1_rank': '1', 'statusbar2_rank': '1', 'statusbar3_rank': '1', 'statusbar4_rank': '1', 'column0_rank': '0', 'column1_rank': '1', 'column2_rank': '-1', 'column3_rank': '2', 'column4_rank': '3', 'column5_rank': '4', 'column6_rank': '5', 'column7_rank': '6', 'column8_rank': '7', 'column9_rank': '-1', 'column10_rank': '-1', 'column11_rank': '-1', 'column12_rank': '-1', 'column13_rank': '-1', 'column14_rank': '-1', 'column15_rank': '-1', 'column16_rank': '-1', 'column17_rank': '-1', 'column0_width': '235', 'column1_width': '85', 'column3_width': '75', 'column4_width': '75', 'column6_width': '80', 'column7_width': '75', 'column8_width': '75', 'column13_width': '80', 'column14_width': '80', 'column15_width': '80', 'column16_width': '80', 'column17_width': '80', 'fileinfo0_rank': '0', 'fileinfo0_width': '300', 'fileinfo1_rank': '1', 'fileinfo1_width': '100', 'fileinfo2_rank': '2', 'fileinfo2_width': '60', 'fileinfo3_rank': '3', 'fileinfo3_width': '200', 'fileinfo4_rank': '-1', 'fileinfo4_width': '200', 'fileinfo5_rank': '-1', 'fileinfo5_width': '200', 'fileinfo6_rank': '-1', 'fileinfo6_width': '200', 'icons_toolbartop':[ACTION_VIEWTRANSFER, ACTION_VIEWSEARCH, ACTION_VIEWRSS, ACTION_VIEWLOG, -1, ACTION_RESUME, ACTION_PAUSE, ACTION_STOP, ACTION_QUEUE, ACTION_REMOVE, -1, ACTION_TORRENTDETAILS, ], 'rightclickmenu': [ACTION_RESUME, ACTION_PAUSE, ACTION_STOP, ACTION_QUEUE, -1, ACTION_HASHCHECK, ACTION_SUPERSEED, ACTION_SCRAPE, -1, ACTION_REMOVE, ACTION_REMOVEFILE, -1, ACTION_SET_PRIORITY, ACTION_OPENDEST, ACTION_CHANGEDEST], 'taskbarmenu': [ACTION_EXIT] } configfilepath = os.path.join(self.getConfigPath(), "lh.conf") return ConfigReader(configfilepath, "LH", defaults) def setupWebConfig(self): defaults = { 'username': '******', 'password': '', 'authorization': 'digest', 'host': '127.0.0.1', 'port': 56667, 'webautostart': '0', 'allow_quit': '1', 'allow_version': '1', 'allow_query': '1', 'allow_getfiles': '1', 'allow_setfileprio': '1', 'allow_getsettings': '0', 'allow_setsettings': '0', 'allow_getprops': '1', 'allow_setprops': '1', 'allow_setpriority': '1', 'allow_addurl': '1', 'allow_addfile': '1', 'allow_remove': '1', 'allow_removedata': '0', 'allow_queue': '1', 'allow_pause': '1', 'allow_stop': '1', 'allow_start': '1', 'allow_move': '1', 'allow_superseed': '1', 'allow_recheck': '1', } webconfigfilepath = os.path.join(self.getConfigPath(), "webservice.conf") return ConfigReader(webconfigfilepath, "LH/Webservice", defaults) def setupTorrentMakerConfig(self): """ Torrent maker config """ defaults = { 'piece_size': '0', 'comment': '', 'created_by': '', 'announcedefault': '', 'announcehistory': '', 'announce-list': '', 'dhtnodes': '', 'httpseeds': '', 'makehash_md5': '0', 'makehash_crc32': '0', 'makehash_sha1': '0', 'startnow': '0', 'savetorrent': '2', 'privatetorrent': '0' } torrentmakerconfigfilepath = os.path.join(self.getConfigPath(), "maker.conf") return ConfigReader(torrentmakerconfigfilepath, "LH/TorrentMaker", defaults) def getConfigPath(self): """ Returns the main config path """ return self.dir_root def setupTorrentList(self): torrentfilepath = os.path.join(self.getConfigPath(), "torrent.list") self.torrentconfig = ConfigReader(torrentfilepath, "list0") def postAppInit(self): """ Called at app initialization Sets the icon and all the actions """ # Set icon if wx.Platform == "__WXGTK__": img = wx.Image(os.path.join(self.getSharePath(), 'icon_bt.png'), wx.BITMAP_TYPE_PNG) self.icon = wx.IconFromBitmap(img.Scale(24, 24).ConvertToBitmap()) self.smallIcon = wx.IconFromBitmap(img.Scale(20, 21).ConvertToBitmap()) else: iconPath = os.path.join(self.getSharePath(), 'icon_bt.ico') self.icon = wx.Icon(iconPath, wx.BITMAP_TYPE_ICO) self.smallIcon = self.icon ## iconPath = os.path.join(self.getSharePath(), 'icon_bt.ico') ## self.icon = wx.Icon(iconPath, wx.BITMAP_TYPE_ICO) ## if wx.Platform == "__WXGTK__": ## icons = wx.IconBundle() ## icons.AddIconFromFile(iconPath, wx.BITMAP_TYPE_ICO) ## self.smallIcon = icons.GetIcon((16,16)) ## else: ## self.smallIcon = self.icon # Load supported languages self.lang.loadLanguages() # Load Actions self.makeactionlist() def makeactionlist(self): """ (Re)creates all actions """ makeActionList() def getPath(self): """ Returns the application path """ return self.path.decode(sys.getfilesystemencoding()) def getSharePath(self): path = os.path.join(sys.prefix, 'share', 'lh-abc') if not existsAndIsReadable(path): path = self.getPath() return path def makeBitmap(self, section, bitmap, trans_color = None): """ Returns a new bitmap from the icons directory """ if bitmap.endswith('.png'): bmpType = wx.BITMAP_TYPE_PNG else: bmpType = wx.BITMAP_TYPE_BMP button_bmp = wx.Bitmap(os.path.join(self.getSharePath(), "icons", section, bitmap), bmpType) if trans_color is not None: button_mask = wx.Mask(button_bmp, trans_color) button_bmp.SetMask(button_mask) return button_bmp def makeBitmapButton(self, parent, bitmap, tooltip, event, trans_color = wx.Colour(200, 200, 200), btnstyle = None, ext = (10, 10), section = 'misc'): """ Returns a bitmap button using an icon from the icons directory """ if btnstyle is None: btnstyle = wx.BU_AUTODRAW if not isinstance(bitmap, wx.Bitmap): bitmap = self.makeBitmap(section, bitmap, trans_color) ID_BUTTON = wx.NewId() button_btn = wx.BitmapButton(parent, ID_BUTTON, bitmap, size=wx.Size(bitmap.GetWidth()+ext[0], bitmap.GetHeight()+ext[1]), style=btnstyle) button_btn.SetToolTipString(tooltip) parent.Bind(wx.EVT_BUTTON, event, button_btn) return button_btn def makePopup(self, menu, event = None, label = "", extralabel = "", bindto = None, kind = wx.ITEM_NORMAL, ID = None): """ Make an entry for a popup menu """ text = "" if label != "": text = label text += extralabel if ID: newid = ID else: newid = wx.NewId() if event is not None: if bindto is None: bindto = menu bindto.Bind(wx.EVT_MENU, event, id = newid) menu.Append(newid, text, kind = kind) return newid def eta_value(self, n, truncate = 3): if n == -1: return '<unknown>' if not n: return '' n = int(n) week, r1 = divmod(n, 60 * 60 * 24 * 7) day, r2 = divmod(r1, 60 * 60 * 24) hour, r3 = divmod(r2, 60 * 60) minute, sec = divmod(r3, 60) if week > 1000: return '<unknown>' weekstr = '%d' % (week) + _('w') daystr = '%d' % (day) + _('d') hourstr = '%d' % (hour) + _('h') minutestr = '%02d' % (minute) + _('m') secstr = '%02d' % (sec) + _('s') if week > 0: text = weekstr if truncate > 1: text += ":" + daystr if truncate > 2: text += "-" + hourstr elif day > 0: text = daystr if truncate > 1: text += "-" + hourstr if truncate > 2: text += ":" + minutestr elif hour > 0: text = hourstr if truncate > 1: text += ":" + minutestr if truncate > 2: text += ":" + secstr else: text = minutestr if truncate > 1: text += ":" + secstr return text def speed_format(self, s, truncate = 1, stopearly = None): return self.size_format(s, truncate, stopearly) + "/" + _('s') def size_format(self, s, truncate = None, stopearly = None, applylabel = True, rawsize = False, showbytes = False, labelonly = False, textonly = False): size = 0.0 label = "" if truncate is None: truncate = 2 if ((s < 1024) and showbytes and stopearly is None) or stopearly == "Byte": truncate = 0 size = s text = _("Byte") elif ((s < 1048576) and stopearly is None) or stopearly == "KB": size = (s/1024.0) text = _("KB") elif ((s < 1073741824L) and stopearly is None) or stopearly == "MB": size = (s/1048576.0) text = _("MB") elif ((s < 1099511627776L) and stopearly is None) or stopearly == "GB": size = (s/1073741824.0) text = _("GB") else: size = (s/1099511627776.0) text = _("TB") if textonly: return text label = text if labelonly: return label if rawsize: return size truncatestring = '%.' + str(truncate) + 'f' text = (truncatestring % size) if applylabel: text += ' ' + label return text def getBTParams(self, skipcheck = False): # Construct BT params ########################### btparams = [] btparams.append("--display_interval") btparams.append(self.config.Read('display_interval')) # ipv6 if sys.version_info >= (2,3) and socket.has_ipv6 and \ self.config.Read('ipv6', "boolean"): btparams.append("--ipv6_enabled") btparams.append("1") btparams.append("--ipv6_binds_v4") btparams.append(self.config.Read('ipv6_binds_v4')) # See if we use a fixed port, or pick a random port from a range useportrange = self.config.Read('useportrange', 'boolean') minport = self.config.Read('minport', "int") maxport = self.config.Read('maxport', "int") useport = self.config.Read('useport', "int") # Pick a random port between minport and maxport if useportrange or useport < 0: useport = str(random.randint(minport, maxport)) else: useport = str(useport) self.config.Write('useport', useport) # Use single port only btparams.append("--minport") btparams.append(useport) btparams.append("--maxport") btparams.append(useport) # Fast resume btparams.append("--selector_enabled") btparams.append(self.config.Read('fastresume')) btparams.append("--auto_kick") btparams.append(self.config.Read('kickban')) btparams.append("--security") btparams.append(self.config.Read('notsameip')) btparams.append("--max_upload_rate") btparams.append("0") # crypto_mode: # @0 disabled # @1 enabled # @2 forced # @3 stealth crypto_mode = self.config.Read('crypto_mode', "int") btparams.append("--crypto_stealth") btparams.append(str(int(crypto_mode==3))) btparams.append("--crypto_only") btparams.append(str(int(crypto_mode>=2))) btparams.append("--crypto_allowed") btparams.append(str(int(crypto_mode>=1))) paramlist = [ "ip", "bind", "alloc_rate", "alloc_type", "double_check", "triple_check", "lock_while_reading", "lock_files", "min_peers", "max_files_open", "max_connections", "upnp_nat_access", "natpmp", "auto_flush", "pex_allowed", "expire_cache_data"] for param in paramlist: value = self.config.Read(param) if value != "": btparams.append("--" + param) btparams.append(value) config, args = parseargs(btparams, BTDefaults) config["dht"] = self.config.Read('dht', "boolean") config["tracker"] = self.config.Read('tracker', "boolean") return config def checkWinPath(self, parent, pathtocheck): if pathtocheck and pathtocheck[-1] == '\\' and pathtocheck != '\\\\': pathitems = pathtocheck[:-1].split('\\') else: pathitems = pathtocheck.split('\\') nexttotest = 1 if isPathRelative(pathtocheck): # Relative path # Empty relative path is allowed if pathtocheck == '': return True fixedname = fixInvalidName(pathitems[0]) if fixedname != pathitems[0]: dlg = wx.MessageDialog(parent, pathitems[0] + '\n' + \ _('This name cannot be used as a Windows file or folder name.') + '\n'+ \ _('Suggested corrected name :') + '\n\n' + \ fixedname, _('Error'), wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() return False else: # Absolute path # An absolute path must have at least one '\' if not '\\' in pathtocheck: dlg = wx.MessageDialog(parent, pathitems[0] + '\n' + _('Invalid syntax in the path. Try adding a \\'), _('Error'), wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() return False if pathtocheck[:2] != '\\\\': # Not a network path fixedname = fixWindowsName(pathitems[0], unit = True) if fixedname: dlg = wx.MessageDialog(parent, pathitems[0] + '\n' + \ _('Invalid syntax in the path. Try adding a \\') + \ fixedname, _('Error'), wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() return False else: # Network path nexttotest = 2 for name in pathitems[nexttotest:]: fixedname = fixInvalidName(name) if fixedname != name: dlg = wx.MessageDialog(parent, name + '\n' + _('This name cannot be used as a Windows file or folder name.') + fixedname, _('Error'), wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() return False return True def shutdownComputer(self): if (sys.platform == 'win32'): winver = sys.getwindowsversion()[3] if winver == VER_PLATFORM_WIN32_NT: shutdownthread = Thread(target = self.shutdown2k) shutdownthread.setDaemon(True) shutdownthread.start() elif winver == VER_PLATFORM_WIN32_WINDOWS: os.system('rundll32 shell32.dll,SHExitWindowsEx 9') else: func = utility.config.Read("shutdowncommand") os.execvp(func, []) def shutdown2k(self): AdjustTokenPrivileges(OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS), False, [(LookupPrivilegeValue(None, SE_SHUTDOWN_NAME), SE_PRIVILEGE_ENABLED)]) ExitWindowsEx(9) def setShutdownCommand(self, event = None): dialog = wx.TextEntryDialog(None, _('Enter the shutdown command for your OS'), _('Set Shutdown Command'), utility.config.Read("shutdowncommand")) result = dialog.ShowModal() shutdowncommand = dialog.GetValue() dialog.Destroy() if result == wx.ID_OK: utility.config.Write("shutdowncommand", shutdowncommand) def install_open_new(self): browsers = ('xdg-open', 'gnome-open', 'nautilus', 'konqueror') userbrowser = self.config.Read('filemanager') # Detect Available Browsers for browser in browsers: if os.system("which " + browser + " >/dev/null 2>&1") == 0: self.browsers.append(browser) # Add Custom Browser if userbrowser: if userbrowser in self.browsers: self.browsers.remove(userbrowser) self.browsers.insert(0, userbrowser) # Set Browser To Use if self.browsers: self.browser = self.browsers[0] else: self.browser = "" def open_new(self, dest): # Fix Path if sys.platform == 'darwin': dest = 'file://%s' % dest elif sys.platform[:5] == 'linux' and not dest.startswith('/'): dest = self.path + '/' + dest # Known browser if self.browser: os.spawnlp(os.P_NOWAIT, self.browser, self.browser, dest) # Let python handle it else: open_new(dest)