package uc.files.filelist; import helpers.FilterLowerBytes; import helpers.GH; import helpers.Observable; import helpers.StatusObject; import helpers.StatusObject.ChangeType; import java.io.FileInputStream; import java.io.InputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PushbackInputStream; import java.io.UnsupportedEncodingException; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import javax.xml.XMLConstants; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import logger.LoggerFactory; import org.apache.log4j.Logger; import org.apache.tools.bzip2.CBZip2InputStream; import org.apache.tools.bzip2.CBZip2OutputStream; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.osgi.framework.Bundle; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXNotRecognizedException; import org.xml.sax.helpers.AttributesImpl; import uc.IUser; import uc.PI; import uc.crypto.HashValue; import uc.crypto.TigerHashValue; public class FileList extends Observable<StatusObject> implements Iterable<IFileListItem> { private static final Logger logger= LoggerFactory.make(); protected HashValue cid = null; protected String generator = "NONE"; protected Exception readProblem; protected final IUser usr; //the owner protected volatile boolean completed = false; private volatile long sharedSize; protected final FileListFolder root = new FileListFolder(this); private final Map<HashValue,FileListFile> contents = new HashMap<HashValue,FileListFile>(); private volatile byte[] filelistCashed; /** * creates an empty FileList for the specified user.. * @param usr - the user that owns the FileList */ public FileList(IUser usr){ this.usr = usr; } public boolean isCompleted(){ return completed; } /** * @return the root */ public FileListFolder getRoot() { return root; } /** * provides an iterator over all FileList Items * will first iterate over all Files then iterate over all * Folders */ public Iterator<IFileListItem> iterator() { return new Iterator<IFileListItem>() { private final Iterator<FileListFile> itFile = root.iterator(); private final Iterator<FileListFolder> itFolder = root.iterator2(); private Iterator<? extends IFileListItem> current= itFile; public boolean hasNext() { if (!current.hasNext()) { current = itFolder; } return current.hasNext(); } public IFileListItem next() { return current.next(); } public void remove() { current.remove(); } }; } public Iterable<FileListFile> getFileIterable() { return root; } public Iterable<FileListFolder> getFolderIterable() { return new Iterable<FileListFolder>() { @Override public Iterator<FileListFolder> iterator() { return root.iterator2(); } }; } void addedOrRemoved(boolean added,IFileListItem item) { if (item.isFile()) { if (added) { addFileToSharesizeIfNotPresent((FileListFile)item); } else { removeFileFromShareSize((FileListFile)item); } } FileListFolder parent = item.getParent(); notifyObservers( new StatusObject(item, added?ChangeType.ADDED:ChangeType.REMOVED, 0, parent)); while (parent.getParent() != null) { notifyObservers( new StatusObject(parent, ChangeType.CHANGED, 0, parent.getParent())); parent = parent.getParent(); } filelistCashed = null; } /** * @return the numberOfFiles */ public int getNumberOfFiles() { return root.getContainedFiles(); } private void addFileToSharesizeIfNotPresent(FileListFile f) { FileListFile old = null; if ((old = contents.put(f.getTTHRoot(),f)) == null) { sharedSize += f.getSize(); } else { contents.put(old.getTTHRoot(),old); } } private void removeFileFromShareSize(FileListFile f) { FileListFile old= null; if ((old = contents.remove(f.getTTHRoot())) == f) { sharedSize -= f.getSize(); } else { contents.put(old.getTTHRoot(),old); } } /** * @return the shared size */ public long getSharesize() { return sharedSize; } /** * reads in a filelist.. * @param path - the path where the filelist resides on the disc.. * @return true if successful .. false implies problem reading.. */ public boolean readFilelist(File path) { InputStream in = null; try { in = new FileInputStream(path); if (!path.toString().endsWith(".xml")) { in = new PushbackInputStream(in); int b = in.read(); if (b == 'B') { in.read(); //Z in = new CBZip2InputStream(in); } else { ((PushbackInputStream)in).unread(b); } } in = new FilterLowerBytes(in); //needed to capture bad FileLists.. readFilelist(in); } catch(Exception e){ logger.warn(e,e); readProblem = e; return false; } finally { GH.close(in); } return true; } public void readFilelist(InputStream in)throws IOException ,SAXException { InputStreamReader isr = null; try { setCompleted(false); //little workaround.. ignore bad input if Stream is directly used for parsing inserted it wouldn't isr = new InputStreamReader(in,"utf-8"); SAXParserFactory saxFactory = SAXParserFactory.newInstance(); try { SchemaFactory schFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); schFactory.setFeature("http://apache.org/xml/features/validation/schema-full-checking", false); Bundle bundle = Platform.getBundle(PI.PLUGIN_ID); Path path = new Path("XMLSchema/FilelistSchema.xml"); URL url = FileLocator.find(bundle, path, Collections.EMPTY_MAP); Schema schema = schFactory.newSchema(url); saxFactory.setSchema(schema); } catch(SAXNotRecognizedException snre) { logger.debug("Checking not supported "+snre, snre); } SAXParser saxParser = saxFactory.newSAXParser(); saxParser.parse(new InputSource(isr), new FileListParser(this)); } catch(ParserConfigurationException pce){ logger.error(pce,pce); } //calcSharesizeAndBuildTTHMap(); } /** * * @param out * @param path - "/" usually otherwise the path of the base from where Serialization should start.. * @param recursive * @throws UnsupportedEncodingException * @throws IOException * @throws IllegalArgumentException - if the path does no exist */ private void writeFilelist(OutputStream out,String path,boolean recursive) throws UnsupportedEncodingException , IOException , IllegalArgumentException { try { StreamResult streamResult = new StreamResult(out); SAXTransformerFactory tf = (SAXTransformerFactory) SAXTransformerFactory .newInstance(); // SAX2.0 ContentHandler. TransformerHandler hd = tf.newTransformerHandler(); Transformer serializer = hd.getTransformer(); serializer.setOutputProperty(OutputKeys.ENCODING, "utf-8"); //serializer.setOutputProperty(OutputKeys.INDENT, "yes"); serializer.setOutputProperty(OutputKeys.VERSION, "1.0"); serializer.setOutputProperty(OutputKeys.STANDALONE, "yes"); hd.setResult(streamResult); hd.startDocument(); AttributesImpl atts = new AttributesImpl(); // USERS tag. atts.addAttribute("", "", "Version", "CDATA", "1"); if (cid != null) { atts.addAttribute("", "", "CID", "CDATA", cid.toString()); } atts.addAttribute("", "", "Base", "CDATA", path); atts.addAttribute("", "", "Generator", "CDATA", generator); hd.startElement("", "", "FileListing", atts); FileListFolder parentFolder = root.getByPath(path.replace('/', File.separatorChar),true); if (parentFolder != null) { parentFolder.writeToXML(hd, atts,recursive,true,true); } else { logger.info("Path requested, but not found: "+path); } hd.endElement("", "", "FileListing"); hd.endDocument(); out.flush(); } catch(SAXException sax) { throw new IOException(sax); } catch (TransformerConfigurationException pce) { throw new IOException(pce); } } /** * writes filelist to a byte[] array * @param path subpath within the filelist / or null for whole filelist * @return */ public byte[] writeFileList(String path, boolean recursive,boolean bz2Compressed) { if (path == null) { path = "/"; } logger.debug("("+(path == null? "null":path)+")" ); // long start = System.currentTimeMillis(); boolean cacheable = path.equals("/") & bz2Compressed & recursive ; byte[] fileList = null; if (cacheable && filelistCashed != null) { return filelistCashed; } ByteArrayOutputStream baos = null; OutputStream out = null; try { baos = new ByteArrayOutputStream(); out = baos; if (bz2Compressed) { baos.write('B'); baos.write('Z'); out = new CBZip2OutputStream(baos); } writeFilelist(out,path,recursive); } catch(IOException ioe) { logger.warn(ioe,ioe); //this should never happen... everything is done in memory.. } finally { GH.close(out); } fileList = baos.toByteArray(); // long end = System.currentTimeMillis(); // logger.info("Time Filelist: " + (end-start)+" size: "+fileList.length+" recursive:"+recursive+" bz2: "+bz2Compressed+" path: "+path); if (cacheable) { filelistCashed = fileList; } return fileList; } // /** // * // * @param file - the target where the filelist should be written to.. // * @throws IOException - if some error occurs.. // * // */ // public void writeFilelist(File file) throws IOException { // FileOutputStream fos = null; // try { // fos = new FileOutputStream(file); // fos.write(writeFileList("/", true,true)); // fos.flush(); // } finally { // GH.close(fos); // } // } /** * deletes all FileLists in the FileList directory */ public static void deleteFilelists() { File f = PI.getFileListPath(); if (f.isDirectory()) { for (File filelist:f.listFiles()) { if (filelist.isFile() && filelist.getName().endsWith(".xml.bz2")) { if (!filelist.delete()) { filelist.deleteOnExit(); } } } } } // /** // * adds the owner of this FileList to all files // * that are in DownloadQueue as well as in this FileList.. // */ // public void match(DownloadQueue dq) { // dq.match(this); // } /** * searches for a file by its hashvalue * * @param value - TTH of the searched file * @return null if not found otherwise the found file */ public FileListFile search(HashValue value) { return contents.get(value); } /** * * @param onSearch - a regexp used to search in names of files and folders * @return the results containing of a list with folders and files */ public List<IFileListItem> search(Pattern onSearch) { List<IFileListItem> results = new ArrayList<IFileListItem>(); root.search(onSearch, results); return results; } /** * * @param onSearch - a substring to match * @return all files and folders which names match the provided substring */ public List<IFileListItem> search(String onSearch) { List<IFileListItem> found = search(Pattern.compile(Pattern.quote(onSearch))); if (onSearch.matches(TigerHashValue.TTHREGEX)) { FileListFile f = search(HashValue.createHash(onSearch)); if (f != null) { found.add(f); } } return found; } /** * @return the usr */ public IUser getUsr() { return usr; } /** * @return the cID */ public HashValue getCID() { return cid; } /** * @param cid the cID to set */ public void setCID(HashValue cid) { this.cid = cid; } /** * @return the generator */ public String getGenerator() { return generator; } /** * @param generator the generator to set */ public void setGenerator(String generator) { this.generator = generator; } /** * @param completed the completed to set */ public void setCompleted(boolean completed) { this.completed = completed; } public boolean deepEquals(FileList f) { return usr.equals(f.usr) && root.deepEquals(f.root); } }