/*
* Created on 21-Mar-2006
* Created by Paul Gardner
* Copyright (C) 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 com.aelitis.azureus.core.util;
import java.util.*;
import java.io.*;
import org.gudy.azureus2.core3.util.Debug;
public class
LinkFileMap
{
/*
* History here: Before 5001_B22 file linkage was performed by linking source files and target files - source file being the
* original, unmodified location of the file as if no linking had been performed, target being wherever. This was designed way
* back when the FileManager didn't have any knowledge of torrents and therefore didn't understand what a file index within
* a torrent was and it just applied a source->target renaming operation transparently. However, things changed and torrent
* knowledge crept into the FileManager (for piece-reordering storage for example). Linking was still based on file though.
* Then a bug appeared caused by the removal of OS-specific illegal file system chars (e.g. : on windows) resulting in
* two files in the torrent resolving to the same physical file on disk. The 'obvious' solution, to rename one of them to
* avoid the conflict, didn't work as renaming is based on linking and linking was based on physical file names, and both
* files had the same physical file so the rename affected both files and left the conflict in existence.
* So I decided to rework the code to use file indexes instead of source file names. Well, I currently have both in place
* to support migration of config, but at some point we should be able to remove the source-file component of linking
* and just use index->target. Maybe
*
* Note that the FileManagerImpl's getLink requires access to the from-name to verify that the link it looks up
* is valid (principally caused by the 'move' method running BEFORE links are updated - really this should be
* reworked
*/
private Map<wrapper,Entry> name_map = new HashMap<wrapper,Entry>();
private Map<Integer,Entry> index_map = new HashMap<Integer,Entry>();
public File
get(
int index,
File from_file )
{
if ( index >= 0 ){
Entry entry = index_map.get( index );
if ( entry != null ){
return( entry.getToFile());
}
}else{
Debug.out( "unexpected index" );
}
Entry entry = name_map.get( new wrapper( from_file ));
if ( entry == null ){
return( null );
}else{
// migration - all existing links to migrate have an index of -1
int e_index = entry.getIndex();
if ( e_index >= 0 && e_index != index ){
return( null );
}
return( entry.getToFile());
}
}
public Entry
getEntry(
int index,
File from_file )
{
if ( index >= 0 ){
Entry entry = index_map.get( index );
if ( entry != null ){
return( entry );
}
}else{
Debug.out( "unexpected index" );
}
Entry entry = name_map.get( new wrapper( from_file ));
if ( entry == null ){
return( null );
}else{
// migration - all existing links to migrate have an index of -1
int e_index = entry.getIndex();
if ( e_index >= 0 && e_index != index ){
return( null );
}
return( entry );
}
}
public void
put(
int index,
File from_file,
File to_file )
{
Entry entry = new Entry( index, from_file, to_file );
if ( index >= 0 ){
index_map.put( index, entry );
// remove any legacy entry
if ( name_map.size() > 0 ){
name_map.remove( new wrapper( from_file ));
}
}else{
wrapper wrap = new wrapper( from_file );
Entry existing = name_map.get( wrap );
if ( existing == null ||
!existing.getFromFile().equals( from_file) ||
!existing.getToFile().equals( to_file )){
Debug.out( "unexpected index" );
}
name_map.put( wrap, entry );
}
}
public void
putMigration(
File from_file,
File to_file )
{
Entry entry = new Entry( -1, from_file, to_file );
name_map.put( new wrapper( from_file ), entry );
}
public void
remove(
int index,
File key )
{
if ( index >= 0 ){
index_map.remove( index );
}else{
// this can happen when removing non-resolved entries, not a problem
//Debug.out( "unexpected index" );
}
if ( name_map.size() > 0 ){
name_map.remove( new wrapper( key ));
}
}
public Iterator<Entry>
entryIterator()
{
if ( index_map.size() > 0 ){
if ( name_map.size() == 0 ){
return( index_map.values().iterator());
}
Set<Entry> entries = new HashSet<Entry>( index_map.values());
entries.addAll( name_map.values());
return( entries.iterator());
}
return( name_map.values().iterator());
}
public String
getString()
{
String str = "";
if ( index_map.size() > 0 ){
String i_str = "";
for ( Entry e: index_map.values()){
i_str += (i_str.length()==0?"":", ") + e.getString();
}
str += "i_map={ " + i_str + " }";
}
if ( name_map.size() > 0 ){
String n_str = "";
for ( Entry e: name_map.values()){
n_str += (n_str.length()==0?"":", ") + e.getString();
}
str += "n_map={ " + n_str + " }";
}
return( str );
}
public static class
Entry
{
private int index;
private File from_file;
private File to_file;
private
Entry(
int _index,
File _from_file,
File _to_file )
{
index = _index;
from_file = _from_file;
to_file = _to_file;
}
public int
getIndex()
{
return( index );
}
public File
getFromFile()
{
return( from_file );
}
public File
getToFile()
{
return( to_file );
}
public String
getString()
{
return( index + ": " + from_file + " -> " + to_file );
}
}
private static class
wrapper
{
private String file_str;
protected
wrapper(
File file )
{
file_str = file.toString();
}
public boolean
equals(
Object other )
{
if ( other instanceof wrapper ){
return( file_str.equals(((wrapper)other).file_str ));
}
return( false );
}
public int
hashCode()
{
return( file_str.hashCode());
}
}
}