/* * FLDir.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.File; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.elite.jdcbot.util.GlobalFunctions; /** * Created on 04-Jun-08<br> * This represents a virtual directory of * a file list. * <p> * You should avoid using this class' object as * key in Map (and its sub-classes). If you need to * do that then call {@link #setImmutable()} to render * the object immutable. This will make {@link #setParent(FLDir)} * and {@link #setName(String)} * have no effect at all. * <p> * This class is thread-safe. * * @author AppleGrew * @since 1.0 * @version 0.1 */ public class FLDir implements Serializable, FLInterface { private static final long serialVersionUID = 8442912993644448963L; private final int HASH_CONST = 11; private String _name; private FLDir _parent; private volatile boolean _isRoot; private List<FLFile> _files; private List<FLDir> _dirs; private volatile boolean isJDCBotGenerated = false; private volatile boolean isShared = true; /** * Should be set only if this dir is * the root. */ private String CID = null; /** * Not setting this to volatile as it is * always accessed in synchronized * mthods. */ private boolean isImmutable = false; /** * * @param name The name of the virtual directory in the file list. * @param isRoot Is this the virtual root directory. This directory * is normaly not shown in the file list. This is merely a way to emcompass * all shared files and directories under a single directory. Hence, * only ONE directory must be set as root per file list. If this is * root then its name is set to 'Root'. * @param parent The parent of this FLDir. This should be null for * root. */ public FLDir(String name, boolean isRoot, FLDir parent) { _files = Collections.synchronizedList(new ArrayList<FLFile>()); _dirs = Collections.synchronizedList(new ArrayList<FLDir>()); _name = name; _isRoot = isRoot; _parent = parent; if (isRoot) _name = "Root"; } /** * Once rendered Immutable * then the object cannot * made mutable again. * */ synchronized public void setImmutable() { isImmutable = true; } synchronized public FLDir getParent() { return _parent; } /** * Once rendered immutable * calling this method will have * no effect. * @param d */ synchronized public void setParent(FLDir d) { if (!isImmutable) _parent = d; } synchronized public void setCID(String cid) { CID = cid; } public void setJDCBotGenerated(boolean flag) { isJDCBotGenerated = flag; } /** * @return true if the file list (which is represented by * this FLDir and FLFile tree) is jDCBot generated or not. * This can be checked for downloaded file lists too. * <p> * Only Root FLDir has this value set. */ public boolean isJDCBotGenerated() { return isJDCBotGenerated; } /** * Sets the isShared flag of this directory. * When this becomes unshared then all its * children become invisble in the file list * too. (Note that their shared flag is not * altered though). * <p> * Root can never be unshared. * @param flag */ synchronized public void setShared(boolean flag) { if (!_isRoot) isShared = flag; } public boolean isShared() { return isShared; } /** * This will set this directory's * isShared flag to true and will * also make sure that all its parent directories * too have their isShared flag to true * so that this directory can get actually * shared. */ synchronized public void actuallyShareIt() { isShared = true; if (_parent != null) _parent.actuallyShareIt(); } synchronized public String getCID() { return CID; } public boolean isRoot() { return _isRoot; } synchronized public String getName() { return _name; } /** * Once rendered immutable * calling this method will have * no effect. * @param name */ synchronized public void setName(String name) { if (!isImmutable) _name = name; } /** * Searches for matching file or directory. * @param For The searching term and criteria. * @param maxResult Maximum number of results to return. * Set this to <=0 to get all the results found. * @param all If true then it will search files with <i>shared</i> == false too. * @return An ArrayList of the matching files/directories. null is never returned. */ public List<SearchResultSet> search(SearchSet For, final int maxResult, boolean all) { String ss[] = For.string.toLowerCase().trim().split(" "); List<FLInterface> sr = new ArrayList<FLInterface>(); List<String> owners = new ArrayList<String>(); synchronized (_files) { synchronized (_dirs) { search(For, ss, sr, owners, maxResult, all); } } return convertFLItoSRS(sr, owners); } /** * Not thread-safe in itself and hence must be called from a synchronized block. * @param For * @param ss * @param sr * @param owners * @param maxResult * @param all */ private void search(SearchSet For, String ss[], List<FLInterface> sr, List<String> owners, final int maxResult, boolean all) { String pwd = this.getDirPath() + "/"; if (For.data_type != SearchSet.DataType.DIRECTORY) { for (FLFile f : _files) { if (maxResult > 0 && sr.size() > maxResult) break; if (!all && f.shared == false) continue; switch (For.data_type) { case ANY: if (GlobalFunctions.matches(ss, f.name) && fulfillsSizeCriteria(f, For)) { sr.add(f); owners.add(pwd); } break; case AUDIO: if (GlobalFunctions.hasExt(f.name, new String[] { "mp3", "mp2", "wav", "au", "rm", "mid", "sm", "ogg" }) && GlobalFunctions.matches(ss, f.name) && fulfillsSizeCriteria(f, For)) { sr.add(f); owners.add(pwd); } break; case COMPRESSED: if (GlobalFunctions.hasExt(f.name, new String[] { "zip", "arj", "rar", "lzh", "gz", "z", "arc", "pak", "bz2" }) && GlobalFunctions.matches(ss, f.name) && fulfillsSizeCriteria(f, For)) { sr.add(f); owners.add(pwd); } break; case DOCUMENT: if (GlobalFunctions.hasExt(f.name, new String[] { "doc", "txt", "wri", "pdf", "ps", "tex", "ppt", "pptx", "docx" }) && GlobalFunctions.matches(ss, f.name) && fulfillsSizeCriteria(f, For)) { sr.add(f); owners.add(pwd); } break; case EXECUTABLE: if (GlobalFunctions.hasExt(f.name, new String[] { "pm", "exe", "bat", "com", "sh", "class" }) && GlobalFunctions.matches(ss, f.name) && fulfillsSizeCriteria(f, For)) { sr.add(f); owners.add(pwd); } else if (!GlobalFunctions.isWindowsOS()) { File ff = new File(f.path); if (ff.exists() && ff.canExecute() && GlobalFunctions.matches(ss, f.name) && fulfillsSizeCriteria(f, For)) { sr.add(f); owners.add(pwd); } } break; case PICTURE: if (GlobalFunctions.hasExt(f.name, new String[] { "gif", "jpg", "jpeg", "bmp", "pcx", "png", "wmf", "psd", "tif" }) && GlobalFunctions.matches(ss, f.name) && fulfillsSizeCriteria(f, For)) { sr.add(f); owners.add(pwd); } break; case VIDEO: if (GlobalFunctions.hasExt(f.name, new String[] { "mpg", "mpeg", "avi", "asf", "mov", "mp4", "mkv", "divx", "rmvb", "rm", "ogg" }) && GlobalFunctions.matches(ss, f.name) && fulfillsSizeCriteria(f, For)) { sr.add(f); owners.add(pwd); } break; case TTH: if (f.hash.equalsIgnoreCase(For.string.trim()) && fulfillsSizeCriteria(f, For)) { sr.add(f); owners.add(pwd); } break; default: break; } } } for (FLDir d : _dirs) { if (maxResult > 0 && sr.size() > maxResult) break; if (!all && d.isShared == false) continue; if ((For.data_type == SearchSet.DataType.DIRECTORY || For.data_type == SearchSet.DataType.ANY) && (GlobalFunctions.matches(ss, d._name))) sr.add(d); d.search(For, ss, sr, owners, maxResult, all); } } /** * No issue of thread safety here as this is re-entrant. * @param f * @param SS * @return true if <i>f</i> fulfills size criteria as specified * in <i>SS</i>. */ private boolean fulfillsSizeCriteria(FLFile f, SearchSet SS) { SearchSet.SizeCriteria c = SS.size_criteria; SearchSet.SizeUnit u = SS.size_unit; long size = SS.size; if (c == SearchSet.SizeCriteria.NONE) return true; size = u == SearchSet.SizeUnit.BYTE ? size : u.getValue() * 1024 * size; if (c == SearchSet.SizeCriteria.ATLEAST) { if (f.size >= size) return true; else return false; } else { if (f.size <= size) return true; else return false; } } /** * No thread safety issue here as this is re-entrant. * @param sr * @param owners For every FLFile in <i>sr</i> there is one entry in this, in * exact order of its appearance in <i>sr</i>. This and <i>sr</i> size * may not be same as no owner information is stored for FLDirs. This is an * old code when FLFiles had no reference of their parents, hence it was * required to store their parents's path to create FLFiles full path. * @return An ArrayList of SearchResultSet created using list of FLInterface. */ private List<SearchResultSet> convertFLItoSRS(List<FLInterface> sr, List<String> owners) { List<SearchResultSet> SR = new ArrayList<SearchResultSet>(); int i = 0; for (FLInterface fd : sr) { SearchResultSet srs = new SearchResultSet(); if (fd instanceof FLDir) { FLDir d = (FLDir) fd; srs.isDir = true; srs.name = d.getDirPath(); srs.size = 0; srs.TTH = ""; } else { FLFile f = (FLFile) fd; srs.isDir = false; srs.name = owners.get(i++) + f.name; srs.size = f.size; srs.TTH = f.hash; } SR.add(srs); } return SR; } public boolean removeFile(FLFile f) { return _files.remove(f); } /** * This will add the file only if * didn't already existed. It will * automatically set its parent. * @param f * @return True if the file has been * added, and false it it already existed. */ public boolean addFile(FLFile f) { synchronized (_files) { if (_files.indexOf(f) == -1) { _files.add(f); f.parent = this; return true; } else return false; } } public void addFile(List<FLFile> files) { _files.addAll(files); } public boolean isFileExistsInTree(FLFile f) { if (_files.indexOf(f) != -1) return true; else { synchronized (_dirs) { for (FLDir d : _dirs) if (d.isFileExistsInTree(f)) return true; } } return false; } /** * Deletes the file <i>f</i> that could be * at any depth in the tree. It is assumed * that a file exists only once in the * tree hence it will be removed from at its * first occurrence. * @param f * @return true if the file was found and deleted. */ public boolean deleteFileInTree(FLFile f) { synchronized (_files) { int in = _files.indexOf(f); if (in != -1) { _files.remove(in); return true; } else { synchronized (_dirs) { for (FLDir d : _dirs) if (d.deleteFileInTree(f)) return true; } } return false; } } /** * @return List of FLFile. A new ArrayList is * created before returing. */ public List<FLFile> getFiles() { synchronized (_files) { return new ArrayList<FLFile>(_files); } } /** * @param hash * @param all If this is true then even FLFiles with share==false * will be searched and FLDir with isShared==false will be looked into. * @return */ public FLFile getFileInTreeByHash(String hash, boolean all) { synchronized (_files) { for (FLFile f : _files) if ((all || f.shared) && f.hash.equalsIgnoreCase(hash)) return f; } synchronized (_dirs) { for (FLDir d : _dirs) { if (!all && !d.isShared) continue; FLFile f = d.getFileInTreeByHash(hash, all); if (f != null) return f; } } return null; } /** * Recursively returns all files under this directory * and its sub-directories. * @return It will never be null. A new ArrayList is * created for returning. */ public List<FLFile> getAllFilesUnderTheTree() { synchronized (_files) { List<FLFile> files = new ArrayList<FLFile>(_files); synchronized (_dirs) { for (FLDir d : _dirs) { files.addAll(d.getAllFilesUnderTheTree()); } } return files; } } /** * Returns a reference to a FLDir in the * tree which is same as the given <i>d</i>. * @param d The FLDir to search for. * @return Reference to the similar FLDir from * anywhere inside the tree. */ public FLFile getFileInTree(FLFile f) { synchronized (_files) { int in = _files.indexOf(f); if (in != -1) return _files.get(in); else { FLFile rf = null; synchronized (_dirs) { for (FLDir D : _dirs) if ((rf = D.getFileInTree(f)) != null) return rf; } } return null; } } /** * @return All FLFiles whose <i>path</i> point to * non-existant files. It will never be null. */ public List<FLFile> getAllNonExistantFiles() { synchronized (_files) { List<FLFile> files = new ArrayList<FLFile>(); for (FLFile f : _files) if (!new File(f.path).exists()) files.add(f); synchronized (_dirs) { for (FLDir d : _dirs) files.addAll(d.getAllNonExistantFiles()); } return files; } } /** * @return true if this directory has atleast one file * directly in it. */ synchronized public boolean hasFile() { return _files.size() != 0; } /** * @return true if this directory has no sub-directories * or files. */ synchronized public boolean isEmpty() { return _files.size() != 0 && _dirs.size() != 0; } public boolean removeSubDir(FLDir d) { return _dirs.remove(d); } /** * Adds a sub-directory only if it didn't exist. * It will automatically set it parent if it isn't * set as immutable. * @param d The directory to add. * @return true of the directory didn't exist and now * it has been added, else false. */ public boolean addSubDir(FLDir d) { synchronized (_dirs) { if (_dirs.indexOf(d) != -1) return false; _dirs.add(d); d.setParent(this); return true; } } public void addSubDirs(List<FLDir> dirs) { _dirs.addAll(dirs); } /** * @return All immediate sub-directories of this directory in * a newly created ArrayList. */ public List<FLDir> getSubDirs() { synchronized (_dirs) { return new ArrayList<FLDir>(_dirs); } } /** * Name of the sub-directory to get. Note, only * immediate sub-directories are searched. * @param name The sub-directory's name. * @return Reference to that directory. null if its * not found. */ public FLDir getSubDir(String name) { synchronized (_dirs) { for (FLDir d : _dirs) if (d._name.equals(name)) return d; return null; } } /** * Deletes the sub-directory <i>d</i> that could be * at any depth in the tree. It is assumed * that a directory exists only once in the * tree hence it will be removed from at its * first occurence. * @param d * @return true if the directory was found and deleted. */ public boolean deleteSubDirInTree(FLDir d) { synchronized (_dirs) { int in = _dirs.indexOf(d); if (in != -1) { _dirs.remove(in); return true; } else { for (FLDir D : _dirs) if (D.deleteSubDirInTree(d)) return true; } return false; } } /** * Returns a reference to a FLDir in the * tree which is same as the given <i>d</i>. * @param d The FLDir to search for. * @return Reference to the similar FLDir from * anywhere inside the tree. */ public FLDir getDirInTree(FLDir d) { synchronized (_dirs) { int in = _dirs.indexOf(d); if (in != -1) return _dirs.get(in); else { FLDir rd = null; for (FLDir D : _dirs) if ((rd = D.getDirInTree(d)) != null) return rd; } return null; } } /** * Returns a FLFile or FLDir instance from * the tree that has matches the given virtual * path. * @param path The virtual path split around '/'. * The path must start with 'Root'. You * can use {@link #getDirNamesFromPath(String)} * to convert path to Vector<String>. * @param dirOnly If true then will look for FLDir only. * @return Returns null if no such node found. */ public FLInterface getChildInTree(List<String> path, boolean dirOnly) { if (path.size() == 0) return null; synchronized (_name) { if (!path.get(0).equals(_name)) return null; } if (path.size() == 1) return this; else { path.remove(0); synchronized (_files) { synchronized (_dirs) { String name = path.get(0); for (FLDir d : _dirs) if (d._name.equals(name)) return d.getChildInTree(path, dirOnly); if (dirOnly) return null; if (path.size() != 1) return null; for (FLFile f : _files) if (f.name.equals(name)) return f; return null; } } } } /** * Use this to split path to List<String>. The forward * slash at the beginning of the path is optional. * <p> * This method is re-entrant. * @param path * @return */ public static List<String> getDirNamesFromPath(String path) { String p[] = path.split("/"); List<String> v = new ArrayList<String>(); for (int i = path.startsWith("/") ? 1 : 0; i < p.length; i++) v.add(p[i]); return v; } /** * @param i * @return true if this directory has <i>i</i> * as its immediate file or sub-directory. */ public boolean hasChild(FLInterface i) { if (i instanceof FLDir) return _dirs.contains((FLDir) i); else if (i instanceof FLFile) return _files.contains((FLFile) i); else return false; } /** * Removes all directories in the whole * tree which are empty. */ public void pruneEmptyDirsFromTree() { synchronized (_dirs) { for (int i = 0; i < _dirs.size(); i++) { if (_dirs.get(i).isEmpty()) _dirs.remove(i); else _dirs.get(i).pruneEmptyDirsFromTree(); } } } /** * Deletes all FLDir and FLFiles in this * tree that are not shared. */ public void pruneUnsharedSharesInTree() { pruneUnsharedSharesInTree(isShared); } private void pruneUnsharedSharesInTree(boolean isShared) { synchronized (_files) { Iterator<FLFile> i = _files.iterator(); while (i.hasNext()) { FLFile f = i.next(); if (!f.shared || !isShared) i.remove(); } } synchronized (_dirs) { Iterator<FLDir> i = _dirs.iterator(); while (i.hasNext()) { FLDir d = i.next(); if (!d.isShared || !isShared) { d.pruneUnsharedSharesInTree(d.isShared && isShared); i.remove(); } } } } /** * You must remember that FLDir are * virtual directories and have no * physical significance hence * this method returns virtual * path of the directory.<br> * e.g.<br> * If A is the parent of B and A * is the topmost FLDir (i.e. it is * the root and hence has not parent), * then B.getDirPath() will return<br> *    <code>/A/B</code> * @return The virtual path of the FLDir. */ synchronized public String getDirPath() { String p = "/" + _name; if (_parent != null) p = _parent.getDirPath() + p; return p; } /** * Saves FLDir and all the sub-FLDirs and FLFiles in its tree to the given * OutputStream. The OutputStream will most probably be * FileOutputStream. * @param out The stream to which this should be saved. * @param object The FLDir object to save. * @throws IOException Ths is thrown if there is any error while writng to the stream. */ public static void saveObjectToStream(OutputStream out, FLDir object) throws IOException { ObjectOutputStream obj_out = new ObjectOutputStream(out); obj_out.writeObject(object); obj_out.close(); } /** * Reads FLDir and all the sub-FLDirs and FLFiles in its tree from the given * InputStream. The InputStream will most probably be FileInputStream. * @param in The stream from which to read. * @return A new instance of FLDir initialized form the data read from the stream. * @throws IOException Thrown when error occurs while reading form the stream. * @throws ClassNotFoundException Class of a serialized object cannot be found. * @throws InstantiationException The read object is not instance of FLDir. */ public static FLDir readObjectFromStream(InputStream in) throws IOException, ClassNotFoundException, InstantiationException { ObjectInputStream obj_in = new ObjectInputStream(in); Object obj = obj_in.readObject(); obj_in.close(); if (obj instanceof FLDir) { // Cast object to a FLDir return (FLDir) obj; } else throw new InstantiationException("The object read is not instance of FLDir."); } /** * Two FLDir are equal if their names and parents are equal. */ public boolean equals(Object o) { if (this == o) return true; if (o instanceof FLDir) { FLDir d = (FLDir) o; if (this._name.equals(d._name) && (this._parent == d._parent || this._parent.equals(d._parent))) return true; } return false; } synchronized public int hashCode() { int hashCode = 1; hashCode = hashCode * HASH_CONST + (_name == null ? 0 : _name.hashCode()); hashCode = hashCode * HASH_CONST + (_parent == null ? 0 : _parent.hashCode()); return hashCode; } synchronized public String toString() { return "Name: " + _name + ", Parent: " + (_parent == null ? "null" : _parent._name); } synchronized public String printTree() { return "* = Hidden" + "\n" + (isShared ? "" : "*") + _name + "\n" + printTree("|-", isShared); } private String printTree(String prefix, boolean isShared) { String tree = ""; for (FLFile f : _files) { tree = tree + prefix + (f.shared && isShared ? "" : "*") + f.name + "\n"; } for (FLDir d : _dirs) { tree = tree + prefix + (d.isShared && isShared ? "" : "*") + d._name + "\n"; tree = tree + d.printTree(prefix.replace('-', ' ') + "|-", isShared && d.isShared); } return tree; } /** * Returns the total size of * the tree of which this directory is * the root. * @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 The total size. */ public long getSize(boolean all) { long size = 0; synchronized (_files) { for (FLFile f : _files) size += all || f.shared ? f.size : 0; } synchronized (_dirs) { for (FLDir d : _dirs) size += all || d.isShared ? d.getSize(all) : 0; } return size; } }