/*
* File : ShareManagerImpl.java
* Created : 30-Dec-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.pluginsimpl.local.sharing;
/**
* @author parg
*
*/
import java.io.*;
import java.net.*;
import java.util.*;
import org.gudy.azureus2.plugins.torrent.*;
import org.gudy.azureus2.pluginsimpl.local.torrent.*;
import org.gudy.azureus2.plugins.sharing.*;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.core3.config.*;
import org.gudy.azureus2.core3.logging.*;
import org.gudy.azureus2.core3.torrent.*;
import org.gudy.azureus2.core3.tracker.util.TRTrackerUtils;
import com.aelitis.azureus.core.AzureusCoreFactory;
public class
ShareManagerImpl
implements ShareManager, TOTorrentProgressListener, ParameterListener, AEDiagnosticsEvidenceGenerator
{
private static final LogIDs LOGID = LogIDs.PLUGIN;
public static final String TORRENT_STORE = "shares";
public static final String TORRENT_SUBSTORE = "cache";
public static final int MAX_FILES_PER_DIR = 1000;
public static final int MAX_DIRS = 1000;
protected static ShareManagerImpl singleton;
private static AEMonitor class_mon = new AEMonitor( "ShareManager:class" );
protected AEMonitor this_mon = new AEMonitor( "ShareManager" );
protected TOTorrentCreator to_creator;
public static ShareManagerImpl
getSingleton()
throws ShareException
{
try{
class_mon.enter();
if ( singleton == null ){
singleton = new ShareManagerImpl();
}
return( singleton );
}finally{
class_mon.exit();
}
}
private volatile boolean initialised;
private volatile boolean initialising;
private File share_dir;
private URL[] announce_urls;
private ShareConfigImpl config;
private Map shares = new HashMap();
private shareScanner current_scanner;
private List listeners = new ArrayList();
protected
ShareManagerImpl()
throws ShareException
{
COConfigurationManager.addListener(
new COConfigurationListener()
{
public void
configurationSaved()
{
announce_urls = null;
}
});
AEDiagnostics.addEvidenceGenerator( this );
}
public void
initialise()
throws ShareException
{
try{
this_mon.enter();
if ( !initialised ){
try{
initialising = true;
initialised = true;
share_dir = FileUtil.getUserFile( TORRENT_STORE );
FileUtil.mkdirs(share_dir);
config = new ShareConfigImpl();
try{
config.suspendSaving();
config.loadConfig(this);
checkConsistency();
}finally{
Iterator it = shares.values().iterator();
while(it.hasNext()){
ShareResourceImpl resource = (ShareResourceImpl)it.next();
if ( resource.getType() == ShareResource.ST_DIR_CONTENTS ){
for (int i=0;i<listeners.size();i++){
try{
((ShareManagerListener)listeners.get(i)).resourceAdded( resource );
}catch( Throwable e ){
Debug.printStackTrace( e );
}
}
}
}
config.resumeSaving();
}
readAZConfig();
}finally{
initialising = false;
}
}
}finally{
this_mon.exit();
}
}
public boolean
isInitialising()
{
return( initialising );
}
protected void
readAZConfig()
{
COConfigurationManager.addParameterListener( "Sharing Rescan Enable", this );
readAZConfigSupport();
}
public void
parameterChanged(
String name )
{
readAZConfigSupport();
}
protected void
readAZConfigSupport()
{
try{
this_mon.enter();
boolean scan_enabled = COConfigurationManager.getBooleanParameter( "Sharing Rescan Enable" );
if ( !scan_enabled ){
current_scanner = null;
}else if ( current_scanner == null ){
current_scanner = new shareScanner();
}
}finally{
this_mon.exit();
}
}
protected ShareConfigImpl
getShareConfig()
{
return( config );
}
protected void
checkConsistency()
throws ShareException
{
// copy set for iteration as consistency check can delete resource
Iterator it = new HashSet(shares.values()).iterator();
while(it.hasNext()){
ShareResourceImpl resource = (ShareResourceImpl)it.next();
try{
resource.checkConsistency();
}catch( ShareException e ){
Debug.printStackTrace(e);
}
}
}
protected void
deserialiseResource(
Map map )
{
try{
ShareResourceImpl new_resource = null;
int type = ((Long)map.get("type")).intValue();
if ( type == ShareResource.ST_FILE ||
type == ShareResource.ST_DIR ){
new_resource = ShareResourceFileOrDirImpl.deserialiseResource( this, map, type );
}else{
new_resource = ShareResourceDirContentsImpl.deserialiseResource( this, map );
}
if ( new_resource != null ){
ShareResourceImpl old_resource = (ShareResourceImpl)shares.get(new_resource.getName());
if ( old_resource != null ){
old_resource.delete(true);
}
shares.put( new_resource.getName(), new_resource );
// we delay the reporting of dir_contents until all recovery is complete so that
// the resource reported is initialised correctly
if ( type != ShareResource.ST_DIR_CONTENTS ){
for (int i=0;i<listeners.size();i++){
try{
((ShareManagerListener)listeners.get(i)).resourceAdded( new_resource );
}catch( Throwable e ){
Debug.printStackTrace( e );
}
}
}
}
}catch( Throwable e ){
Debug.printStackTrace( e );
}
}
protected String
getNewTorrentLocation()
throws ShareException
{
Random rand = new Random(SystemTime.getCurrentTime());
for (int i=1;i<=MAX_DIRS;i++){
String cache_dir_str = share_dir + File.separator + TORRENT_SUBSTORE + i;
File cache_dir = new File(cache_dir_str);
if ( !cache_dir.exists()){
FileUtil.mkdirs(cache_dir);
}
if ( cache_dir.listFiles().length < MAX_FILES_PER_DIR ){
for (int j=0;j<MAX_FILES_PER_DIR;j++){
long file = Math.abs(rand.nextLong());
File file_name = new File(cache_dir_str + File.separator + file + ".torrent");
if ( !file_name.exists()){
// return path relative to cache_dir to save space
return( TORRENT_SUBSTORE + i + File.separator + file + ".torrent" );
}
}
}
}
throw( new ShareException( "ShareManager: Failed to allocate cache file"));
}
protected void
writeTorrent(
ShareItemImpl item )
throws ShareException
{
try{
item.getTorrent().writeToFile( getTorrentFile(item ));
}catch( TorrentException e ){
throw( new ShareException( "ShareManager: Torrent write fails", e ));
}
}
protected void
readTorrent(
ShareItemImpl item )
throws ShareException
{
try{
TOTorrent torrent = TOTorrentFactory.deserialiseFromBEncodedFile( getTorrentFile(item ));
item.setTorrent(new TorrentImpl(torrent));
}catch( TOTorrentException e ){
throw( new ShareException( "ShareManager: Torrent read fails", e ));
}
}
protected void
deleteTorrent(
ShareItemImpl item )
{
File torrent_file = getTorrentFile(item);
torrent_file.delete();
}
protected boolean
torrentExists(
ShareItemImpl item )
{
return( getTorrentFile(item).exists());
}
protected File
getTorrentFile(
ShareItemImpl item )
{
return( new File(share_dir+File.separator+item.getTorrentLocation()));
}
protected URL[]
getAnnounceURLs()
throws ShareException
{
if ( announce_urls == null ){
String protocol = COConfigurationManager.getStringParameter( "Sharing Protocol" );
if ( protocol.equalsIgnoreCase( "DHT" )){
announce_urls = new URL[]{ TorrentUtils.getDecentralisedEmptyURL()};
}else{
URL[][] tracker_url_sets = TRTrackerUtils.getAnnounceURLs();
if ( tracker_url_sets.length == 0 ){
throw( new ShareException( "ShareManager: Tracker must be configured"));
}
for (int i=0;i<tracker_url_sets.length;i++){
URL[] tracker_urls = tracker_url_sets[i];
if ( tracker_urls[0].getProtocol().equalsIgnoreCase( protocol )){
announce_urls = tracker_urls;
break;
}
}
if ( announce_urls == null ){
throw( new ShareException( "ShareManager: Tracker must be configured for protocol '" + protocol + "'" ));
}
}
}
return( announce_urls );
}
protected boolean
getAddHashes()
{
return( COConfigurationManager.getBooleanParameter( "Sharing Add Hashes" ));
}
public ShareResource[]
getShares()
{
ShareResource[] res = new ShareResource[shares.size()];
shares.values().toArray( res );
return( res );
}
protected ShareResourceImpl
getResource(
File file )
throws ShareException
{
try{
return((ShareResourceImpl)shares.get(file.getCanonicalFile().toString()));
}catch( IOException e ){
throw( new ShareException( "getCanonicalFile fails", e ));
}
}
public ShareResource
getShare(
File file_or_dir )
{
try{
return( getResource( file_or_dir ));
}catch( ShareException e ){
return( null );
}
}
public ShareResourceFile
addFile(
File file )
throws ShareException, ShareResourceDeletionVetoException
{
return( addFile( null, file ));
}
protected ShareResourceFile
addFile(
ShareResourceDirContentsImpl parent,
File file )
throws ShareException, ShareResourceDeletionVetoException
{
if (Logger.isEnabled())
Logger.log(new LogEvent(LOGID, "ShareManager: addFile '"
+ file.toString() + "'"));
try{
return( (ShareResourceFile)addFileOrDir( parent, file, ShareResource.ST_FILE, false ));
}catch( ShareException e ){
reportError(e);
throw(e);
}
}
public ShareResourceFile
getFile(
File file )
throws ShareException
{
return( (ShareResourceFile)ShareResourceFileImpl.getResource( this, file ));
}
public ShareResourceDir
addDir(
File dir )
throws ShareException, ShareResourceDeletionVetoException
{
return( addDir( null, dir ));
}
public ShareResourceDir
addDir(
ShareResourceDirContentsImpl parent,
File dir )
throws ShareException, ShareResourceDeletionVetoException
{
if (Logger.isEnabled())
Logger.log(new LogEvent(LOGID, "ShareManager: addDir '" + dir.toString()
+ "'"));
try{
this_mon.enter();
return( (ShareResourceDir)addFileOrDir( parent, dir, ShareResource.ST_DIR, false ));
}catch( ShareException e ){
reportError(e);
throw(e);
}finally{
this_mon.exit();
}
}
public ShareResourceDir
getDir(
File file )
throws ShareException
{
return( (ShareResourceDir)ShareResourceDirImpl.getResource( this, file ));
}
protected ShareResource
addFileOrDir(
ShareResourceDirContentsImpl parent,
File file,
int type,
boolean modified )
throws ShareException, ShareResourceDeletionVetoException
{
try{
this_mon.enter();
String name = file.getCanonicalFile().toString();
ShareResource old_resource = (ShareResource)shares.get(name);
if ( old_resource != null ){
old_resource.delete();
}
ShareResourceImpl new_resource;
if ( type == ShareResource.ST_FILE ){
reportCurrentTask( "Adding file '" + name + "'");
new_resource = new ShareResourceFileImpl( this, parent, file );
}else{
reportCurrentTask( "Adding dir '" + name + "'");
new_resource = new ShareResourceDirImpl( this, parent, file );
}
shares.put(name, new_resource );
config.saveConfig();
for (int i=0;i<listeners.size();i++){
try{
if ( modified ){
((ShareManagerListener)listeners.get(i)).resourceModified( new_resource );
}else{
((ShareManagerListener)listeners.get(i)).resourceAdded( new_resource );
}
}catch( Throwable e ){
Debug.printStackTrace( e );
}
}
return( new_resource );
}catch( IOException e ){
throw( new ShareException( "getCanoncialFile fails", e ));
}finally{
this_mon.exit();
}
}
public ShareResourceDirContents
addDirContents(
File dir,
boolean recursive )
throws ShareException, ShareResourceDeletionVetoException
{
if (Logger.isEnabled())
Logger.log(new LogEvent(LOGID, "ShareManager: addDirContents '"
+ dir.toString() + "'"));
try{
this_mon.enter();
String name = dir.getCanonicalFile().toString();
reportCurrentTask( "Adding dir contents '" + name + "', recursive = " + recursive );
ShareResource old_resource = (ShareResource)shares.get( name );
if ( old_resource != null ){
old_resource.delete();
}
ShareResourceDirContents new_resource = new ShareResourceDirContentsImpl( this, dir, recursive );
shares.put( name, new_resource );
config.saveConfig();
for (int i=0;i<listeners.size();i++){
try{
((ShareManagerListener)listeners.get(i)).resourceAdded( new_resource );
}catch( Throwable e ){
Debug.printStackTrace( e );
}
}
return( new_resource );
}catch( IOException e ){
reportError(e);
throw( new ShareException( "getCanoncialFile fails", e ));
}catch( ShareException e ){
reportError(e);
throw(e);
}finally{
this_mon.exit();
}
}
protected void
delete(
ShareResourceImpl resource )
throws ShareException
{
if (Logger.isEnabled())
Logger.log(new LogEvent(LOGID, "ShareManager: resource '"
+ resource.getName() + "' deleted"));
try{
this_mon.enter();
shares.remove(resource.getName());
resource.deleteInternal();
config.saveConfig();
for (int i=0;i<listeners.size();i++){
try{
((ShareManagerListener)listeners.get(i)).resourceDeleted( resource );
}catch( Throwable e ){
Debug.printStackTrace( e );
}
}
}finally{
this_mon.exit();
}
}
protected void
scanShares()
throws ShareException
{
if (Logger.isEnabled())
Logger.log(new LogEvent(LOGID,
"ShareManager: scanning resources for changes"));
checkConsistency();
}
// bit of a hack this, but to do it properly would require extensive rework to decouple the
// process of saying "share file" and then actually doing it
protected void
setTorrentCreator(
TOTorrentCreator _to_creator )
{
to_creator = _to_creator;
}
public void
cancelOperation()
{
TOTorrentCreator temp = to_creator;
if ( temp != null ){
temp.cancel();
}
}
public void
reportProgress(
int percent_complete )
{
for (int i=0;i<listeners.size();i++){
try{
((ShareManagerListener)listeners.get(i)).reportProgress( percent_complete );
}catch( Throwable e ){
Debug.printStackTrace( e );
}
}
}
public void
reportCurrentTask(
String task_description )
{
for (int i=0;i<listeners.size();i++){
try{
((ShareManagerListener)listeners.get(i)).reportCurrentTask( task_description );
}catch( Throwable e ){
Debug.printStackTrace( e );
}
}
}
protected void
reportError(
Throwable e )
{
String message = e.getMessage();
if ( message != null ){
reportCurrentTask( Debug.getNestedExceptionMessage(e));
}else{
reportCurrentTask( e.toString());
}
}
public void
addListener(
ShareManagerListener l )
{
listeners.add(l);
}
public void
removeListener(
ShareManagerListener l )
{
listeners.remove(l);
}
public void
generate(
IndentWriter writer )
{
writer.println( "Shares" );
try{
writer.indent();
ShareResource[] shares = getShares();
HashSet share_map = new HashSet();
for ( int i=0;i<shares.length;i++ ){
ShareResource share = shares[i];
if ( share instanceof ShareResourceDirContents ){
share_map.add( share );
}else if ( share.getParent() != null ){
}else{
writer.println( getDebugName( share ));
}
}
Iterator it = share_map.iterator();
TorrentManager tm = AzureusCoreFactory.getSingleton().getPluginManager().getDefaultPluginInterface().getTorrentManager();
TorrentAttribute category_attribute = tm.getAttribute( TorrentAttribute.TA_CATEGORY );
TorrentAttribute props_attribute = tm.getAttribute( TorrentAttribute.TA_SHARE_PROPERTIES );
while( it.hasNext()){
ShareResourceDirContents root = (ShareResourceDirContents)it.next();
String cat = root.getAttribute( category_attribute );
String props = root.getAttribute( props_attribute );
String extra = cat==null?"":(",cat=" + cat );
extra += props==null?"":(",props=" + props );
extra += ",rec=" + root.isRecursive();
writer.println( root.getName() + extra );
generate( writer, root );
}
}finally{
writer.exdent();
}
}
protected void
generate(
IndentWriter writer,
ShareResourceDirContents node )
{
try{
writer.indent();
ShareResource[] kids = node.getChildren();
for (int i=0;i<kids.length;i++){
ShareResource kid = kids[i];
writer.println( getDebugName( kid ));
if ( kid instanceof ShareResourceDirContents ){
generate( writer, (ShareResourceDirContents)kid );
}
}
}finally{
writer.exdent();
}
}
protected String
getDebugName(
ShareResource _share )
{
Torrent torrent = null;
try{
if ( _share instanceof ShareResourceFile ){
ShareResourceFile share = (ShareResourceFile)_share;
torrent = share.getItem().getTorrent();
}else if ( _share instanceof ShareResourceDir ){
ShareResourceDir share = (ShareResourceDir)_share;
torrent = share.getItem().getTorrent();
}
}catch( Throwable e ){
}
if ( torrent == null ){
return( Debug.secretFileName( _share.getName()));
}else{
return( Debug.secretFileName( torrent.getName() ) + "/" + ByteFormatter.encodeString( torrent.getHash()));
}
}
protected class
shareScanner
{
boolean run = true;
protected
shareScanner()
{
current_scanner = this;
Thread t =
new AEThread( "ShareManager::scanner" )
{
public void
runSupport()
{
while( current_scanner == shareScanner.this ){
try{
int scan_period = COConfigurationManager.getIntParameter( "Sharing Rescan Period" );
if ( scan_period < 1 ){
scan_period = 1;
}
Thread.sleep( scan_period * 1000 );
if ( current_scanner == shareScanner.this ){
scanShares();
}
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
}
};
t.setDaemon(true);
t.start();
}
}
}