/*
* ShareManager.java
*
* Copyright (C) 2008 AppleGrew
*
* 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 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.
*/
package org.elite.jdcbot.shareframework;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.tools.bzip2.CBZip2OutputStream;
import org.elite.jdcbot.framework.BotException;
import org.elite.jdcbot.framework.BotInterface;
import org.elite.jdcbot.framework.DUEntity;
import org.elite.jdcbot.framework.GlobalObjects;
import org.elite.jdcbot.framework.User;
import org.elite.jdcbot.util.GlobalFunctions;
import org.elite.jdcbot.util.InputEntityStream;
import org.slf4j.Logger;
import org.xml.sax.SAXException;
/**
* Created on 26-May-08<br>
* Its purpose is to manage the user shared files, i.e. hashing them,
* creating/updating file list, etc. You should use this download other
* users' file lists. This class will automatically parse the file list
* and create an instance of FileListManager for it. Note that this class
* won't monitor added directories for changes, you will needed to monitor
* files and directories for changes and call the appropriate methods. There
* is one advice though. Whenever you find a new file then get a list of
* files in the file list that no longer exist and if any one of them 'looks'
* very similar to the new file (by say size or file name) then ask user
* if this is really true or not. If true then simply update the converned
* FLFile to point to this new path.
* <p>
* This class is thread safe.
*
* @author AppleGrew
* @since 0.7
* @version 0.2.1
*/
public class ShareManager {
private static final Logger logger = GlobalObjects.getLogger(ShareManager.class);
private final String fileListHash = "hashDump";
private final String fileList = "files.xml.bz2";
private final String downloadedFLDirName = "DowloadedFLs";
protected File miscDir;
protected File downloadFLDir;
/**
* This is an abstraction of the file list. This is
* saved into 'fileListHash' under {@link #miscDir}
* directory using serialization.
* <p>
* <b>Note:</b> You must call {@link #setDirs(String, String) setDirs(String, String)} as soon as
* possible as many exceptions could be thrown and it is actually undefined what will happen if
* the directories are not setup. After that call {@link #init() init()}.<br>
* So, always follow the following steps:-
* <ol>
* <li>Call Constructor</li>
* <li>call {@link #setDirs(String, String) setDirs(String, String)}</li>
* <li>Call {@link #init() init()}</li>
* </ol>
*/
protected FileListManager ownFL;
protected Map<User, FileListManager> FLs;
protected HashManager hashMan;
protected List<ShareManagerListener> listeners;
private InputEntityStream hash_ies;
protected UploadStreamManager uploadStreamManager;
private String hashingFile;
private double hashSpeed = -1;
private int maximumFLtoKeepInRAM = 5;
protected ShareWorker shareWorker = null;
protected BotInterface boi;
/**
* <b>Note:</b> You must call {@link #setDirs(String, String) setDirs(String, String)} as soon as
* possible as many exceptions could be thrown and it is actually undefined what will happen if
* the directories are not setup. After that call {@link #init() init()}.<br>
* So, always follow the following steps:-
* <ol>
* <li>Call Constructor</li>
* <li>call {@link #setDirs(String, String) setDirs(String, String)}</li>
* <li>Call {@link #init() init()}</li>
* </ol>
* <b>Note:</b> Since version 1.0, the above methods are called automatically by
* jDCBot or MultiHubsAdapter's setShareManager().
* @param boi It should be jDCBot or if MultiHubsAdapter is present then it should
* be that.
*/
public ShareManager(BotInterface boi) {
ownFL = new FileListManager();
FLs = Collections.synchronizedMap(new HashMap<User, FileListManager>());
listeners = Collections.synchronizedList(new ArrayList<ShareManagerListener>());
hashMan = new HashManager();
uploadStreamManager = new UploadStreamManager();
ownFL.setFilelist(null);
this.boi = boi;
}
/**
* Called by jDCBot or MultiHubsAdapter on
* setShareManager().
* <p>
* <b>Note:</b> The given directories' must exist and should be empty, as
* already existing files in them will overwritten without warning.
*
* @param path2DirForMiscData In this directory own file list, hash data, etc. will be kept.
*/
public void setDirs(String path2DirForMiscData) {
miscDir = new File(path2DirForMiscData);
downloadFLDir = new File(path2DirForMiscData + File.separator + downloadedFLDirName);
if (!downloadFLDir.exists())
downloadFLDir.mkdir();
}
/**
* Called by jDCBot or MultiHubsAdapter on
* setShareManager().
* <p>
* Loads own file list from the disk.
*/
public void init() {
ownFL.setFilelist(null);
File fl = new File(miscDir.getPath() + File.separator + fileListHash);
boolean otherException = false;
try {
ownFL.setFilelist(FLDir.readObjectFromStream(new BufferedInputStream(new FileInputStream(fl))));
} catch (FileNotFoundException e) {
ownFL.setFilelist(new FLDir("Root", true, null));
ownFL.getFilelist().setCID(generateUniqueCID());
} catch (IOException e) {
logger.error("Exception in init()", e);
otherException = true;
} catch (ClassNotFoundException e) {
logger.error("Exception in init()", e);
otherException = true;
} catch (InstantiationException e) {
logger.error("Exception in init()", e);
otherException = true;
}
if (otherException) {
fl.delete();
}
if (ownFL.getFilelist() == null) {
ownFL.setFilelist(new FLDir("Root", true, null));
ownFL.getFilelist().setCID(generateUniqueCID());
}
}
public void addListener(ShareManagerListener sml) {
listeners.add(sml);
}
public void removeListener(ShareManagerListener sml) {
listeners.remove(sml);
}
protected void notifyMiscMsg(String msg) {
synchronized (listeners) {
for (ShareManagerListener sml : listeners)
sml.onMiscMsg(msg);
}
}
/**
* Deletes the 'fileListHash' dump file
* and frees the internal on RAM data structure,
* i.e. {@link #ownFL}.
*/
public void purgeHash() {
synchronized (ownFL) {
ownFL.setFilelist(new FLDir("Root", true, null));
ownFL.getFilelist().setCID(generateUniqueCID());
File f = new File(miscDir.getPath() + File.separator + fileListHash);
f.delete();
}
}
protected void saveOwnFL() throws FileNotFoundException, IOException {
FLDir.saveObjectToStream(new BufferedOutputStream(new FileOutputStream(miscDir.getPath() + File.separator + fileListHash)), ownFL
.getFilelist());
}
/**
* The number of file lists to keep in the
* RAM. Excess file lists are unloaded and saved
* into secondary storage disk. When required it
* will be automatically restored back into the RAM.
* @param count The number of file lists to keep including
* bot's own. This can have a minimum value of 2. If it
* is not then it is ignored.
*/
public void setMaximumFLtoKeepInRAM(int count) {
if (count < 2)
return;
maximumFLtoKeepInRAM = count;
}
protected void saveOthersFLs() throws FileNotFoundException, IOException {
Set<User> users = FLs.keySet();
synchronized (FLs) {
for (User u : users)
FLDir.saveObjectToStream(new BufferedOutputStream(new FileOutputStream(downloadFLDir.getPath() + File.separator
+ (u.getClientID().isEmpty() ? u.username() : u.getClientID()))), FLs.get(u).getFilelist());
}
}
/**
*
* @param u
* @throws FileNotFoundException
* @throws IOException
*/
protected void saveOthersFL(User u) throws FileNotFoundException, IOException {
File fl = new File(downloadFLDir.getPath() + File.separator + (u.getClientID().isEmpty() ? u.username() : u.getClientID()));
fl.deleteOnExit();
FLDir.saveObjectToStream(new BufferedOutputStream(new FileOutputStream(fl)), FLs.get(u).getFilelist());
}
/**
*
* @param u
* @throws FileNotFoundException
* @throws IOException
* @throws ClassNotFoundException
* @throws InstantiationException
* @return A new instance of FileListManager with the file list of User <i>u</i>
* loaded from the secondary disk.
*/
protected FileListManager loadOthersFL(User u) throws FileNotFoundException, IOException, ClassNotFoundException,
InstantiationException {
FLDir root =
FLDir.readObjectFromStream(new BufferedInputStream(new FileInputStream(downloadFLDir.getPath() + File.separator
+ (u.getClientID().isEmpty() ? u.username() : u.getClientID()))));
return new FileListManager(root);
}
protected void freeOthersFL(User u) {
try {
saveOthersFL(u);
} catch (FileNotFoundException e) {
logger.error("Exception in loadOthersFL()", e);
} catch (IOException e) {
logger.error("Exception in loadOthersFL()", e);
}
FLs.remove(u);
}
protected void freeExcessOthersFLs(int max) {
synchronized (FLs) {
if (FLs.size() <= max)
return;
User users[] = FLs.keySet().toArray(new User[0]);
int i = 0;
while (FLs.size() > max)
freeOthersFL(users[i++]);
}
}
public void close() {
try {
saveOwnFL();
} catch (FileNotFoundException e) {
logger.error("Exception in close()", e);
} catch (IOException e) {
logger.error("Exception in close()", e);
}
}
/**
* Rebulids the file list using the
* 'fileListHash' {@link #ownFL} dump.
* @throws IOException
* @throws FileNotFoundException
*/
synchronized public void rebuildFileList() throws FileNotFoundException, IOException {
OutputStream bos = new BufferedOutputStream(new FileOutputStream(miscDir.getPath() + File.separator + fileList));
bos.write("BZ".getBytes());
bos = new CBZip2OutputStream(bos);
writeFL(bos, ownFL.getFilelist());
bos.close();
boi.updateShareSize();
}
protected void writeFL(OutputStream out, FLDir flRoot) throws IOException {
out.write("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n".getBytes());
out.write(("<FileListing Version=\"1\" CID=\"" + flRoot.getCID() + "\" Base=\"/\" Generator=\"" + GlobalObjects.CLIENT_NAME + " "
+ GlobalObjects.VERSION + "\">\n").getBytes());
writeDir2FL(out, flRoot, "");
out.write("</FileListing>\n".getBytes());
}
private void writeDir2FL(OutputStream out, FLDir dir, String indentTabs) throws UnsupportedEncodingException, IOException {
if (!dir.isShared())
return;
//if (!dir.isRoot() || (dir.isRoot() && dir.hasFile()))
if (!dir.isRoot())
out.write((indentTabs + "<Directory Name=\"" + dir.getName() + "\">\n").getBytes("utf-8"));
FLDir dirs[] = dir.getSubDirs().toArray(new FLDir[0]);
for (FLDir d : dirs) {
writeDir2FL(out, d, indentTabs + "\t");
}
FLFile files[] = dir.getFiles().toArray(new FLFile[0]);
for (FLFile f : files) {
if (f.shared && f.hash != null && !f.hash.isEmpty()) {
out.write((indentTabs + "\t" + "<File Name=\"" + f.name + "\" Size=\"" + f.size + "\" TTH=\"" + f.hash + "\"/>\n")
.getBytes("utf-8"));
} else
notifyMiscMsg(f + " not written to file list, since it is not shared or its hash is not set.");
}
//if (!dir.isRoot() || (dir.isRoot() && dir.hasFile()))
if (!dir.isRoot())
out.write((indentTabs + "</Directory>\n").getBytes());
}
/**
*
* @param search
* @param maxResult
* @param user The user who has searched for this. If it was an
* active search then it is any one arbitrary user from the source
* IP. She may not have actually made this search.
* @param certainity It is the probability that the above user did actually
* search this. (1.0 being certain)
* @return null is never returned.
*/
public List<SearchResultSet> searchOwnFileList(SearchSet search, final int maxResult, User user, double certainity) {
synchronized (ownFL) {
return getOwnFL(user, certainity).search(search, maxResult, false);
}
}
/**
* This is similar to {@link #searchOwnFileList(SearchSet, int, User, double)}
* except for the fact that it guarantees that the search is made in bot's
* own file list, no matter what {@link #getOwnFL(User,double)} returns. Furthermore,
* it doesn't require you to specify some weird arguments like the user who
* is searching and the certainity factor.
* @param search
* @param maxResult
* @return
*/
public List<SearchResultSet> searchOwnFileList(SearchSet search, final int maxResult) {
synchronized (ownFL) {
return ownFL.search(search, maxResult, false);
}
}
/**
* @return Percentage completion of hashing task.
*/
public int getPercentageHashCompletion() {
if (hash_ies == null)
return 100;
return (int) Math.round(hash_ies.getPercentageCompletion());
}
/**
* @return Speed of hashing in
* bytes/second.
*/
public double getHashingSpeed() {
if (hash_ies == null)
return 0;
return hash_ies.getTransferRate();
}
/**
* Will cancel hashing while adding new
* share or updating existing share.
*/
public void cancelHashing() {
if (shareWorker != null)
shareWorker.cancelJob();
}
/**
* @return Time left to complete hashing
* in seconds. -1 is returned when this value
* cannot be calculated.
*/
public double getTimeLeft2CompleteHashing() {
if (hash_ies == null)
return -1;
return hash_ies.getTimeRemaining();
}
/**
* @return The path of the currently hashing
* file.
*/
public String getCurrentlyHashedFileName() {
if (hashingFile == null)
return "";
return hashingFile;
}
/**
* You need to use UploadStreamManager to monitor
* progress of uploads and restrict the upload
* transfer rate to a value.
* @return Returns an UploadStreamManager.
*/
public UploadStreamManager getUploadStreamManager() {
return uploadStreamManager;
}
public void setMaxHashingSpeed(double rate) {
if (hash_ies != null)
hash_ies.setTransferLimit(rate);
hashSpeed = rate;
}
public double getMaxHashingSpeed() {
return hashSpeed;
}
/**
*
* @param file The hash (should start with "TTH/") or virtual path to file to download. The
* path may start with Root or may not. Also the path may start with / or may not, it is always
* assumed that the path is an absolute path name. And yes of course the path deliminator can be
* forward '/' or backward '\' slashes.
* @param fileType The file type. See {@link org.elite.jdcbot.framework.DUEntity#fileType fileType}
* @param start
* @param fileLen
* @return It returns an instance of DUEntity with its properly initialized and its <i>in</i>
* field set to a valid InputStream.
* @throws FileNotFoundException If the file is not found in the file list or it no more exists
* on the file system.
*/
public DUEntity getFile(User u, String file, DUEntity.Type fileType, long start, long Len) throws FileNotFoundException {
if (fileType == DUEntity.Type.FILELIST)
return getFileList(u);
if (fileType == DUEntity.Type.TTHL)
throw new FileNotFoundException("Not supported");
FLDir root = getOwnFL(u, 1.0);
FLFile f;
String tfile = file;
if (tfile.startsWith("TTH/")) {//file is hash
tfile = tfile.substring(4);
f = root.getFileInTreeByHash(tfile, true);
} else {//file is not hash
tfile = tfile.replace('\\', '/');
if (!tfile.startsWith("/" + root.getName()) || !tfile.startsWith(root.getName()))
tfile = root.getName() + (tfile.startsWith("/") ? "" : "/") + tfile;
FLInterface fd = root.getChildInTree(FLDir.getDirNamesFromPath(tfile), false);
if (fd == null || fd instanceof FLDir)
throw new FileNotFoundException("File not found");
f = (FLFile) fd;
}
if (f == null)
throw new FileNotFoundException("File not found");
if (!canUpload(u, f.path))
throw new FileNotFoundException("File not found");
DUEntity due;
if (!f.path.startsWith("cache://")) {
File ff = new File(f.path);
if (!ff.canRead()) {
throw new FileNotFoundException("File is not readable");
}
if (!ff.exists()) {
f.shared = false;
throw new FileNotFoundException("File not found");
}
due =
new DUEntity(DUEntity.Type.FILE, file, start, Len, uploadStreamManager.getInputEntityStream(u, new BufferedInputStream(
new FileInputStream(ff))));
} else {
//This allows you to send 'virtual files', i.e. files which are stored in RAM as byte stream.
//These files' FLFile.path should start with cache://.
byte b[] = getCacheFileData(f.path.substring(8));
if (b == null)
throw new FileNotFoundException("File not found");
due =
new DUEntity(DUEntity.Type.FILE, file, start, Len, uploadStreamManager.getInputEntityStream(u, new BufferedInputStream(
new ByteArrayInputStream(b))));
f.size = b.length;
}
if (start >= f.size)
throw new FileNotFoundException("Start offset exceeds or is equal to the file length");
try {
due.in().skip(start);
} catch (IOException e) {
logger.error("Exception in getFile()", e);
throw new FileNotFoundException("File was found but IOException occured while skipping to the requested position");
}
if (due.in() instanceof InputEntityStream) {
InputEntityStream in = (InputEntityStream) due.in();
in.setTotalStreamLength(Len); //Needed for calculating percentage completion.
}
return due;
}
public DUEntity getFileList(User u) throws FileNotFoundException {
byte b[] = getVirtualFLData(u);
InputStream ifl;
long len;
if (b == null) {
File fl = new File(miscDir.getPath() + File.separator + "files.xml.bz2");
if (!fl.exists() && fl.canRead())
throw new FileNotFoundException("User file list not found or is not readable.");
ifl = new FileInputStream(fl);
len = fl.length();
} else {
len = b.length;
ifl = new ByteArrayInputStream(b);
}
DUEntity due = new DUEntity(DUEntity.Type.FILELIST, "", 0, len, new BufferedInputStream(ifl));
due.in();
return due;
}
public FileListManager getOwnFileListManager() {
return ownFL;
}
/**
*
* @param u The user whose file list is required.
* @return Returns the FileListManager of the user. If the user's file list
* has not been downloaded then null is returned.
*/
public FileListManager getOthersFileListManager(User u) {
FileListManager flm = FLs.get(u);
if (flm == null) {
try {
flm = loadOthersFL(u);
if (FLs.size() + 1 > maximumFLtoKeepInRAM) {
freeExcessOthersFLs(FLs.size() + 1 - maximumFLtoKeepInRAM + 1);
}
FLs.put(u, flm);
} catch (FileNotFoundException e) {
logger.error("Exception in getOthersFileListManager()", e);
} catch (IOException e) {
logger.error("Exception in getOthersFileListManager()", e);
} catch (ClassNotFoundException e) {
logger.error("Exception in getOthersFileListManager()", e);
} catch (InstantiationException e) {
logger.error("Exception in getOthersFileListManager()", e);
}
}
return flm;
}
/**
*
* @param all If set to true then files which are
* not shared, but still are in the tree;
* their sizes too will be counted.
* @return Your total share size.
*/
public long getOwnShareSize(boolean all) {
synchronized (ownFL) {
if (ownFL.getFilelist() == null)
return 0;
return ownFL.getFilelist().getSize(all);
}
}
/**
* Adds new files or directories to share. If these files already exists
* in share (could be hidden) then they are skipped.
* @param includes The list of files or directories to share.
* @param excludes The list of files or directories to exclude
* from <i>includes</i>.
* @param filter This allows you to filter out files like ones which are
* hidden, etc.
* @param inside This is the virtual path in the file list where you want the
* shares to get added. If this is null then root of the file list is assumed.
* @throws HashException When exception occurs while starting hashing.
* @throws ShareException When hashing is in progress since the last time
* this method or {@link #updateShare(Vector)} was called or <i>inside</i>
* path is not null and it is not found.
*/
public void addShare(List<File> includes, List<File> excludes, FilenameFilter filter, String inside) throws HashException,
ShareException {
if (shareWorker != null)
throw new ShareException(ShareException.Error.HASHING_JOB_IN_PROGRESS);
FLDir root = null;
if (inside != null) {
FLInterface fi = ownFL.getFilelist().getChildInTree(FLDir.getDirNamesFromPath(sanitizeVirtualPath(inside)), true);
if (fi == null || fi instanceof FLFile)
throw new ShareException(ShareException.Error.FILE_OR_DIR_NOT_FOUND);
root = (FLDir) fi;
}
ShareAdder sa = new ShareAdder(includes, excludes, filter, root);
shareWorker = sa;
sa.addShare();
}
/**
* Removes files and directories from share. Note that
* they are <b>not</b> deleted from the share, but
* simply hidden from share. You will need to call
* {@link #pruneUnsharedShares()} to actually delete them.
* @param fORd The list of virtual paths to files and/or
* directories to be removed from share.
* @throws ShareException When any of the given paths are not found.
* The process is not interrupted, rather at the completion of this task
* the last path that was not found is returned in the message. All the
* paths that were not found are reported using onMiscMsg() event.
*/
public void removeShare(List<String> fORd) throws ShareException {
String pathNotFound = null;
FLDir fl = ownFL.getFilelist();
for (String p : fORd) {
p = sanitizeVirtualPath(p);
FLInterface fd = fl.getChildInTree(FLDir.getDirNamesFromPath(p), false);
if (fd != null) {
if (fd instanceof FLDir)
((FLDir) fd).setShared(false);
else
((FLFile) fd).shared = false;
} else {
pathNotFound = p;
notifyMiscMsg("Path: '" + p + "' not found and hence cannot be removed.");
}
}
try {
rebuildFileList();
} catch (FileNotFoundException e) {
logger.error("Exception in removeShare()", e);
} catch (IOException e) {
logger.error("Exception in removeShare()", e);
}
if (pathNotFound != null) {
throw new ShareException(pathNotFound, ShareException.Error.FILE_OR_DIR_NOT_FOUND);
}
}
/**
* Has mercy over petty users who don't care
* give a proper virtual path.
* <p>
* Converts \ to /, if the path doesn't start
* with / then prefixes one, if the path doesn't
* start with /Root then prefixes one. Note
* that use this to sanitize ONLY absolute
* paths, not relative paths.
* <p>
* This method is re-entrant.
*
* @param p The absolute virtual path to sanitize.
* @return Sanitized virtual path.
*/
protected String sanitizeVirtualPath(String p) {
p = p.replace('\\', '/');
FLDir fl = ownFL.getFilelist();
if (!p.startsWith("/" + fl.getName()))
p = "/" + fl.getName() + (p.startsWith("/") ? "" : "/") + p;
return p;
}
/**
* Rehashes the given files.
* @param fORd Path to the list of files or
* directories to hash. If the path is a directory
* then all files in its tree will be rehashed. The
* path may or maynot start wiht /Root.
* @throws HashException When exception occurs while starting hashing.
* @throws ShareException When hashing is in progress since the last time
* this method or {@link #addShare(Vector, Vector, FilenameFilter)} was called.
*/
public void updateShare(List<String> fORd) throws HashException, ShareException {
if (shareWorker != null)
throw new ShareException(ShareException.Error.HASHING_JOB_IN_PROGRESS);
shareWorker = prvUpdateShare(fORd);
}
private ShareUpdater prvUpdateShare(List<String> fORd) throws HashException {
ShareUpdater su = new ShareUpdater(fORd);
shareWorker = su;
su.updateShare();
return su;
}
/**
* Deletes all FLDir and FLFiles in own
* file list that are not shared.
*/
public void pruneUnsharedShares() {
synchronized (ownFL) {
ownFL.getFilelist().pruneUnsharedSharesInTree();
}
}
/**
* @return All FLFiles in own file list whose path
* points to not existant files. It will never be
* null.
*/
public Collection<FLFile> getAllNonExistantFiles() {
synchronized (ownFL) {
return ownFL.getFilelist().getAllNonExistantFiles();
}
}
/**
* Downloads other user's file list.
* @param u
* @throws BotException
*/
public void downloadOthersFileList(User u) throws BotException {
if (u != null)
u.downloadFileList(new FilelistDownloader(u), DUEntity.NO_SETTING);
}
/**
* Generates a unique ID for the client.
*/
protected String generateUniqueCID() {
return hashMan.getHash(Long.toString(System.currentTimeMillis()) + "jDCBot" + Double.toString(Math.random()));
}
//************Private Classes*******************/
private abstract class ShareWorker implements HashUser {
protected void notifyHashingOfFileSkipped(String f, String reason) {
synchronized (listeners) {
for (ShareManagerListener sml : listeners)
sml.hashingOfFileSkipped(f, reason);
}
}
public abstract void cancelJob();
protected void notifyHashingOfFileComplete(String f, boolean success, HashException e) {
synchronized (listeners) {
for (ShareManagerListener s : listeners)
s.hashingOfFileComplete(f, success, e);
}
}
protected boolean isSubOf(File who, File ofWhom) {
if (GlobalFunctions.isWindowsOS())
return who.getAbsolutePath().toLowerCase().startsWith(ofWhom.getAbsolutePath().toLowerCase());
else
return who.getAbsolutePath().startsWith(ofWhom.getAbsolutePath());
}
protected FLDir createParentFLDirs(File f, File targetparent, FLDir parent) {
f = f.getParentFile();
if (f.equals(targetparent)) {
return parent;
} else {
FLDir myparent = createParentFLDirs(f, targetparent, parent);
FLDir me = new FLDir(f.getName(), false, myparent);
if (!myparent.addSubDir(me)) {//then it already exists.
return myparent.getDirInTree(me);
}
return me;
}
}
protected void notifyHashingOfFileStarting(String f) {
synchronized (listeners) {
for (ShareManagerListener s : listeners)
s.hashingOfFileStarting(f);
}
}
protected void notifyHashingJobFinished() {
synchronized (listeners) {
for (ShareManagerListener s : listeners)
s.hashingJobFinished();
}
}
protected long totalSize(Collection<File> all) {
long size = 0;
for (File fd : all) {
size += totalSize(fd);
}
return size;
}
protected long totalSize(File any) {
long size = 0;
if (any.isDirectory()) {
File fs[] = any.listFiles(new FileFilter() {
public boolean accept(File pathname) {
if (pathname.isFile())
return true;
else
return false;
}
});
if (fs != null)
for (File f : fs) {
size += f.length();
}
File ds[] = any.listFiles(new FileFilter() {
public boolean accept(File pathname) {
if (pathname.isDirectory())
return true;
else
return false;
}
});
if (ds != null)
for (File d : ds)
size += totalSize(d);
} else
size = any.length();
return size;
}
}
private class ShareAdder extends ShareWorker {
private List<File> includes;
private List<File> excludes;
private FilenameFilter filter;
private List<FLFile> flfiles;
private List<String> updateShare;
private ShareUpdater shareUpdater = null;
private FLDir root;
private List<FLDir> inc_fldir;
private List<File> inc_dir;
private boolean closing = false;
public ShareAdder(List<File> includes, List<File> excludes, FilenameFilter filter, FLDir inside) {
hash_ies = null;
this.includes = includes;
this.excludes = excludes;
this.filter = filter;
synchronized (ownFL) {
if (ownFL.getFilelist() == null) {
ownFL.setFilelist(new FLDir("Root", true, null));
ownFL.getFilelist().setCID(generateUniqueCID());
}
root = inside;
if (root == null)
root = ownFL.getFilelist();
flfiles = ownFL.getFilelist().getAllFilesUnderTheTree();
}
inc_dir = Collections.synchronizedList(new ArrayList<File>());
inc_fldir = Collections.synchronizedList(new ArrayList<FLDir>());
synchronized (root) {
for (File d : includes) {
if (d.isDirectory()) {
try {
d = d.getCanonicalFile();
inc_dir.add(d);
FLDir fld = new FLDir(d.getName(), false, root);
inc_fldir.add(fld);
root.addSubDir(fld);
} catch (IOException e) {
logger.error("Exception in ShareAdder()", e);
}
}
}
}
for (int i = 0; i < inc_dir.size(); i++) { //removing FLDirs which are sub-dir of an existing FLDir.
for (int j = 0; j < inc_dir.size();) {
if (i == j) {
j++;
continue;
}
if (isSubOf(inc_dir.get(i), inc_dir.get(j))) {
inc_dir.remove(i);
inc_fldir.remove(i);
if (i > j)
j++;
else
i--;
break;
} else
j++;
}
}
updateShare = Collections.synchronizedList(new ArrayList<String>());
}
public void addShare() throws HashException {
hashMan.hash(includes, this);
}
public void cancelJob() {
closing = true;
if (shareUpdater == null) {
hashMan.cancelHashing();
//finish();
} else {
shareUpdater.cancelJob();
}
}
public boolean canHash(File f) {
File cf = f;
try {
cf = f.getCanonicalFile();
} catch (IOException e) {
logger.error("Exception in canHash()", e);
}
if (closing) {
notifyHashingOfFileSkipped(cf.getAbsolutePath(), "Hashing cancelled.");
return false;
}
if (filter.accept(cf.getParentFile(), cf.getName())) {
boolean accept = true;
String reason = "";
int in = excludes.indexOf(f);
if (in != -1) {
accept = false;
reason = "In exclude list.";
excludes.remove(in); //Done so that for next iteration we need to search lesser elements.
}
FLFile flf = new FLFile(cf.getName(), cf.length(), cf.getAbsolutePath(), cf.lastModified(), true, null);
in = flfiles.indexOf(flf);
if (in != -1) {
FLFile tflf = flfiles.get(in);
if (tflf.lastModified != flf.lastModified || tflf.size != flf.size) {
if (accept)
updateShare.add(tflf.getVirtualPath());
}
tflf.shared = accept;
if (accept)
reason = "Already shared.";
//Done so that for next iteration we need to search lesser elements.
//A possible source of problem could be when this file is in includes more
//than once, then the second time it will get added becuase of the following
//line. Anyway it has been assumed that it will not happen.
flfiles.remove(in);
accept = false;
}
if (accept)
return true;
else
notifyHashingOfFileSkipped(cf.getAbsolutePath(), reason);
}
return false;
}
public InputStream getInputStream(File f) {
if (closing)
return null;
try {
if (hash_ies == null) {
hash_ies = new InputEntityStream(new BufferedInputStream(new FileInputStream(f)));
hash_ies.setTotalStreamLength(totalSize(includes) - totalSize(excludes));
hash_ies.setTransferLimit(hashSpeed);
} else {
hash_ies.setInputStream(new BufferedInputStream(new FileInputStream(f)));
}
} catch (FileNotFoundException e) {
logger.error("Exception in getInputStream()", e);
}
return hash_ies;
}
public void onFileHashed(File f, String hash, boolean success, HashException e) {
try {
f = f.getCanonicalFile();
} catch (IOException ioe) {
logger.error("Exception in onFileHashed()", e);
}
if (success) {
FLFile flf = new FLFile(f.getName(), f.length(), f.getAbsolutePath(), f.lastModified(), true, null);
flf.hash = hash;
boolean added = false;
for (int i = 0; i < inc_dir.size(); i++) {
File d = inc_dir.get(i);
if (isSubOf(f, d)) {
FLDir parent = createParentFLDirs(f, d, inc_fldir.get(i));
parent.addFile(flf);
flf.parent = parent;
added = true;
break;
}
}
if (!added) {//This file was not a sub of any dirs above, so add it in the Root.
root.addFile(flf);
flf.parent = root;
}
}
notifyHashingOfFileComplete(f.getAbsolutePath(), success, e);
}
public void hashingOfFileStarting(File file) {
try {
file = file.getCanonicalFile();
} catch (IOException e) {
logger.error("Exception in hashingOfFileStarting()", e);
}
hashingFile = file.getName();
notifyHashingOfFileStarting(file.getAbsolutePath());
}
public void onHashingJobFinished() {
hash_ies = null;
hashingFile = null;
if (closing) {
finish();
return;
}
boolean updatedShare = false;
if (updateShare.size() != 0) {
try {
shareUpdater = prvUpdateShare(updateShare);
updatedShare = true;
} catch (HashException e) {
logger.error("Exception in onHashingJobFinished()", e);
}
}
if (!updatedShare) {
finish();
}
}
private void finish() {
try {
saveOwnFL();
rebuildFileList();
} catch (FileNotFoundException e) {
logger.error("Exception in finish()", e);
} catch (IOException e) {
logger.error("Exception in finish()", e);
}
hash_ies = null;
hashingFile = null;
shareWorker = null;
notifyHashingJobFinished();
}
}
private class ShareUpdater extends ShareWorker {
private Map<File, FLFile> _updateShares;
private boolean closing = false;
public ShareUpdater(List<String> updateShares) {
_updateShares = new HashMap<File, FLFile>();
synchronized (ownFL) {
FLDir fl = ownFL.getFilelist();
for (String p : updateShares) {
if (!p.startsWith("/" + fl.getName()))
p = "/" + fl.getName() + "/" + p;
FLInterface fd = fl.getChildInTree(FLDir.getDirNamesFromPath(p), false);
if (fd instanceof FLDir) {
FLDir d = ((FLDir) fd);
for (FLFile f : d.getAllFilesUnderTheTree())
_updateShares.put(new File(f.path), f);
} else {
FLFile f = ((FLFile) fd);
_updateShares.put(new File(f.path), f);
}
}
}
}
public void updateShare() throws HashException {
hashMan.hash(_updateShares.keySet(), this);
}
public void cancelJob() {
closing = true;
hashMan.cancelHashing();
//finish();
}
public boolean canHash(File f) {
if (closing) {
notifyHashingOfFileSkipped(f.getAbsolutePath(), "Hashing cancelled.");
return false;
}
return true;
}
public InputStream getInputStream(File f) {
if (closing)
return null;
try {
if (hash_ies == null) {
hash_ies = new InputEntityStream(new BufferedInputStream(new FileInputStream(f)));
hash_ies.setTotalStreamLength(totalSize(_updateShares.keySet()));
hash_ies.setTransferLimit(hashSpeed);
} else {
hash_ies.setInputStream(new BufferedInputStream(new FileInputStream(f)));
}
} catch (FileNotFoundException e) {
logger.error("Exception in getInputStream()", e);
}
return hash_ies;
}
public void onFileHashed(File f, String hash, boolean success, HashException e) {
if (success) {
_updateShares.get(f).hash = hash;
}
notifyHashingOfFileComplete(f.getAbsolutePath(), success, e);
}
public void hashingOfFileStarting(File file) {
hashingFile = file.getName();
notifyHashingOfFileStarting(file.getAbsolutePath());
}
public void onHashingJobFinished() {
finish();
}
private void finish() {
try {
saveOwnFL();
rebuildFileList();
} catch (FileNotFoundException e) {
logger.error("Exception in finish()", e);
} catch (IOException e) {
logger.error("Exception in finish()", e);
}
hash_ies = null;
hashingFile = null;
shareWorker = null;
notifyHashingJobFinished();
}
}
private class FilelistDownloader extends ByteArrayOutputStream {
User _u;
public FilelistDownloader(User u) {
_u = u;
}
public void close() {
FilelistConverter fc = new FilelistConverter(this.toByteArray());
try {
FLDir root = fc.parse();
_u.setClientID(root.getCID());
if (FLs.size() + 1 >= maximumFLtoKeepInRAM) {
freeExcessOthersFLs(FLs.size() + 1 - maximumFLtoKeepInRAM + 1);
}
FLs.put(_u, new FileListManager(root));
notifyListeners(true, null);
} catch (ParserConfigurationException e) {
logger.error("Exception in close()", e);
notifyListeners(false, e);
} catch (SAXException e) {
logger.error("Exception in close()", e);
notifyListeners(false, e);
} catch (IOException e) {
logger.error("Exception in close()", e);
notifyListeners(false, e);
}
try {
fc.close();
} catch (IOException e) {
logger.warn("Exception in close()", e);
}
}
private void notifyListeners(boolean success, Exception e) {
synchronized (listeners) {
for (ShareManagerListener sml : listeners)
sml.onFilelistDownloadFinished(_u, success, e);
}
}
}
//***********Methods meant to be overridden*************/
/**
* This is a method that allows you implement
* a <i>unique and weird</i>, albeit
* an interesting, feature, to stop particular
* users from downloading particular files from
* you. <b>Note:</b> You are advised to use
* {@link org.elite.jdcbot.framework.User#setBlockUploadToUser(boolean)}
* to block upload of all files to a particular
* user.
* <p>
* You will need to extend this class and overload this
* method to implement this feature.
* @param u The User who is trying to download
* from you. When this method is called you be certain that
* remote user's IP is available irrespective of the fact that
* you are an Operator in the hub or not.
* @param path The actual path to the file on the file system that the
* user is requesting.
* @return Return true to allow the upload. Passing false will
* return "File not found" error to the remote user.
*/
protected boolean canUpload(User u, String path) {
return true;
}
/**
* Allows you to send data of a virtual
* file. A virtual file is simply bytes
* stored in main memory (RAM). It could
* have been read from a file, but usually
* this will be String.getBytes().
* @param uri The path of the virtual
* file without the 'cache://' prefix.
* This path can be anything independent
* of its location in file list. This path
* is simply FLFile.path without the
* 'cache://' prefix.
* @return The bytes of the virtual file.
*/
protected byte[] getCacheFileData(String uri) {
return null;
}
/**
* This provides easy means to set
* different exclusive file list
* for a particular user. You can
* use this (say) return empty file
* list to a particular user.
* <p>
* You probably won't be using this feature
* but what's the problem with giving
* you the power to do so. ;-)
* <p>
* You will most probably use it in conjugation with
* {@link #getOwnFL(User, double)}.
*
* @param u The user who wants to download
* your file list.
* @return The file list's bytes. It
* must have been compressed using
* bzip2 and must be in XML format.
*/
protected byte[] getVirtualFLData(User u) {
return null;
}
/**
* Override this if you want to provide a
* custom file list for a user. This will
* be called by {@link #getFile(User, String, org.elite.jdcbot.framework.DUEntity.Type, long, long)}
* and {@link #searchOwnFileList(SearchSet, int, User, double)}.
* <p>
* You will most probably use it in conjugation with
* {@link #getVirtualFLData(User)}.
* @param u The user in question.
* @param certainity This is probability value
* which specifies how certain we are that the
* above user is <u>really</u> one who needs to access
* your file list. This weird argument was needed as
* during active search only user's IP is known and
* many users can actually share that IP. If only
* one user was found with this IP then probability
* is 1.0, else it gets divided by the total number
* of users found with that IP. In passive search,
* etc. it is always 1.0. But do note that <b><i>u</i>
* can be null</b> sometimes (particularly in case of
* active search) as IP information of all users is
* not always available.
* <p>
* See comment in the code of
* {@link org.elite.jdcbot.framework.jDCBot#onSearch(String,int,SearchSet) onSearch(String,int,SearchSet)}
* to exactly how this probability is calculated.
* @return Root FLDir of the file list.
*/
protected FLDir getOwnFL(User u, double certainity) {
synchronized (ownFL) {
return ownFL.getFilelist();
}
}
}