/* * Created on 31-Jul-2004 * Created by Paul Gardner * Copyright (C) 2004, 2005, 2006 Aelitis, All Rights Reserved. * * 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * AELITIS, SAS au capital de 46,603.30 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package org.gudy.azureus2.core3.disk.impl.resume; import java.util.*; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.config.ParameterListener; import org.gudy.azureus2.core3.disk.DiskManagerCheckRequest; import org.gudy.azureus2.core3.disk.DiskManagerCheckRequestListener; import org.gudy.azureus2.core3.disk.DiskManagerFileInfo; import org.gudy.azureus2.core3.disk.DiskManagerPiece; import org.gudy.azureus2.core3.disk.DiskManagerReadRequest; import org.gudy.azureus2.core3.disk.DiskManagerReadRequestListener; import org.gudy.azureus2.core3.disk.DiskManagerWriteRequest; import org.gudy.azureus2.core3.disk.DiskManagerWriteRequestListener; import org.gudy.azureus2.core3.disk.impl.DiskManagerFileInfoImpl; import org.gudy.azureus2.core3.disk.impl.DiskManagerImpl; import org.gudy.azureus2.core3.disk.impl.DiskManagerRecheckInstance; import org.gudy.azureus2.core3.disk.impl.access.DMChecker; import org.gudy.azureus2.core3.disk.impl.piecemapper.DMPieceList; import org.gudy.azureus2.core3.disk.impl.piecemapper.DMPieceMapEntry; import org.gudy.azureus2.core3.download.DownloadManager; import org.gudy.azureus2.core3.download.DownloadManagerState; import org.gudy.azureus2.core3.logging.LogEvent; import org.gudy.azureus2.core3.logging.LogIDs; import org.gudy.azureus2.core3.logging.Logger; import org.gudy.azureus2.core3.torrent.TOTorrent; import org.gudy.azureus2.core3.util.AESemaphore; import org.gudy.azureus2.core3.util.ByteArrayHashMap; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.DirectByteBuffer; import com.aelitis.azureus.core.diskmanager.cache.CacheFileManagerException; /** * @author parg * */ public class RDResumeHandler { private static final LogIDs LOGID = LogIDs.DISK; private static final boolean TEST_RECHECK_FAILURE_HANDLING = false; static{ if ( TEST_RECHECK_FAILURE_HANDLING ){ Debug.out( "**** test recheck failure enabled ****" ); } } private static final byte PIECE_NOT_DONE = 0; private static final byte PIECE_DONE = 1; private static final byte PIECE_RECHECK_REQUIRED = 2; private static final byte PIECE_STARTED = 3; private static boolean use_fast_resume; private static boolean use_fast_resume_recheck_all; static{ COConfigurationManager.addAndFireParameterListeners( new String[]{ "Use Resume", "On Resume Recheck All" }, new ParameterListener() { public void parameterChanged( String str ) { use_fast_resume = COConfigurationManager.getBooleanParameter("Use Resume"); use_fast_resume_recheck_all = COConfigurationManager.getBooleanParameter("On Resume Recheck All"); } }); } private DiskManagerImpl disk_manager; private DMChecker checker; private volatile boolean started; private volatile boolean stopped; private volatile boolean stopped_for_close; private volatile boolean check_in_progress; private volatile boolean check_resume_was_valid; private volatile boolean check_is_full_check; private volatile boolean check_interrupted; private volatile int check_position; public RDResumeHandler( DiskManagerImpl _disk_manager, DMChecker _writer_and_checker ) { disk_manager = _disk_manager; checker = _writer_and_checker; } public void start() { if ( started ){ Debug.out( "RDResumeHandler: reuse not supported" ); } started = true; } public void stop( boolean closing ) { stopped_for_close = stopped_for_close | closing; // can get in here > once during close if ( check_in_progress ){ check_interrupted = true; } stopped = true; } public void checkAllPieces( boolean newfiles ) { //long start = System.currentTimeMillis(); DiskManagerRecheckInstance recheck_inst = disk_manager.getRecheckScheduler().register( disk_manager, false ); final AESemaphore run_sem = new AESemaphore( "RDResumeHandler::checkAllPieces:runsem", 2 ); final List<DiskManagerCheckRequest> failed_pieces = new ArrayList<DiskManagerCheckRequest>(); try{ boolean resume_data_complete = false; try{ check_in_progress = true; boolean resumeEnabled = use_fast_resume; //disable fast resume if a new file was created if ( newfiles ){ resumeEnabled = false; } final AESemaphore pending_checks_sem = new AESemaphore( "RD:PendingChecks" ); int pending_check_num = 0; DiskManagerPiece[] pieces = disk_manager.getPieces(); // calculate the current file sizes up front for performance reasons DiskManagerFileInfo[] files = disk_manager.getFiles(); Map file_sizes = new HashMap(); for (int i=0;i<files.length;i++){ try{ Long len = new Long(((DiskManagerFileInfoImpl)files[i]).getCacheFile().getLength()); file_sizes.put( files[i], len ); }catch( CacheFileManagerException e ){ Debug.printStackTrace(e); } } if ( resumeEnabled ){ boolean resumeValid = false; byte[] resume_pieces = null; Map partialPieces = null; Map resume_data = getResumeData(); if ( resume_data != null ){ try { resume_pieces = (byte[])resume_data.get("resume data"); if ( resume_pieces != null ){ if ( resume_pieces.length != pieces.length ){ Debug.out( "Resume data array length mismatch: " + resume_pieces.length + "/" + pieces.length ); resume_pieces = null; } } partialPieces = (Map)resume_data.get("blocks"); resumeValid = ((Long)resume_data.get("valid")).intValue() == 1; // if the torrent download is complete we don't need to invalidate the // resume data if ( isTorrentResumeDataComplete( disk_manager.getDownloadManager().getDownloadState(), resume_data )){ resume_data_complete = true; }else{ // set it so that if we crash the NOT_DONE pieces will be // rechecked resume_data.put("valid", new Long(0)); saveResumeData( resume_data ); } }catch(Exception ignore){ // ignore.printStackTrace(); } } if ( resume_pieces == null ){ check_is_full_check = true; resumeValid = false; resume_pieces = new byte[pieces.length]; Arrays.fill( resume_pieces, PIECE_RECHECK_REQUIRED ); } check_resume_was_valid = resumeValid; boolean recheck_all = use_fast_resume_recheck_all; if ( !recheck_all ){ // override if not much left undone long total_not_done = 0; int piece_size = disk_manager.getPieceLength(); for (int i = 0; i < pieces.length; i++){ if ( resume_pieces[i] != PIECE_DONE ){ total_not_done += piece_size; } } if ( total_not_done < 64*1024*1024 ){ recheck_all = true; } } if (Logger.isEnabled()){ int total_not_done = 0; int total_done = 0; int total_started = 0; int total_recheck = 0; for (int i = 0; i < pieces.length; i++){ byte piece_state = resume_pieces[i]; if ( piece_state == PIECE_NOT_DONE ){ total_not_done++; }else if ( piece_state == PIECE_DONE ){ total_done++; }else if ( piece_state == PIECE_STARTED ){ total_started++; }else{ total_recheck++; } } String str = "valid=" + resumeValid + ",not done=" + total_not_done + ",done=" + total_done + ",started=" + total_started + ",recheck=" + total_recheck + ",rc all=" + recheck_all + ",full=" + check_is_full_check; Logger.log(new LogEvent(disk_manager, LOGID, str )); } for (int i = 0; i < pieces.length; i++){ check_position = i; DiskManagerPiece dm_piece = pieces[i]; disk_manager.setPercentDone(((i + 1) * 1000) / disk_manager.getNbPieces() ); boolean pieceCannotExist = false; byte piece_state = resume_pieces[i]; // valid resume data means that the resume array correctly represents // the state of pieces on disk, be they done or not if ( piece_state == PIECE_DONE || !resumeValid || recheck_all ){ // at least check that file sizes are OK for this piece to be valid DMPieceList list = disk_manager.getPieceList(i); for (int j=0;j<list.size();j++){ DMPieceMapEntry entry = list.get(j); Long file_size = (Long)file_sizes.get(entry.getFile()); if ( file_size == null ){ piece_state = PIECE_NOT_DONE; pieceCannotExist = true; if (Logger.isEnabled()) Logger.log(new LogEvent(disk_manager, LOGID, LogEvent.LT_WARNING, "Piece #" + i + ": file is missing, " + "fails re-check.")); break; } long expected_size = entry.getOffset() + entry.getLength(); if ( file_size.longValue() < expected_size ){ piece_state = PIECE_NOT_DONE; pieceCannotExist = true; if (Logger.isEnabled()) Logger.log(new LogEvent(disk_manager, LOGID, LogEvent.LT_WARNING, "Piece #" + i + ": file is too small, fails re-check. File size = " + file_size + ", piece needs " + expected_size)); break; } } } if ( piece_state == PIECE_DONE ){ dm_piece.setDone( true ); }else if ( piece_state == PIECE_NOT_DONE && !recheck_all ){ // if the piece isn't done and we haven't been asked to recheck all pieces // on restart (only started pieces) then just set as not done }else{ // We only need to recheck pieces that are marked as not-ok // if the resume data is invalid or explicit recheck needed if(pieceCannotExist) { dm_piece.setDone( false ); } else if ( piece_state == PIECE_RECHECK_REQUIRED || !resumeValid ){ run_sem.reserve(); while( !stopped ){ if ( recheck_inst.getPermission()){ break; } } if ( stopped ){ break; }else{ try{ DiskManagerCheckRequest request = disk_manager.createCheckRequest( i, null ); request.setLowPriority( true ); checker.enqueueCheckRequest( request, new DiskManagerCheckRequestListener() { public void checkCompleted( DiskManagerCheckRequest request, boolean passed ) { if ( TEST_RECHECK_FAILURE_HANDLING && (int)(Math.random()*10) == 0 ){ disk_manager.getPiece(request.getPieceNumber()).setDone(false); passed = false; } if ( !passed ){ synchronized( failed_pieces ){ failed_pieces.add( request ); } } complete(); } public void checkCancelled( DiskManagerCheckRequest request ) { complete(); } public void checkFailed( DiskManagerCheckRequest request, Throwable cause ) { complete(); } protected void complete() { run_sem.release(); pending_checks_sem.release(); } }); pending_check_num++; }catch( Throwable e ){ Debug.printStackTrace(e); } } } } } while( pending_check_num > 0 ){ pending_checks_sem.reserve(); pending_check_num--; } if ( partialPieces != null ){ Iterator iter = partialPieces.entrySet().iterator(); while (iter.hasNext()) { Map.Entry key = (Map.Entry)iter.next(); int pieceNumber = Integer.parseInt((String)key.getKey()); DiskManagerPiece dm_piece = pieces[ pieceNumber ]; if ( !dm_piece.isDone()){ List blocks = (List)partialPieces.get(key.getKey()); Iterator iterBlock = blocks.iterator(); while (iterBlock.hasNext()) { dm_piece.setWritten(((Long)iterBlock.next()).intValue()); } } } } }else{ // resume not enabled, recheck everything for (int i = 0; i < pieces.length; i++){ check_position = i; disk_manager.setPercentDone(((i + 1) * 1000) / disk_manager.getNbPieces() ); boolean pieceCannotExist = false; // check if there is an underlying file for this piece, if not set it to not done DMPieceList list = disk_manager.getPieceList(i); for (int j=0;j<list.size();j++){ DMPieceMapEntry entry = list.get(j); Long file_size = (Long)file_sizes.get(entry.getFile()); if ( file_size == null ){ pieceCannotExist = true; break; } long expected_size = entry.getOffset() + entry.getLength(); if ( file_size.longValue() < expected_size ){ pieceCannotExist = true; break; } } if(pieceCannotExist) { disk_manager.getPiece(i).setDone(false); continue; } run_sem.reserve(); while( ! stopped ){ if ( recheck_inst.getPermission()){ break; } } if ( stopped ){ break; } try{ DiskManagerCheckRequest request = disk_manager.createCheckRequest( i, null ); request.setLowPriority( true ); checker.enqueueCheckRequest( request, new DiskManagerCheckRequestListener() { public void checkCompleted( DiskManagerCheckRequest request, boolean passed ) { if ( TEST_RECHECK_FAILURE_HANDLING && (int)(Math.random()*10) == 0 ){ disk_manager.getPiece(request.getPieceNumber()).setDone(false); passed = false; } if ( !passed ){ synchronized( failed_pieces ){ failed_pieces.add( request ); } } complete(); } public void checkCancelled( DiskManagerCheckRequest request ) { complete(); } public void checkFailed( DiskManagerCheckRequest request, Throwable cause ) { complete(); } protected void complete() { run_sem.release(); pending_checks_sem.release(); } }); pending_check_num++; }catch( Throwable e ){ Debug.printStackTrace(e); } } while( pending_check_num > 0 ){ pending_checks_sem.reserve(); pending_check_num--; } } if ( failed_pieces.size() > 0 && !TEST_RECHECK_FAILURE_HANDLING ){ byte[][] piece_hashes = disk_manager.getTorrent().getPieces(); ByteArrayHashMap<Integer> hash_map = new ByteArrayHashMap<Integer>(); for ( int i=0;i<piece_hashes.length;i++){ hash_map.put( piece_hashes[i], i ); } for ( DiskManagerCheckRequest request: failed_pieces ){ while( ! stopped ){ if ( recheck_inst.getPermission()){ break; } } if ( stopped ){ break; } byte[] hash = request.getHash(); if ( hash != null ){ final Integer target_index = hash_map.get( hash ); int current_index = request.getPieceNumber(); int piece_size = disk_manager.getPieceLength( current_index ); if ( target_index != null && target_index != current_index && disk_manager.getPieceLength( target_index ) == piece_size && !disk_manager.isDone( target_index )){ final AESemaphore sem = new AESemaphore( "PieceReorder" ); disk_manager.enqueueReadRequest( disk_manager.createReadRequest( current_index, 0, piece_size ), new DiskManagerReadRequestListener() { public void readCompleted( DiskManagerReadRequest request, DirectByteBuffer data ) { try{ disk_manager.enqueueWriteRequest( disk_manager.createWriteRequest( target_index, 0, data, null ), new DiskManagerWriteRequestListener() { public void writeCompleted( DiskManagerWriteRequest request ) { try{ DiskManagerCheckRequest check_request = disk_manager.createCheckRequest( target_index, null ); check_request.setLowPriority( true ); checker.enqueueCheckRequest( check_request, new DiskManagerCheckRequestListener() { public void checkCompleted( DiskManagerCheckRequest request, boolean passed ) { sem.release(); } public void checkCancelled( DiskManagerCheckRequest request ) { sem.release(); } public void checkFailed( DiskManagerCheckRequest request, Throwable cause ) { sem.release(); } }); }catch( Throwable e ){ sem.release(); } } public void writeFailed( DiskManagerWriteRequest request, Throwable cause ) { sem.release(); } }); }catch( Throwable e ){ sem.release(); } } public void readFailed( DiskManagerReadRequest request, Throwable cause ) { sem.release(); } public int getPriority() { return( -1 ); } public void requestExecuted( long bytes ) { } }); sem.reserve(); } } } } }finally{ check_in_progress = false; } //dump the newly built resume data to the disk/torrent if ( !( stopped || resume_data_complete )){ try{ saveResumeData( true ); }catch( Exception e ){ Debug.out( "Failed to dump initial resume data to disk" ); Debug.printStackTrace( e ); } } }catch( Throwable e ){ // if something went wrong then log and continue. Debug.printStackTrace(e); }finally{ recheck_inst.unregister(); // System.out.println( "Check of '" + disk_manager.getDownloadManager().getDisplayName() + "' completed in " + (System.currentTimeMillis() - start)); } } public void saveResumeData( boolean interim_save ) // data is marked as "invalid" if this is true to enable checking on pieces on crash restart throws Exception { if ( check_in_progress && interim_save ){ // while we are rechecking it is important that an interim save doesn't come // along and overwite the persisted resume data. This is because should we crash // while rechecking we need the persisted state to be unchanged so that on // restart the rechecking occurs again // a non-interim save means that the user has decided to stop the download (or some // other such significant event) so we just persist the current state return; } // if file caching is enabled then this is an important time to ensure that the cache is // flushed as we are going to record details about the accuracy of written data. // First build the resume map from the data (as updates can still be goin on) // Then, flush the cache. This means that on a successful flush the built resume // data matches at least the valid state of the data // Then update the torrent DiskManagerFileInfo[] files = disk_manager.getFiles(); if ( !use_fast_resume ){ // flush cache even if resume disable as this is a good point to ensure that data // is persisted anyway for (int i=0;i<files.length;i++){ files[i].flushCache(); } return; } boolean was_complete = isTorrentResumeDataComplete( disk_manager.getDownloadManager().getDownloadState()); DiskManagerPiece[] pieces = disk_manager.getPieces(); //build the piece byte[] byte[] resume_pieces = new byte[pieces.length]; for (int i = 0; i < resume_pieces.length; i++) { DiskManagerPiece piece = pieces[i]; // if we are terminating due to az closure and this has interrupted a recheck then // make sure that the recheck continues appropriately on restart if ( stopped_for_close && check_interrupted && check_is_full_check && i >= check_position ){ resume_pieces[i] = PIECE_RECHECK_REQUIRED; }else if ( piece.isDone()){ resume_pieces[i] = PIECE_DONE; }else if ( piece.getNbWritten() > 0 ){ resume_pieces[i] = PIECE_STARTED; }else{ resume_pieces[i] = PIECE_NOT_DONE; } } Map resume_data = new HashMap(); resume_data.put( "resume data", resume_pieces ); Map partialPieces = new HashMap(); for (int i = 0; i < pieces.length; i++) { DiskManagerPiece piece = pieces[i]; // save the partial pieces for any pieces that have not yet been completed // and are in-progress (i.e. have at least one block downloaded) boolean[] written = piece.getWritten(); if (( !piece.isDone()) && piece.getNbWritten() > 0 && written != null ){ boolean all_written = true; for (int j = 0; j < written.length; j++) { if ( !written[j] ){ all_written = false; break; } } if ( all_written ){ // just mark the entire piece for recheck as we've stopped the torrent at the // point where a check-piece was, or was about to be, scheduled resume_pieces[ i ] = PIECE_RECHECK_REQUIRED; }else{ List blocks = new ArrayList(); for (int j = 0; j < written.length; j++) { if (written[j]){ blocks.add(new Long(j)); } } partialPieces.put("" + i, blocks); } } } resume_data.put("blocks", partialPieces); long lValid; if ( check_interrupted ){ // set validity to what it was before the check started lValid = check_resume_was_valid?1:0; }else if ( interim_save ){ // set invalid so that not-done pieces get rechecked on startup lValid = 0; }else{ lValid = 1; } resume_data.put("valid", new Long(lValid)); for (int i=0;i<files.length;i++){ files[i].flushCache(); } // OK, we've got valid resume data and flushed the cache boolean is_complete = isTorrentResumeDataComplete( disk_manager.getDownloadManager().getDownloadState(), resume_data ); if ( was_complete && is_complete ){ // no change, no point in writing }else{ saveResumeData( resume_data ); } } protected Map getResumeData() { return( getResumeData( disk_manager.getDownloadManager())); } protected static Map getResumeData( DownloadManager download_manager) { return( getResumeData( download_manager.getDownloadState())); } protected static Map getResumeData( DownloadManagerState download_manager_state ) { Map resume_map = download_manager_state.getResumeData(); if ( resume_map != null ){ Map resume_data = (Map)resume_map.get( "data" ); return( resume_data ); }else{ return( null ); } } protected void saveResumeData( Map resume_data ) { saveResumeData( disk_manager.getDownloadManager().getDownloadState(), resume_data ); } protected static void saveResumeData( DownloadManagerState download_manager_state, Map resume_data ) { Map resume_map = new HashMap(); resume_map.put( "data", resume_data ); download_manager_state.setResumeData( resume_map ); } public static void setTorrentResumeDataComplete( DownloadManagerState download_manager_state ) { TOTorrent torrent = download_manager_state.getTorrent(); int piece_count = torrent.getNumberOfPieces(); byte[] resume_pieces = new byte[piece_count]; Arrays.fill( resume_pieces, PIECE_DONE ); Map resume_data = new HashMap(); resume_data.put( "resume data", resume_pieces ); Map partialPieces = new HashMap(); resume_data.put("blocks", partialPieces ); resume_data.put("valid", new Long(1)); saveResumeData( download_manager_state, resume_data ); } protected static int clearResumeDataSupport( DownloadManager download_manager, DiskManagerFileInfo file, boolean recheck, boolean onlyClearUnsharedFirstLast ) { DownloadManagerState download_manager_state = download_manager.getDownloadState(); Map resume_data = getResumeData( download_manager ); if ( resume_data == null ){ return(0); } int pieces_cleared = 0; // clear any affected pieces byte[] resume_pieces = (byte[])resume_data.get("resume data"); int firstPiece = file.getFirstPieceNumber(); int lastPiece = file.getLastPieceNumber(); if ( onlyClearUnsharedFirstLast ){ DiskManagerFileInfo[] files = download_manager.getDiskManagerFileInfo(); boolean firstPieceShared = false; boolean lastPieceShared = false; int firstFile = findFirstFileWithPieceN(firstPiece, files); for(int i = firstFile;i<files.length;i++) { DiskManagerFileInfo currentFile = files[i]; if(currentFile.getLastPieceNumber() < firstPiece) continue; if(currentFile.getIndex() == file.getIndex()) continue; if(currentFile.getFirstPieceNumber() > lastPiece) break; if(currentFile.getFirstPieceNumber() <= firstPiece && firstPiece <= currentFile.getLastPieceNumber()) firstPieceShared |= !currentFile.isSkipped(); if(currentFile.getFirstPieceNumber() <= lastPiece && lastPiece <= currentFile.getLastPieceNumber()) lastPieceShared |= !currentFile.isSkipped(); } if(firstPieceShared) firstPiece++; if(lastPieceShared) lastPiece--; } if ( resume_pieces != null ){ for (int i=firstPiece;i<=lastPiece;i++){ if ( i >= resume_pieces.length ){ break; } if ( resume_pieces[i] == PIECE_DONE ){ pieces_cleared++; } resume_pieces[i] = recheck?PIECE_RECHECK_REQUIRED:PIECE_NOT_DONE; } } // clear any affected partial pieces Map partial_pieces = (Map)resume_data.get("blocks"); if ( partial_pieces != null ){ Iterator iter = partial_pieces.keySet().iterator(); while (iter.hasNext()) { int piece_number = Integer.parseInt((String)iter.next()); if ( piece_number >= firstPiece && piece_number <= lastPiece ){ iter.remove(); } } } // either way we're valid as // 1) clear -> pieces are set as not done // 2) recheck -> pieces are set as "recheck" and will be checked on restart resume_data.put( "valid", new Long(1)); saveResumeData( download_manager_state, resume_data ); return( pieces_cleared ); } /** * finds the first affected file via binary search, this is necessary as some methods might be * invoked for all files, which would result in O(n²) if we'd scan the whole file array every * time */ private static int findFirstFileWithPieceN(int firstPiece, DiskManagerFileInfo[] files) { int start = 0; int end = files.length-1; int pivot = 0; while (start <= end) { pivot = (start + end) >>> 1; int midVal = files[pivot].getLastPieceNumber(); if (midVal < firstPiece) start = pivot + 1; else if (midVal > firstPiece) end = pivot - 1; else { // some matching file, now slide leftwards to find the first one, shouldn't be that many while(pivot > 0 && files[pivot-1].getLastPieceNumber() == firstPiece) pivot--; break; } } return pivot; } public static boolean fileMustExist(DownloadManager download_manager, DiskManagerFileInfo file) { Map resumeData = getResumeData( download_manager ); byte[] resumePieces = resumeData != null ? (byte[])resumeData.get("resume data") : null; boolean sharesAnyNeededPieces = false; DiskManagerFileInfo[] files = download_manager.getDiskManagerFileInfo(); int firstPiece = file.getFirstPieceNumber(); int lastPiece = file.getLastPieceNumber(); int firstFile = findFirstFileWithPieceN(firstPiece, files); // we must sweep over the files, as any number of files could share the first/last piece of the file we're probing for (int i = firstFile; i < files.length && !sharesAnyNeededPieces; i++) { DiskManagerFileInfo currentFile = files[i]; if(currentFile.getLastPieceNumber() < firstPiece) continue; if (currentFile.getIndex() == file.getIndex() && resumePieces != null && file.getStorageType() != DiskManagerFileInfo.ST_COMPACT && file.getStorageType() != DiskManagerFileInfo.ST_REORDER_COMPACT) for (int j = firstPiece; j <= lastPiece && !sharesAnyNeededPieces; j++) sharesAnyNeededPieces |= resumePieces[j] != PIECE_NOT_DONE; if (currentFile.getFirstPieceNumber() > lastPiece) break; if (currentFile.getFirstPieceNumber() <= firstPiece && firstPiece <= currentFile.getLastPieceNumber()) sharesAnyNeededPieces |= !currentFile.isSkipped(); if (currentFile.getFirstPieceNumber() <= lastPiece && lastPiece <= currentFile.getLastPieceNumber()) sharesAnyNeededPieces |= !currentFile.isSkipped(); } return sharesAnyNeededPieces; } public static int storageTypeChanged( DownloadManager download_manager, DiskManagerFileInfo file ) { return( clearResumeDataSupport( download_manager, file, false, true )); } public static void clearResumeData( DownloadManager download_manager, DiskManagerFileInfo file ) { clearResumeDataSupport( download_manager, file, false, false ); } public static void recheckFile( DownloadManager download_manager, DiskManagerFileInfo file ) { clearResumeDataSupport( download_manager, file, true, false ); } public static void setTorrentResumeDataNearlyComplete( DownloadManagerState download_manager_state ) { // backwards compatability, resume data key is the dir TOTorrent torrent = download_manager_state.getTorrent(); long piece_count = torrent.getNumberOfPieces(); byte[] resume_pieces = new byte[(int)piece_count]; Arrays.fill( resume_pieces, PIECE_DONE ); // randomly clear some pieces for (int i=0;i<3;i++){ int piece_num = (int)(Math.random()*piece_count); resume_pieces[piece_num]= PIECE_RECHECK_REQUIRED; } Map resumeMap = new HashMap(); resumeMap.put( "resume data", resume_pieces); Map partialPieces = new HashMap(); resumeMap.put("blocks", partialPieces); resumeMap.put("valid", new Long(0)); // recheck the not-done pieces saveResumeData(download_manager_state,resumeMap); } public static boolean isTorrentResumeDataComplete( DownloadManagerState dms ) { // backwards compatability, resume data key is the dir Map resume_data = getResumeData( dms ); return( isTorrentResumeDataComplete( dms, resume_data )); } protected static boolean isTorrentResumeDataComplete( DownloadManagerState download_manager_state, Map resume_data ) { try{ int piece_count = download_manager_state.getTorrent().getNumberOfPieces(); if ( resume_data != null ){ byte[] pieces = (byte[])resume_data.get("resume data"); Map blocks = (Map)resume_data.get("blocks"); boolean valid = ((Long)resume_data.get("valid")).intValue() == 1; // any partial pieced -> not complete if ( blocks == null || blocks.size() > 0 ){ return( false ); } if ( valid && pieces != null && pieces.length == piece_count ){ for (int i=0;i<pieces.length;i++){ if ( pieces[i] != PIECE_DONE ){ // missing piece or recheck outstanding return( false ); } } return( true ); } } }catch( Throwable e ){ Debug.printStackTrace( e ); } return( false ); } }