# -*- coding: utf-8 -*- # # deluge/ui/common.py # # Copyright (C) Damien Churchill 2008-2009 # Copyright (C) Andrew Resch 2009 # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, write to: # The Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor # Boston, MA 02110-1301, USA. # # In addition, as a special exception, the copyright holders give # permission to link the code of portions of this program with the OpenSSL # library. # You must obey the GNU General Public License in all respects for all of # the code used other than OpenSSL. If you modify file(s) with this # exception, you may extend this exception to your version of the file(s), # but you are not obligated to do so. If you do not wish to do so, delete # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. # # """ The ui common module contains methods and classes that are deemed useful for all the interfaces. """ import os import sys import urlparse import chardet try: from hashlib import sha1 as sha except ImportError: from sha import sha from deluge import bencode from deluge.log import LOG as log import deluge.configmanager def decode_string(s, encoding="utf8"): """ Decodes a string and re-encodes it in utf8. If it cannot decode using `:param:encoding` then it will try to detect the string encoding and decode it. :param s: string to decode :type s: string :keyword encoding: the encoding to use in the decoding :type encoding: string """ try: s = s.decode(encoding).encode("utf8") except UnicodeDecodeError: s = s.decode(chardet.detect(s)["encoding"]).encode("utf8") return s class TorrentInfo(object): """ Collects information about a torrent file. :param filename: The path to the torrent :type filename: string """ def __init__(self, filename): # Get the torrent data from the torrent file try: log.debug("Attempting to open %s.", filename) self.__m_filedata = open(filename, "rb").read() self.__m_metadata = bencode.bdecode(self.__m_filedata) except Exception, e: log.warning("Unable to open %s: %s", filename, e) raise e self.__m_info_hash = sha(bencode.bencode(self.__m_metadata["info"])).hexdigest() # Get encoding from torrent file if available self.encoding = "UTF-8" if "encoding" in self.__m_metadata: self.encoding = self.__m_metadata["encoding"] elif "codepage" in self.__m_metadata: self.encoding = str(self.__m_metadata["codepage"]) # Check if 'name.utf-8' is in the torrent and if not try to decode the string # using the encoding found. if "name.utf-8" in self.__m_metadata["info"]: self.__m_name = decode_string(self.__m_metadata["info"]["name.utf-8"]) else: self.__m_name = decode_string(self.__m_metadata["info"]["name"], self.encoding) # Get list of files from torrent info paths = {} if self.__m_metadata["info"].has_key("files"): prefix = "" if len(self.__m_metadata["info"]["files"]) > 1: prefix = self.__m_name for index, f in enumerate(self.__m_metadata["info"]["files"]): if "path.utf-8" in f: path = os.path.join(prefix, *f["path.utf-8"]) else: path = decode_string(os.path.join(prefix, decode_string(os.path.join(*f["path"]), self.encoding)), self.encoding) f["index"] = index paths[path] = f def walk(path, item): if type(item) is dict: return item return [paths[path]['index'], paths[path]['length'], True] file_tree = FileTree(paths) file_tree.walk(walk) self.__m_files_tree = file_tree.get_tree() else: self.__m_files_tree = { self.__m_name: (0, self.__m_metadata["info"]["length"], True) } self.__m_files = [] if self.__m_metadata["info"].has_key("files"): prefix = "" if len(self.__m_metadata["info"]["files"]) > 1: prefix = self.__m_name for f in self.__m_metadata["info"]["files"]: if "path.utf-8" in f: path = os.path.join(prefix, *f["path.utf-8"]) else: path = decode_string(os.path.join(prefix, decode_string(os.path.join(*f["path"]), self.encoding)), self.encoding) self.__m_files.append({ 'path': path, 'size': f["length"], 'download': True }) else: self.__m_files.append({ "path": self.__m_name, "size": self.__m_metadata["info"]["length"], "download": True }) def as_dict(self, *keys): """ Return the torrent info as a dictionary, only including the passed in keys. :param keys: a number of key strings :type keys: string """ return dict([(key, getattr(self, key)) for key in keys]) @property def name(self): """ The name of the torrent. :rtype: string """ return self.__m_name @property def info_hash(self): """ The torrents info_hash :rtype: string """ return self.__m_info_hash @property def files(self): """ A list of the files that the torrent contains. :rtype: list """ return self.__m_files @property def files_tree(self): """ A dictionary based tree of the files. :: { "some_directory": { "some_file": (index, size, download) } } :rtype: dictionary """ return self.__m_files_tree @property def metadata(self): """ The torrents metadata. :rtype: dictionary """ return self.__m_metadata @property def filedata(self): """ The torrents file data. This will be the bencoded dictionary read from the torrent file. :rtype: string """ return self.__m_filedata class FileTree2(object): """ Converts a list of paths in to a file tree. :param paths: The paths to be converted :type paths: list """ def __init__(self, paths): self.tree = {"contents": {}, "type": "dir"} def get_parent(path): parent = self.tree while "/" in path: directory, path = path.split("/", 1) child = parent["contents"].get(directory) if child is None: parent["contents"][directory] = { "type": "dir", "contents": {} } parent = parent["contents"][directory] return parent, path for path in paths: if path[-1] == "/": path = path[:-1] parent, path = get_parent(path) parent["contents"][path] = { "type": "dir", "contents": {} } else: parent, path = get_parent(path) parent["contents"][path] = { "type": "file" } def get_tree(self): """ Return the tree. :returns: the file tree. :rtype: dictionary """ return self.tree def walk(self, callback): """ Walk through the file tree calling the callback function on each item contained. :param callback: The function to be used as a callback, it should have the signature func(item, path) where item is a `tuple` for a file and `dict` for a directory. :type callback: function """ def walk(directory, parent_path): for path in directory["contents"].keys(): full_path = os.path.join(parent_path, path) if directory["contents"][path]["type"] == "dir": directory["contents"][path] = callback(full_path, directory["contents"][path]) or \ directory["contents"][path] walk(directory["contents"][path], full_path) else: directory["contents"][path] = callback(full_path, directory["contents"][path]) or \ directory["contents"][path] walk(self.tree, "") def __str__(self): lines = [] def write(path, item): depth = path.count("/") path = os.path.basename(path) path = path + "/" if item["type"] == "dir" else path lines.append(" " * depth + path) self.walk(write) return "\n".join(lines) class FileTree(object): """ Convert a list of paths in a file tree. :param paths: The paths to be converted. :type paths: list """ def __init__(self, paths): self.tree = {} def get_parent(path): parent = self.tree while "/" in path: directory, path = path.split("/", 1) child = parent.get(directory) if child is None: parent[directory] = {} parent = parent[directory] return parent, path for path in paths: if path[-1] == "/": path = path[:-1] parent, path = get_parent(path) parent[path] = {} else: parent, path = get_parent(path) parent[path] = [] def get_tree(self): """ Return the tree, after first converting all file lists to a tuple. :returns: the file tree. :rtype: dictionary """ def to_tuple(path, item): if type(item) is dict: return item return tuple(item) self.walk(to_tuple) return self.tree def walk(self, callback): """ Walk through the file tree calling the callback function on each item contained. :param callback: The function to be used as a callback, it should have the signature func(item, path) where item is a `tuple` for a file and `dict` for a directory. :type callback: function """ def walk(directory, parent_path): for path in directory.keys(): full_path = os.path.join(parent_path, path) if type(directory[path]) is dict: directory[path] = callback(full_path, directory[path]) or \ directory[path] walk(directory[path], full_path) else: directory[path] = callback(full_path, directory[path]) or \ directory[path] walk(self.tree, "") def __str__(self): lines = [] def write(path, item): depth = path.count("/") path = os.path.basename(path) path = type(item) is dict and path + "/" or path lines.append(" " * depth + path) self.walk(write) return "\n".join(lines) def get_localhost_auth(): """ Grabs the localclient auth line from the 'auth' file and creates a localhost uri :returns: with the username and password to login as :rtype: tuple """ auth_file = deluge.configmanager.get_config_dir("auth") if os.path.exists(auth_file): for line in open(auth_file): if line.startswith("#"): # This is a comment line continue line = line.strip() try: lsplit = line.split(":") except Exception, e: log.error("Your auth file is malformed: %s", e) continue if len(lsplit) == 2: username, password = lsplit elif len(lsplit) == 3: username, password, level = lsplit else: log.error("Your auth file is malformed: Incorrect number of fields!") continue if username == "localclient": return (username, password) return ("", "")