deluge/deluge/core/torrent.py
2008-02-12 21:48:58 +00:00

416 lines
14 KiB
Python

#
# torrent.py
#
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# deluge 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 deluge. 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.
"""Internal Torrent class"""
import os
import deluge.libtorrent as lt
import deluge.common
from deluge.configmanager import ConfigManager
from deluge.log import LOG as log
TORRENT_STATE = deluge.common.TORRENT_STATE
class Torrent:
"""Torrent holds information about torrents added to the libtorrent session.
"""
def __init__(self, filename, handle, compact, save_path, total_uploaded=0,
trackers=None):
# Get the core config
self.config = ConfigManager("core.conf")
# Set the filename
self.filename = filename
# Set the libtorrent handle
self.handle = handle
# Set the torrent_id for this torrent
self.torrent_id = str(handle.info_hash())
# This is for saving the total uploaded between sessions
self.total_uploaded = total_uploaded
# Set the allocation mode
self.compact = compact
# Where the torrent is being saved to
self.save_path = save_path
# The state of the torrent
self.state = None
# Holds status info so that we don't need to keep getting it from lt
self.status = self.handle.status()
self.torrent_info = self.handle.torrent_info()
# Set the initial state
if self.status.state == deluge.common.LT_TORRENT_STATE["Allocating"]:
self.set_state("Allocating")
elif self.status.state == deluge.common.LT_TORRENT_STATE["Checking"]:
self.set_state("Checking")
else:
self.set_state("Paused")
# Various torrent options
self.max_connections = -1
self.max_upload_slots = -1
self.max_upload_speed = -1
self.max_download_speed = -1
self.private = False
self.prioritize_first_last = False
# The tracker status
self.tracker_status = ""
# Tracker list
if trackers == None:
self.trackers = []
# Create a list of trackers
for value in self.handle.trackers():
tracker = {}
tracker["url"] = value.url
tracker["tier"] = value.tier
self.trackers.append(tracker)
else:
self.trackers = trackers
self.set_trackers(self.trackers)
# Files dictionary
self.files = self.get_files()
# Set the default file priorities to normal
self.file_priorities = [1]* len(self.files)
def set_tracker_status(self, status):
"""Sets the tracker status"""
self.tracker_status = status
def set_max_connections(self, max_connections):
self.max_connections = max_connections
self.handle.set_max_connections(max_connections)
def set_max_upload_slots(self, max_slots):
self.max_upload_slots = max_slots
self.handle.set_max_uploads(max_slots)
def set_max_upload_speed(self, m_up_speed):
self.max_upload_speed = m_up_speed
self.handle.set_upload_limit(int(m_up_speed * 1024))
def set_max_download_speed(self, m_down_speed):
self.max_download_speed = m_down_speed
self.handle.set_download_limit(int(m_down_speed * 1024))
def set_private_flag(self, private):
self.private = private
self.handle.torrent_info().set_priv(private)
def set_prioritize_first_last(self, prioritize):
self.prioritize_first_last = prioritize
def set_save_path(self, save_path):
self.save_path = save_path
def set_file_priorities(self, file_priorities):
self.file_priorities = file_priorities
self.handle.prioritize_files(file_priorities)
def set_state(self, state):
"""Accepts state strings, ie, "Paused", "Seeding", etc."""
# Only set 'Downloading' or 'Seeding' state if not paused
if state == "Downloading" or state == "Seeding":
if self.handle.is_paused():
state = "Paused"
try:
self.state = TORRENT_STATE[state]
except:
pass
def get_eta(self):
"""Returns the ETA in seconds for this torrent"""
if self.status == None:
status = self.handle.status()
else:
status = self.status
left = status.total_wanted - status.total_done
if left == 0 or status.download_payload_rate == 0:
return 0
try:
eta = left / status.download_payload_rate
except ZeroDivisionError:
eta = 0
return eta
def get_ratio(self):
"""Returns the ratio for this torrent"""
if self.status == None:
status = self.handle.status()
else:
status = self.status
up = self.total_uploaded + status.total_payload_upload
down = status.total_done
# Convert 'up' and 'down' to floats for proper calculation
up = float(up)
down = float(down)
try:
ratio = up / down
except ZeroDivisionError:
return 0.0
return ratio
def get_files(self):
"""Returns a list of files this torrent contains"""
if self.torrent_info == None:
torrent_info = self.handle.torrent_info()
else:
torrent_info = self.torrent_info
ret = []
files = torrent_info.files()
for file in files:
ret.append({
'path': file.path,
'size': file.size,
'offset': file.offset
})
return ret
def get_status(self, keys):
"""Returns the status of the torrent based on the keys provided"""
# Create the full dictionary
self.status = self.handle.status()
self.torrent_info = self.handle.torrent_info()
# Adjust progress to be 0-100 value
progress = self.status.progress * 100
# Adjust status.distributed_copies to return a non-negative value
distributed_copies = self.status.distributed_copies
if distributed_copies < 0:
distributed_copies = 0.0
full_status = {
"distributed_copies": distributed_copies,
"total_done": self.status.total_done,
"total_uploaded": self.total_uploaded + self.status.total_payload_upload,
"state": self.state,
"paused": self.status.paused,
"progress": progress,
"next_announce": self.status.next_announce.seconds,
"total_payload_download": self.status.total_payload_download,
"total_payload_upload": self.status.total_payload_upload,
"download_payload_rate": self.status.download_payload_rate,
"upload_payload_rate": self.status.upload_payload_rate,
"num_peers": self.status.num_peers - self.status.num_seeds,
"num_seeds": self.status.num_seeds,
"total_peers": self.status.num_incomplete,
"total_seeds": self.status.num_complete,
"total_wanted": self.status.total_wanted,
"tracker": self.status.current_tracker,
"trackers": self.trackers,
"tracker_status": self.tracker_status,
"save_path": self.save_path,
"files": self.files,
"file_priorities": self.file_priorities
}
fns = {
"name" : self.torrent_info.name,
"total_size" : self.torrent_info.total_size,
"num_files" : self.torrent_info.num_files,
"num_pieces" : self.torrent_info.num_pieces,
"piece_length" : self.torrent_info.piece_length,
"eta" : self.get_eta,
"ratio" : self.get_ratio,
"file_progress" : self.handle.file_progress
}
self.status = None
self.torrent_info = None
# Create the desired status dictionary and return it
status_dict = {}
if len(keys) == 0:
status_dict = full_status
for key in fns:
status_dict[key] = fns[key]()
else:
for key in keys:
if key in full_status:
status_dict[key] = full_status[key]
elif key in fns:
status_dict[key] = fns[key]()
return status_dict
def pause(self):
"""Pause this torrent"""
try:
self.handle.pause()
except Exception, e:
log.debug("Unable to pause torrent: %s", e)
return False
return True
def resume(self):
"""Resumes this torrent"""
if self.state != TORRENT_STATE["Paused"]:
return False
try:
self.handle.resume()
except:
return False
# Set the state
if self.handle.is_seed():
self.set_state("Seeding")
else:
self.set_state("Downloading")
status = self.get_status(["total_done", "total_wanted"])
# Only delete the .fastresume file if we're still downloading stuff
if status["total_done"] < status["total_wanted"]:
self.delete_fastresume()
return True
def move_storage(self, dest):
"""Move a torrent's storage location"""
try:
self.handle.move_storage(dest)
except:
return False
return True
def write_fastresume(self):
"""Writes the .fastresume file for the torrent"""
resume_data = lt.bencode(self.handle.write_resume_data())
path = "%s/%s.fastresume" % (
self.config["torrentfiles_location"],
self.filename)
log.debug("Saving fastresume file: %s", path)
try:
fastresume = open(path, "wb")
fastresume.write(resume_data)
fastresume.close()
except IOError:
log.warning("Error trying to save fastresume file")
def delete_fastresume(self):
"""Deletes the .fastresume file"""
path = "%s/%s.fastresume" % (
self.config["torrentfiles_location"],
self.filename)
log.debug("Deleting fastresume file: %s", path)
try:
os.remove(path)
except Exception, e:
log.warning("Unable to delete the fastresume file: %s", e)
def force_reannounce(self):
"""Force a tracker reannounce"""
try:
self.handle.force_reannounce()
except Exception, e:
log.debug("Unable to force reannounce: %s", e)
return False
return True
def scrape_tracker(self):
"""Scrape the tracker"""
try:
self.handle.scrape_tracker()
except Exception, e:
log.debug("Unable to scrape tracker: %s", e)
return False
return True
def set_trackers(self, trackers):
"""Sets trackers"""
if trackers == None:
trackers = []
log.debug("Setting trackers for %s: %s", self.torrent_id, trackers)
tracker_list = []
for tracker in trackers:
new_entry = lt.announce_entry(tracker["url"])
new_entry.tier = tracker["tier"]
tracker_list.append(new_entry)
self.handle.replace_trackers(tracker_list)
# Print out the trackers
for t in self.handle.trackers():
log.debug("tier: %s tracker: %s", t.tier, t.url)
# Set the tracker list in the torrent object
self.trackers = trackers
if len(trackers) > 0:
# Force a reannounce if there is at least 1 tracker
self.force_reannounce()
def save_torrent_file(self, filedump=None):
"""Saves a torrent file"""
log.debug("Attempting to save torrent file: %s", self.filename)
# Test if the torrentfiles_location is accessible
if os.access(
os.path.join(self.config["torrentfiles_location"]), os.F_OK) \
is False:
# The directory probably doesn't exist, so lets create it
try:
os.makedirs(os.path.join(self.config["torrentfiles_location"]))
except IOError, e:
log.warning("Unable to create torrent files directory: %s", e)
# Write the .torrent file to the torrent directory
try:
save_file = open(os.path.join(self.config["torrentfiles_location"],
self.filename),
"wb")
if filedump == None:
filedump = self.handle.torrent_info().create_torrent()
save_file.write(lt.bencode(filedump))
save_file.close()
except IOError, e:
log.warning("Unable to save torrent file: %s", e)