deluge/deluge/ui/common.py
Damien Churchill 9362ec0103 ui: add a new file tree geared towards ext
Add a new file tree that is more suited to created a tree that
an ext proxy will be able to load and convert into a data store.
This file tree also has an improved file tree walk method that
uses generators instead of callbacks.
2011-10-05 01:38:37 +01:00

512 lines
16 KiB
Python

# -*- coding: utf-8 -*-
#
# deluge/ui/common.py
#
# Copyright (C) Damien Churchill 2008-2009 <damoxc@gmail.com>
# Copyright (C) Andrew Resch 2009 <andrewresch@gmail.com>
#
# 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 logging
import urlparse
import locale
try:
from hashlib import sha1 as sha
except ImportError:
from sha import sha
from deluge import bencode
from deluge.common import decode_string, path_join
import deluge.configmanager
log = logging.getLogger(__name__)
class TorrentInfo(object):
"""
Collects information about a torrent file.
:param filename: The path to the torrent
:type filename: string
"""
def __init__(self, filename, filetree=1):
# 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 = {}
dirs = {}
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
dirname = os.path.dirname(path)
while dirname:
dirinfo = dirs.setdefault(dirname, {})
dirinfo["length"] = dirinfo.get("length", 0) + f["length"]
dirname = os.path.dirname(dirname)
if filetree == 2:
def walk(path, item):
if item["type"] == "dir":
item.update(dirs[path])
else:
item.update(paths[path])
item["download"] = True
file_tree = FileTree2(paths.keys())
file_tree.walk(walk)
else:
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:
if filetree == 2:
self.__m_files_tree = {
"contents": {
self.__m_name: {
"type": "file",
"index": 0,
"length": self.__m_metadata["info"]["length"],
"download": True
}
}
}
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 ExtFileTree(object):
"""
Convert a list of paths into a compatible file tree format for Ext.
:param paths: The paths to be converted
:type paths: list
"""
def __init__(self, paths):
self.tree = {"children": [], "text": ""}
def get_parent(path):
parent = self.tree
while "/" in path:
directory, sep, path = path.partition("/")
new_parent = None
for child in parent["children"]:
if child["text"] == directory:
new_parent = child
if not new_parent:
new_parent = {"children": [], "text": directory}
parent["children"].append(new_parent)
parent = new_parent
return parent, path
for path in paths:
if path[-1] == "/":
path = path[:-1]
parent, path = get_parent(path)
parent["children"].append({
"text": path,
"children": []
})
else:
parent, path = get_parent(path)
parent["children"].append({"text": path})
def get_tree(self):
"""
Return the tree.
:returns: the file tree.
:rtype: dictionary
"""
return self.tree
def walk(self):
"""
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
"""
for path, child in self.__walk(self.tree, ""):
yield path, child
def __walk(self, directory, parent_path):
for item in directory["children"]:
path = path_join(parent_path, item["text"])
if "children" in item:
for path, child in self.__walk(item, path):
yield path, child
yield path, item
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 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 = 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 not os.path.exists(auth_file):
from deluge.common import create_localclient_account
create_localclient_account()
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)