/* * Created on 30-Nov-2005 * Created by Paul Gardner * Copyright (C) 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 40,000 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package com.aelitis.azureus.core.diskmanager.file.impl; import java.io.File; import java.io.RandomAccessFile; import org.gudy.azureus2.core3.torrent.TOTorrent; import org.gudy.azureus2.core3.torrent.TOTorrentFile; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.DirectByteBuffer; import org.gudy.azureus2.core3.util.DirectByteBufferPool; import org.gudy.azureus2.core3.util.StringInterner; import com.aelitis.azureus.core.diskmanager.file.FMFile; import com.aelitis.azureus.core.diskmanager.file.FMFileManagerException; public class FMFileAccessController implements FMFileAccess { private static final String REORDER_SUFFIX = ".2"; private static final boolean TEST_PIECE_REORDER = System.getProperty( "azureus.file.piece.reorder.force", "0" ).equals( "1" ); static{ if ( TEST_PIECE_REORDER ){ Debug.out( "*** Piece reordering storage forced ***" ); } } private FMFileImpl owner; private int type = FMFile.FT_LINEAR; private File control_dir; private String controlFileName; private FMFileAccess file_access; protected FMFileAccessController( FMFileImpl _file, int _target_type ) throws FMFileManagerException { if ( TEST_PIECE_REORDER ){ _target_type = FMFile.FT_PIECE_REORDER; } owner = _file; // actual file shouldn't exist for change to occur - it is the responsibility // of the caller to delete the file first and take consequent actions (in // particular force recheck the file to ensure that the loss in save state // is represented in the resume view of the world ) // in the future, if we support format conversion, this obviously changes setControlFile(); if ( control_dir == null ){ // Debug.out( "No control file" ); in optimised environments we don't support compact and return null here if ( _target_type == FMFile.FT_LINEAR ){ file_access = new FMFileAccessLinear( owner ); }else{ throw( new FMFileManagerException( "Compact storage not supported: no control file available" )); } }else{ if ( new File( control_dir, controlFileName ).exists()){ type = FMFile.FT_COMPACT; }else if ( new File( control_dir, controlFileName + REORDER_SUFFIX ).exists()){ type = _target_type==FMFile.FT_PIECE_REORDER?FMFile.FT_PIECE_REORDER:FMFile.FT_PIECE_REORDER_COMPACT; }else{ if ((_target_type == FMFile.FT_PIECE_REORDER || _target_type == FMFile.FT_PIECE_REORDER_COMPACT )){ File target_file = owner.getLinkedFile(); if ( target_file.exists()){ FMFileAccessPieceReorderer.recoverConfig( owner.getOwner().getTorrentFile(), target_file, new File( control_dir, controlFileName + REORDER_SUFFIX ), _target_type ); } type = _target_type; }else{ type = FMFile.FT_LINEAR; } } if ( type == FMFile.FT_LINEAR ){ file_access = new FMFileAccessLinear( owner ); }else if ( type == FMFile.FT_COMPACT ){ file_access = new FMFileAccessCompact( owner.getOwner().getTorrentFile(), control_dir, controlFileName, new FMFileAccessLinear( owner )); }else{ file_access = new FMFileAccessPieceReorderer( owner.getOwner().getTorrentFile(), control_dir, controlFileName + REORDER_SUFFIX, type, new FMFileAccessLinear( owner )); } convert( _target_type ); } } protected void convert( int target_type ) throws FMFileManagerException { if ( type == target_type ){ return; } if ( type == FMFile.FT_PIECE_REORDER || target_type == FMFile.FT_PIECE_REORDER ){ if ( target_type == FMFile.FT_PIECE_REORDER_COMPACT || type == FMFile.FT_PIECE_REORDER_COMPACT ){ // these two access modes are in fact identical at the moment type = target_type; return; } throw( new FMFileManagerException( "Conversion to/from piece-reorder not supported" )); } File file = owner.getLinkedFile(); RandomAccessFile raf = null; boolean ok = false; try{ FMFileAccess target_access; if ( target_type == FMFile.FT_LINEAR ){ target_access = new FMFileAccessLinear( owner ); }else{ target_access = new FMFileAccessCompact( owner.getOwner().getTorrentFile(),control_dir, controlFileName, new FMFileAccessLinear( owner )); } if ( file.exists()){ raf = new RandomAccessFile( file, FMFileImpl.WRITE_ACCESS_MODE); // due to the simplistic implementation of compact we only actually need to deal with // the last piece of the file (first piece is in the right place already) FMFileAccessCompact compact_access; if ( target_type == FMFile.FT_LINEAR ){ compact_access = (FMFileAccessCompact)file_access; }else{ compact_access = (FMFileAccessCompact)target_access; } long length = file_access.getLength( raf ); long last_piece_start = compact_access.getLastPieceStart(); long last_piece_length = compact_access.getLastPieceLength(); // see if we have any potential data for the last piece if ( last_piece_length > 0 && length > last_piece_start ){ long data_length = length - last_piece_start; if ( data_length > last_piece_length ){ Debug.out("data length inconsistent: len=" + data_length + ",limit=" + last_piece_length ); data_length = last_piece_length; } DirectByteBuffer buffer = DirectByteBufferPool.getBuffer( DirectByteBuffer.AL_FILE, (int)data_length ); try{ file_access.read( raf, new DirectByteBuffer[]{ buffer }, last_piece_start ); // see if we need to truncate if ( target_type == FMFile.FT_COMPACT ){ long first_piece_length = compact_access.getFirstPieceLength(); long physical_length = raf.length(); if ( physical_length > first_piece_length ){ raf.setLength( first_piece_length ); } } buffer.flip( DirectByteBuffer.AL_FILE ); target_access.write( raf, new DirectByteBuffer[]{ buffer }, last_piece_start ); }finally{ buffer.returnToPool(); } }else{ // no last piece, truncate after the first piece if ( target_type == FMFile.FT_COMPACT ){ long first_piece_length = compact_access.getFirstPieceLength(); long physical_length = raf.length(); if ( physical_length > first_piece_length ){ raf.setLength( first_piece_length ); } } } target_access.setLength( raf, length ); target_access.flush(); } type = target_type; file_access = target_access; ok = true; }catch( Throwable e ){ Debug.printStackTrace( e ); throw( new FMFileManagerException( "convert fails", e )); }finally{ try{ if ( raf != null ){ try{ raf.close(); }catch( Throwable e ){ // override original exception if there isn't one if ( ok ){ ok = false; throw( new FMFileManagerException( "convert fails", e )); } } } }finally{ if ( !ok ){ // conversion failed - replace with linear access, caller is responsible for // handling this (marking file requiring recheck) type = FMFile.FT_LINEAR; file_access = new FMFileAccessLinear( owner ); } if ( type == FMFile.FT_LINEAR ){ new File(control_dir,controlFileName).delete(); } } } } protected void setControlFile() { TOTorrentFile tf = owner.getOwner().getTorrentFile(); if ( tf == null ){ controlFileName = null; control_dir = null; }else{ TOTorrent torrent = tf.getTorrent(); TOTorrentFile[] files = torrent.getFiles(); int file_index = -1; for (int i=0;i<files.length;i++){ if ( files[i] == tf ){ file_index = i; break; } } if ( file_index == -1 ){ Debug.out("File '" + owner.getName() + "' not found in torrent!" ); controlFileName = null; control_dir = null; }else{ control_dir = owner.getOwner().getControlFileDir( ); controlFileName = StringInterner.intern("fmfile" + file_index + ".dat"); } } } public void setStorageType( int new_type ) throws FMFileManagerException { convert( new_type ); } public int getStorageType() { return( type ); } // FileAccess public void aboutToOpen() throws FMFileManagerException { file_access.aboutToOpen(); } public long getLength( RandomAccessFile raf ) throws FMFileManagerException { return( file_access.getLength( raf )); } public void setLength( RandomAccessFile raf, long length ) throws FMFileManagerException { file_access.setLength( raf, length ); } public boolean isPieceCompleteProcessingNeeded( int piece_number ) { return( file_access.isPieceCompleteProcessingNeeded( piece_number )); } public void setPieceComplete( RandomAccessFile raf, int piece_number, DirectByteBuffer piece_data ) throws FMFileManagerException { file_access.setPieceComplete( raf, piece_number, piece_data ); } public void read( RandomAccessFile raf, DirectByteBuffer[] buffers, long offset ) throws FMFileManagerException { file_access.read( raf, buffers, offset ); } public void write( RandomAccessFile raf, DirectByteBuffer[] buffers, long position ) throws FMFileManagerException { file_access.write( raf, buffers, position ); } public void flush() throws FMFileManagerException { file_access.flush(); } public FMFileImpl getFile() { return( owner ); } public String getString() { return( "type=" + type + ",acc=" + file_access.getString()); } }