📄 downloadmanagerimpl.java
字号:
/*
* File : DownloadManagerImpl.java
* Created : 19-Oct-2003
* By : parg
*
* Azureus - a Java Bittorrent client
*
* 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 2 of the License.
*
* 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 ( see the LICENSE file ).
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.gudy.azureus2.core3.download.impl;
/*
* Created on 30 juin 2003
*
*/
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.*;
import java.net.*;
import org.gudy.azureus2.core3.config.*;
import org.gudy.azureus2.core3.disk.*;
import org.gudy.azureus2.core3.global.GlobalManager;
import org.gudy.azureus2.core3.internat.*;
import org.gudy.azureus2.core3.logging.LogRelation;
import org.gudy.azureus2.core3.peer.*;
import org.gudy.azureus2.core3.tracker.client.*;
import org.gudy.azureus2.core3.torrent.*;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.core3.download.*;
import org.gudy.azureus2.plugins.download.DownloadAnnounceResult;
import org.gudy.azureus2.plugins.download.DownloadScrapeResult;
import org.gudy.azureus2.plugins.network.ConnectionManager;
/**
* @author Olivier
*
*/
public class
DownloadManagerImpl
extends LogRelation
implements DownloadManager
{
// DownloadManager listeners
private static final int LDT_STATECHANGED = 1;
private static final int LDT_DOWNLOADCOMPLETE = 2;
private static final int LDT_COMPLETIONCHANGED = 3;
private static final int LDT_POSITIONCHANGED = 4;
private AEMonitor listeners_mon = new AEMonitor( "DM:DownloadManager:L" );
private static ListenerManager listeners_aggregator = ListenerManager.createAsyncManager(
"DM:ListenAggregatorDispatcher",
new ListenerManagerDispatcher()
{
public void
dispatch(
Object _listener,
int type,
Object _value )
{
DownloadManagerListener listener = (DownloadManagerListener)_listener;
Object[] value = (Object[])_value;
DownloadManagerImpl dm = (DownloadManagerImpl)value[0];
if ( type == LDT_STATECHANGED ){
listener.stateChanged(dm, ((Integer)value[1]).intValue());
}else if ( type == LDT_DOWNLOADCOMPLETE ){
listener.downloadComplete(dm);
}else if ( type == LDT_COMPLETIONCHANGED ){
listener.completionChanged(dm, ((Boolean)value[1]).booleanValue());
}else if ( type == LDT_POSITIONCHANGED ){
listener.positionChanged( dm, ((Integer)value[1]).intValue(), ((Integer)value[2]).intValue());
}
}
});
private ListenerManager listeners = ListenerManager.createManager(
"DM:ListenDispatcher",
new ListenerManagerDispatcher()
{
public void
dispatch(
Object listener,
int type,
Object value )
{
listeners_aggregator.dispatch( listener, type, value );
}
});
// TrackerListeners
private static final int LDT_TL_ANNOUNCERESULT = 1;
private static final int LDT_TL_SCRAPERESULT = 2;
private ListenerManager tracker_listeners = ListenerManager.createManager(
"DM:TrackerListenDispatcher",
new ListenerManagerDispatcher()
{
public void
dispatch(
Object _listener,
int type,
Object value )
{
DownloadManagerTrackerListener listener = (DownloadManagerTrackerListener)_listener;
if ( type == LDT_TL_ANNOUNCERESULT ){
listener.announceResult((TRTrackerAnnouncerResponse)value);
}else if ( type == LDT_TL_SCRAPERESULT ){
listener.scrapeResult((TRTrackerScraperResponse)value);
}
}
});
// PeerListeners
private static final int LDT_PE_PEER_ADDED = 1;
private static final int LDT_PE_PEER_REMOVED = 2;
private static final int LDT_PE_PIECE_ADDED = 3;
private static final int LDT_PE_PIECE_REMOVED = 4;
private static final int LDT_PE_PM_ADDED = 5;
private static final int LDT_PE_PM_REMOVED = 6;
// one static async manager for them all
private static ListenerManager peer_listeners_aggregator = ListenerManager.createAsyncManager(
"DM:PeerListenAggregatorDispatcher",
new ListenerManagerDispatcher()
{
public void
dispatch(
Object _listener,
int type,
Object value )
{
DownloadManagerPeerListener listener = (DownloadManagerPeerListener)_listener;
if ( type == LDT_PE_PEER_ADDED ){
listener.peerAdded((PEPeer)value);
}else if ( type == LDT_PE_PEER_REMOVED ){
listener.peerRemoved((PEPeer)value);
}else if ( type == LDT_PE_PIECE_ADDED ){
listener.pieceAdded((PEPiece)value);
}else if ( type == LDT_PE_PIECE_REMOVED ){
listener.pieceRemoved((PEPiece)value);
}else if ( type == LDT_PE_PM_ADDED ){
listener.peerManagerAdded((PEPeerManager)value);
}else if ( type == LDT_PE_PM_REMOVED ){
listener.peerManagerRemoved((PEPeerManager)value);
}
}
});
private ListenerManager peer_listeners = ListenerManager.createManager(
"DM:PeerListenDispatcher",
new ListenerManagerDispatcher()
{
public void
dispatch(
Object listener,
int type,
Object value )
{
peer_listeners_aggregator.dispatch( listener, type, value );
}
});
private AEMonitor peer_listeners_mon = new AEMonitor( "DM:DownloadManager:PL" );
private List current_peers = new ArrayList();
private List current_pieces = new ArrayList();
private DownloadManagerController controller;
private DownloadManagerStatsImpl stats;
protected AEMonitor this_mon = new AEMonitor( "DM:DownloadManager" );
private boolean persistent;
/**
* Only seed this torrent. Never download or allocate<P>
* Current Implementation:
* - implies that the user completed the download at one point
* - Checks if there's Data Missing when torrent is done (or torrent load)
*
* Perhaps a better name would be "bCompleted"
*/
private boolean onlySeeding;
/**
* forceStarted torrents can't/shouldn't be automatically stopped
*/
private int last_informed_state = STATE_START_OF_DAY;
private boolean latest_informed_force_start;
private GlobalManager globalManager;
private String torrentFileName;
private String display_name = "";
private String internal_name = "";
// for simple torrents this refers to the torrent file itself. For non-simple it refers to the
// folder containing the torrent's files
private File torrent_save_location;
// Position in Queue
private int position = -1;
private Object[] read_torrent_state;
private DownloadManagerState download_manager_state;
private TOTorrent torrent;
private String torrent_comment;
private String torrent_created_by;
private TRTrackerAnnouncer tracker_client;
private TRTrackerAnnouncerListener tracker_client_listener =
new TRTrackerAnnouncerListener()
{
public void
receivedTrackerResponse(
TRTrackerAnnouncerResponse response)
{
PEPeerManager pm = controller.getPeerManager();
if ( pm != null ) {
pm.processTrackerResponse( response );
}
tracker_listeners.dispatch( LDT_TL_ANNOUNCERESULT, response );
}
public void
urlChanged(
String url,
boolean explicit)
{
if ( explicit ){
checkTracker( true );
}
}
public void
urlRefresh()
{
checkTracker( true );
}
};
// a second listener used to catch and propagate the "stopped" event
private TRTrackerAnnouncerListener stopping_tracker_client_listener =
new TRTrackerAnnouncerListener()
{
public void
receivedTrackerResponse(
TRTrackerAnnouncerResponse response)
{
tracker_listeners.dispatch( LDT_TL_ANNOUNCERESULT, response );
}
public void
urlChanged(
String url,
boolean explicit)
{
}
public void
urlRefresh()
{
}
};
private long scrape_random_seed = SystemTime.getCurrentTime();
private HashMap data;
private boolean data_already_allocated = false;
private long creation_time = SystemTime.getCurrentTime();
private int iSeedingRank;
private boolean az_messaging_enabled = true;
private boolean dl_identity_obtained;
private byte[] dl_identity;
private int dl_identity_hashcode;
private int max_connections;
private int max_uploads_when_seeding;
private boolean max_uploads_when_seeding_enabled;
// Only call this with STATE_QUEUED, STATE_WAITING, or STATE_STOPPED unless you know what you are doing
public
DownloadManagerImpl(
GlobalManager _gm,
byte[] _torrent_hash,
String _torrentFileName,
String _torrent_save_dir,
String _torrent_save_file,
int _initialState,
boolean _persistent,
boolean _recovered,
boolean _open_for_seeding,
boolean _has_ever_been_started )
{
if ( _initialState != STATE_WAITING &&
_initialState != STATE_STOPPED &&
_initialState != STATE_QUEUED ){
Debug.out( "DownloadManagerImpl: Illegal start state, " + _initialState );
}
persistent = _persistent;
globalManager = _gm;
stats = new DownloadManagerStatsImpl( this );
controller = new DownloadManagerController( this );
stats.setMaxUploads( COConfigurationManager.getIntParameter("Max Uploads") );
torrentFileName = _torrentFileName;
while( _torrent_save_dir.endsWith( File.separator )){
_torrent_save_dir = _torrent_save_dir.substring(0, _torrent_save_dir.length()-1 );
}
// readTorrent adjusts the save dir and file to be sensible values
readTorrent( _torrent_save_dir, _torrent_save_file, _torrent_hash,
persistent && !_recovered, _open_for_seeding, _has_ever_been_started,
_initialState );
}
private void
readTorrent(
String torrent_save_dir,
String torrent_save_file,
byte[] torrent_hash, // can be null for initial torrents
boolean new_torrent, // probably equivalend to (torrent_hash == null)????
boolean open_for_seeding,
boolean has_ever_been_started,
int initial_state )
{
try{
display_name = torrentFileName; // default if things go wrong decoding it
internal_name = "";
torrent_comment = "";
torrent_created_by = "";
try{
// this is the first thing we do and most likely to go wrong - hence its
// existence is used below to indicate success or not
download_manager_state =
DownloadManagerStateImpl.getDownloadState(
this, torrentFileName, torrent_hash );
readParameters();
// establish any file links
download_manager_state.addListener(
new DownloadManagerStateListener()
{
public void
stateChanged(
DownloadManagerState state,
DownloadManagerStateEvent event )
{
if ( event.getType() == DownloadManagerStateEvent.ET_ATTRIBUTE_WRITTEN ){
String attribute_name = (String)event.getData();
if ( attribute_name.equals( DownloadManagerState.AT_FILE_LINKS )){
setFileLinks();
}else if ( attribute_name.equals( DownloadManagerState.AT_PARAMETERS )){
readParameters();
}
}
}
});
torrent = download_manager_state.getTorrent();
setFileLinks();
// We can't have the identity of this download changing as this will screw up
// anyone who tries to maintain a unique set of downloads (e.g. the GlobalManager)
//
if ( !dl_identity_obtained ){
// flag set true below
dl_identity = torrent_hash==null?torrent.getHash():torrent_hash;
this.dl_identity_hashcode = new String( dl_identity ).hashCode();
}
if ( !Arrays.equals( dl_identity, torrent.getHash())){
torrent = null; // prevent this download from being used
throw( new Exception( "Download identity changed - please remove and re-add the download" ));
}
read_torrent_state = null; // no longer needed if we saved it
LocaleUtilDecoder locale_decoder = LocaleUtil.getSingleton().getTorrentEncoding( torrent );
// if its a simple torrent and an explicit save file wasn't supplied, use
// the torrent name itself
display_name = locale_decoder.decodeString( torrent.getName());
display_name = FileUtil.convertOSSpecificChars( display_name );
internal_name = ByteFormatter.nicePrint(torrent.getHash(),true);
// now we know if its a simple torrent or not we can make some choices about
// the save dir and file. On initial entry the save_dir will have the user-selected
// save location and the save_file will be null
File save_dir_file = new File( torrent_save_dir );
// System.out.println( "before: " + torrent_save_dir + "/" + torrent_save_file );
// if save file is non-null then things have already been sorted out
if ( torrent_save_file == null ){
// make sure we're working off a canonical save dir if possible
try{
if ( save_dir_file.exists()){
save_dir_file = save_dir_file.getCanonicalFile();
}
}catch( Throwable e ){
Debug.printStackTrace(e);
}
if ( torrent.isSimpleTorrent()){
// if target save location is a directory then we use that as the save
// dir and use the torrent display name as the target. Otherwise we
// use the file name
if ( save_dir_file.exists()){
if ( save_dir_file.isDirectory()){
torrent_save_file = display_name;
}else{
torrent_save_dir = save_dir_file.getParent().toString();
torrent_save_file = save_dir_file.getName();
}
}else{
// doesn't exist, assume it refers directly to the file
if ( save_dir_file.getParent() == null ){
throw( new Exception( "Data location '" + torrent_save_dir + "' is invalid" ));
}
torrent_save_dir = save_dir_file.getParent().toString();
torrent_save_file = save_dir_file.getName();
}
}else{
// torrent is a folder. It is possible that the natural location
// for the folder is X/Y and that in fact 'Y' already exists and
// has been selected. If ths is the case the select X as the dir and Y
// as the file name
if ( save_dir_file.exists()){
if ( !save_dir_file.isDirectory()){
throw( new Exception( "'" + torrent_save_dir + "' is not a directory" ));
}
if ( save_dir_file.getName().equals( display_name )){
torrent_save_dir = save_dir_file.getParent().toString();
}
}
torrent_save_file = display_name;
}
}
torrent_save_location = new File( torrent_save_dir, torrent_save_file );
// final validity test must be based of potentially linked target location as file
// may have been re-targetted
File linked_target = getSaveLocation();
if ( !linked_target.exists()){
// if this isn't a new torrent then we treat the absence of the enclosing folder
// as a fatal error. This is in particular to solve a problem with the use of
// externally mounted torrent data on OSX, whereby a re-start with the drive unmounted
// results in the creation of a local diretory in /Volumes that subsequently stuffs
// up recovery when the volume is mounted
// changed this to only report the error on non-windows platforms
if ( !(new_torrent || Constants.isWindows )){
// another exception here - if the torrent has never been started then we can
// fairly safely continue as its in a stopped state
if ( has_ever_been_started ){
throw( new Exception( MessageText.getString("DownloadManager.error.datamissing") + " " + linked_target.toString()));
}
}
}
// if this is a newly introduced torrent trash the tracker cache. We do this to
// prevent, say, someone publishing a torrent with a load of invalid cache entries
// in it and a bad tracker URL. This could be used as a DOS attack
if ( new_torrent ){
download_manager_state.setTrackerResponseCache( new HashMap());
// also remove resume data incase someone's published a torrent with resume
// data in it
if ( open_for_seeding ){
DiskManagerFactory.setTorrentResumeDataNearlyComplete(download_manager_state);
}else{
download_manager_state.clearResumeData();
}
}
//trackerUrl = torrent.getAnnounceURL().toString();
torrent_comment = locale_decoder.decodeString(torrent.getComment());
if ( torrent_comment == null ){
torrent_comment = "";
}
torrent_created_by = locale_decoder.decodeString(torrent.getCreatedBy());
if ( torrent_created_by == null ){
torrent_created_by = "";
}
// only restore the tracker response cache for non-seeds
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -