package edu.washington.cs.oneswarm.watchdir; import java.beans.XMLDecoder; import java.beans.XMLEncoder; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.UnsupportedEncodingException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.logging.LogManager; import java.util.logging.Logger; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.config.ParameterListener; import org.gudy.azureus2.core3.config.StringList; import org.gudy.azureus2.core3.config.impl.StringListImpl; import org.gudy.azureus2.core3.disk.DiskManagerFileInfo; import org.gudy.azureus2.core3.download.DownloadManager; import org.gudy.azureus2.core3.download.DownloadManagerListener; import org.gudy.azureus2.core3.download.DownloadManagerState; import org.gudy.azureus2.core3.global.GlobalManager; import org.gudy.azureus2.core3.global.GlobalManagerListener; import org.gudy.azureus2.core3.internat.LocaleTorrentUtil; import org.gudy.azureus2.core3.internat.LocaleUtilEncodingException; import org.gudy.azureus2.core3.torrent.TOTorrent; import org.gudy.azureus2.core3.torrent.TOTorrentCreator; import org.gudy.azureus2.core3.torrent.TOTorrentException; import org.gudy.azureus2.core3.torrent.TOTorrentFactory; import org.gudy.azureus2.core3.torrent.TOTorrentFile; import org.gudy.azureus2.core3.torrent.TOTorrentProgressListener; import org.gudy.azureus2.core3.util.Base32; import org.gudy.azureus2.core3.util.ByteFormatter; import org.gudy.azureus2.core3.util.HashWrapper; import org.gudy.azureus2.core3.util.SystemProperties; import org.gudy.azureus2.plugins.PluginInterface; import org.gudy.azureus2.plugins.ipc.IPCInterface; import org.gudy.azureus2.plugins.torrent.TorrentException; import org.jaudiotagger.audio.AudioFile; import org.jaudiotagger.audio.AudioFileIO; import org.jaudiotagger.audio.AudioHeader; import org.jaudiotagger.tag.Tag; import com.aelitis.azureus.core.AzureusCore; import com.aelitis.azureus.core.AzureusCoreComponent; import com.aelitis.azureus.core.AzureusCoreException; import com.aelitis.azureus.core.AzureusCoreLifecycleListener; import com.aelitis.azureus.core.impl.AzureusCoreImpl; import edu.washington.cs.oneswarm.f2f.FileCollection; import edu.washington.cs.oneswarm.f2f.permissions.GroupBean; import edu.washington.cs.oneswarm.f2f.permissions.PermissionsDAO; import edu.washington.cs.oneswarm.ui.gwt.BackendErrorLog; import edu.washington.cs.oneswarm.ui.gwt.CoreInterface; import edu.washington.cs.oneswarm.ui.gwt.client.newui.FileTypeFilter; import edu.washington.cs.oneswarm.ui.gwt.client.newui.settings.MagicPath; import edu.washington.cs.oneswarm.ui.gwt.client.newui.settings.MagicPathParseException; import edu.washington.cs.oneswarm.ui.gwt.client.newui.settings.MagicWatchType; import edu.washington.cs.oneswarm.ui.gwt.rpc.OneSwarmConstants.InOrderType; import edu.washington.cs.oneswarm.ui.gwt.server.BackendTaskManager; import edu.washington.cs.oneswarm.ui.gwt.server.PreviewImageGenerator; import edu.washington.cs.oneswarm.ui.gwt.server.BackendTaskManager.CancellationListener; import edu.washington.cs.oneswarm.ui.gwt.server.ffmpeg.FFMpegAsyncOperationManager; import edu.washington.cs.oneswarm.ui.gwt.server.ffmpeg.FFMpegAsyncOperationManager.DataNotAvailableException; public final class MagicDirectoryManager extends Thread implements ParameterListener, DirectoryWatcherListener { private static Logger logger = Logger.getLogger(MagicDirectoryManager.class.getName()); // HACK! this is from OneSwarmConstants, but isdal has build problems using // it there. public final static String BITTORRENT_MAGNET_PREFIX = "urn_btih_"; static volatile MagicDirectoryManager mInstance = null; StringList watch_dirs = null; class FileChange { public FileChange(UpdatingFileTree tree, MagicWatchType type, File baseDir) { this.tree = tree; this.type = type; this.baseDir = baseDir; } public UpdatingFileTree tree; public MagicWatchType type; public File baseDir; } LinkedBlockingQueue<FileChange> additionsToProcess = new LinkedBlockingQueue<FileChange>(); LinkedBlockingQueue<FileChange> removalsToProcess = new LinkedBlockingQueue<FileChange>(); final List<String> mExclusions = Collections.synchronizedList(new ArrayList<String>()); Map<String, DirectoryWatcher> watchers = new HashMap<String, DirectoryWatcher>(); /** * Used to avoid the CPU overhead of deciding torrents for unchanged * directory trees. */ Set<Long> previouslyCheckedTrees = new HashSet<Long>(); boolean stopping = false; boolean stopWatching = false; private long nextSyncTime; private MagicDirectoryManager() { logger.fine("MagicDirectoryManager()"); watch_dirs = COConfigurationManager.getStringListParameter("Magic Watch Directories"); if (watch_dirs == null) { watch_dirs = new StringListImpl(); } for (int i = 0; i < watch_dirs.size(); i++) { logger.info(watch_dirs.get(i) + " wdir" + " " + watch_dirs.get(i).getClass().getName()); } load_exclusions(); AzureusCoreImpl.getSingleton().addLifecycleListener(new AzureusCoreLifecycleListener() { public void componentCreated(AzureusCore core, AzureusCoreComponent component) { } public boolean restartRequested(AzureusCore core) throws AzureusCoreException { return true; } public void started(AzureusCore core) { } public boolean stopRequested(AzureusCore core) throws AzureusCoreException { stopping = true; return true; } public void stopped(AzureusCore core) { logger.fine("got stopped"); stopping = true; } public void stopping(AzureusCore core) { logger.fine("got stopping"); stopping = true; } public boolean syncInvokeRequired() { return false; } }); // DEBUG // watch_dirs.add("/tmp/test/"); COConfigurationManager.addParameterListener("Magic Watch Directories", this); setName("Magic directory scanner"); setDaemon(true); start(); } private void load_exclusions() { if (AzureusCoreImpl.isCoreAvailable() == false) { logger.warning("couldn't load exclusions: core not available yet!"); return; } if (AzureusCoreImpl.getSingleton().isStarted() == false) { logger.warning("couldn't load exclusions: core not started yet!"); return; } for (DownloadManager dm : (List<DownloadManager>) AzureusCoreImpl.getSingleton() .getGlobalManager().getDownloadManagers()) { mExclusions.add(dm.getSaveLocation().getAbsolutePath()); } AzureusCoreImpl.getSingleton().getGlobalManager().addListener(new GlobalManagerListener() { public void destroyInitiated() { } public void destroyed() { } public void downloadManagerAdded(DownloadManager dm) { mExclusions.add(dm.getSaveLocation().getAbsolutePath()); } public void downloadManagerRemoved(DownloadManager dm) { } public void seedingStatusChanged(boolean seeding_only_mode) { } }); synchronized (mExclusions) { for (String s : mExclusions) { logger.finest("exclusion: " + s); } } } public static MagicDirectoryManager get() { if (mInstance == null) { mInstance = new MagicDirectoryManager(); } return mInstance; } public void parameterChanged(String parameterName) { logger.finer("param changed: " + parameterName); if (parameterName.equals("Magic Watch Directories")) { synchronized (watch_dirs) { watch_dirs = COConfigurationManager .getStringListParameter("Magic Watch Directories"); if (watch_dirs == null) { watch_dirs = new StringListImpl(); } } logger.finer("magic watch dirs param changed, calling sync"); // take this as a signal that the user wants to try again stopWatching = false; // not strictly necessary, but makes testing easier (and possibly // avoids weird bugs) previouslyCheckedTrees.clear(); nextSyncTime = System.currentTimeMillis(); } } /** * We used to be much more proactive here -- maintaining file tree info and * periodically refreshing it. This used a lot of memory for large directory * trees, so now we simply refresh all watch directories periodically. */ private void sync() { logger.fine("sync()"); synchronized (watch_dirs) { long startSyncTime = System.currentTimeMillis(); /** * Since we are updating now, just remove all existing ones and * create new ones. */ for (DirectoryWatcher existing : watchers.values()) { existing.setDone(); } watchers.clear(); /** * We may have built up a backlog of things to hash -- these will * regenerate once we do the initial scan of the newly created watch * directories */ additionsToProcess.clear(); /** * first pass: anything to add */ logger.fine(watch_dirs.size() + " watchdirs"); for (int i = 0; i < watch_dirs.size(); i++) { MagicPath magic = null; try { if (watch_dirs.get(i) == null) { continue; } if (watch_dirs.get(i).length() == 0) { continue; } magic = new MagicPath(watch_dirs.get(i)); } catch (MagicPathParseException e) { logger.warning(e.toString() + " on " + watch_dirs.get(i)); watch_dirs = new StringListImpl(); break; } String path = magic.getPath(); if (watchers.containsKey(path) == false && !alreadyWatched(path)) { try { logger.fine("new directoryWatcher: " + path); DirectoryWatcher dw = new DirectoryWatcher(magic, 60); // dw.start(); additionsToProcess.put(new FileChange(dw.mTree, magic.getType(), new File( magic.getPath()))); // dw.addListener(this); // watchers.put(path, dw); } catch (Exception e) { e.printStackTrace(); } } } int preferredIntervalMinutes = 0; try { preferredIntervalMinutes = COConfigurationManager .getIntParameter("oneswarm.watchdir.refresh.interval"); } catch (Exception e) { e.printStackTrace(); } long duration = System.currentTimeMillis() - startSyncTime; long interval = 120 * 1000; // if( duration < 1000 ) { // interval = 30*1000; // } else { // huge scan time! // at least once/hour, 10X the scan time, but no less than 60 // seconds interval = (long) Math.min(Math.max(120 * 1000, 20 * duration), 60 * 60 * 1000); interval = Math.max(120 * 1000, interval); // } if (preferredIntervalMinutes > 0) { logger.finest("Using provided setting for watch directory refresh interval: " + interval); interval = preferredIntervalMinutes * 60 * 1000; } nextSyncTime = System.currentTimeMillis() + interval; logger.fine("Watch directories sync took: " + duration + " (wall) next in: " + interval + " (" + new Date(nextSyncTime) + ")"); } } private boolean alreadyWatched(String path) { synchronized (watch_dirs) { for (String comp : watchers.keySet()) { if (path.startsWith(comp)) return true; } } return false; } public void run() { // long last_audio_bind = System.currentTimeMillis() + (120 * 1000); int errors = 0; /** * Sleep for a bit during start to speed up UI loading tasks -- this * doesn't need to happen immediately */ try { Thread.sleep(30 * 1000); } catch (Exception e) { e.printStackTrace(); } sync(); // try { // if( // COConfigurationManager.getBooleanParameter("oneswarm.v06.firstrun") ) // { // COConfigurationManager.setParameter("oneswarm.v06.firstrun", false); // logger.info("First run of 0.6, trying to auto-add tags for audio"); // autotag_existing_audio(); // } // } catch( Exception e ) { // e.printStackTrace(); // } while (!stopping) { try { if (stopWatching) { Thread.sleep(10 * 1000); continue; } try { process_additions(); // we'll no longer have any removals now that we need to do // full scans to avoid memory usage // process_removals(); if (System.currentTimeMillis() > nextSyncTime) { sync(); } else { Thread.sleep(1000); } } catch (TOTorrentException e) { if (e.getReason() == TOTorrentException.RT_CANCELLED) { ; // this is okay actually -- skips just this one. } else { throw e; } } } catch (Exception e) { BackendErrorLog.get().logException(e); errors++; if (errors > 3) { for (DirectoryWatcher dw : watchers.values()) { dw.removeListener(this); dw.setDone(); } watchers.clear(); BackendErrorLog .get() .logString( "Previous errors have caused OneSwarm to stop monitoring current watch directories."); stopWatching = true; } try { Thread.sleep(1 * 1000); } catch (Exception e2) { } } } } static class MutableBoolean { public MutableBoolean(boolean v) { val = v; } public boolean val; } public static void autotag_existing_audio() { final MutableBoolean stopIt = new MutableBoolean(false); int ourID = BackendTaskManager.get().createTask("Auto-tagging audio files...", new CancellationListener() { public void cancelled(int inID) { synchronized (stopIt) { stopIt.val = true; } } }); List<DownloadManager> managers = (List<DownloadManager>) AzureusCoreImpl.getSingleton() .getGlobalManager().getDownloadManagers(); for (int i = 0; i < managers.size(); i++) { DownloadManager dm = managers.get(i); BackendTaskManager.get().getTask(ourID) .setProgress(Math.round(((double) i / (double) managers.size()) * 100.0) + "%"); if (dm.getDownloadState() == null) { logger.warning("null download state for: " + dm.getDisplayName()); continue; } synchronized (stopIt) { if (stopIt.val == true) { break; } } if (dm.getDownloadState().getAttribute(FileCollection.ONESWARM_TAGS_ATTRIBUTE) == null) { bind_audio_xml(dm, true); } // if no current tags } BackendTaskManager.get().removeTask(ourID); } public static String bind_audio_scan() { long start = System.currentTimeMillis(); StringBuilder out = new StringBuilder(); out.append("bind audio scan..."); String metainfoPath = SystemProperties.getMetaInfoPath(); UpdatingFileTree metainfo = new UpdatingFileTree(new File(metainfoPath)); List<UpdatingFileTree> q = new ArrayList<UpdatingFileTree>(); q.add(metainfo); while (q.isEmpty() == false) { UpdatingFileTree curr = q.remove(0); if (curr.isDirectory()) { q.addAll(curr.getChildren()); } else if (curr.getThisFile().getName().equals(PreviewImageGenerator.AUDIO_INFO_FILE)) { String hashStr = curr.getThisFile().getParentFile().getName(); byte[] hashBytes = Base32.decode(hashStr); DownloadManager dm = AzureusCoreImpl.getSingleton().getGlobalManager() .getDownloadManager(new HashWrapper(hashBytes)); // old metainfo and/or bogus directory name if (dm == null) { continue; } logger.finest("rebind audio considering: " + dm.getDisplayName()); out.append("considered: " + dm.getDisplayName()); if (dm.getDownloadState().getAttribute(FileCollection.ONESWARM_ARTIST_ATTRIBUTE) == null && dm.getDownloadState().getAttribute( FileCollection.ONESWARM_ALBUM_ATTRIBUTE) == null) { logger.finer("should rebind audio " + dm.getDisplayName()); out.append("rebinding for: " + dm.getDisplayName()); bind_audio_xml(dm); } } } String str = "binding audio scan took: " + (System.currentTimeMillis() - start); logger.info(str); out.append(str); return out.toString(); } private void process_removals() throws Exception { FileChange change = removalsToProcess.poll(1, TimeUnit.SECONDS); if (change == null) { return; } logger.fine("process removals: " + change.tree); String comp_path = change.tree.thisFile.getAbsolutePath(); List<DownloadManager> toRemove = new ArrayList<DownloadManager>(); GlobalManager gm = AzureusCoreImpl.getSingleton().getGlobalManager(); for (DownloadManager dm : (List<DownloadManager>) gm.getDownloadManagers()) { if (dm.getSaveLocation().getAbsolutePath().startsWith(comp_path)) { logger.fine("saveLocation.startsWith() delete of: " + dm.getSaveLocation().getAbsolutePath() + " / " + comp_path); toRemove.add(dm); } else if (comp_path.startsWith(dm.getSaveLocation().getAbsolutePath())) // deleted // a // file // in // the // torrent // directory // we // created, // need // to // remove // (and // possibly // rehash) { logger.fine("comp_path.startsWith delete of: " + dm.getSaveLocation().getAbsolutePath() + " / " + comp_path); toRemove.add(dm); } } for (DownloadManager dm : toRemove) { logger.info("delete causes removal of: " + dm.getTorrentFileName()); try { gm.removeDownloadManager(dm, true, false); // remove torrent // file (which we // created), but not // data } catch (Exception e) { e.printStackTrace(); } } } private void process_additions() throws Exception { FileChange change = null; change = additionsToProcess.poll(); if (change == null) { return; } if (change.tree == null) { logger.warning("Change with null file tree!"); return; } logger.fine("dequeued: " + change.tree.thisFile.getAbsoluteFile() + " lastmod: " + change.tree.thisFile.lastModified() + " now " + System.currentTimeMillis()); if (MagicDecider.isExcluded(mExclusions, change.tree)) { logger.fine(change.tree.thisFile.getAbsoluteFile() + " excluded"); return; } boolean shouldExcludeFirstLevel = false; for (int i = 0; i < this.watch_dirs.size(); i++) { if ((new File(watch_dirs.get(i))).equals(change.tree.getThisFile())) { shouldExcludeFirstLevel = true; logger.finest("excluding first level based on watchdir match: " + watch_dirs.get(i)); break; } } logger.fine("should exclude first level?: " + shouldExcludeFirstLevel); Long digest = new Long(change.tree.modifiedChecksum()); if (previouslyCheckedTrees.contains(digest)) { logger.fine("Skipping decideTorrents() for tree: " + change.baseDir.getAbsolutePath() + " as modified checksum matches (" + digest + ")"); return; } List<File> torrents = MagicDecider.decideTorrents(change.tree, change.type, mExclusions, watch_dirs); previouslyCheckedTrees.add(digest); logger.fine("got " + torrents.size() + " from " + change.tree.thisFile.getName() + " digest: " + digest); logger.finer("digests has: " + previouslyCheckedTrees.size()); for (File f : torrents) { mExclusions.add(f.getAbsolutePath()); } for (File p : torrents) { // perhaps moved since we scanned it. if (p.exists() && (p.length() > 0 || p.isDirectory())) { logger.info("create: " + p.getAbsolutePath()); String tag = computeTags(change.baseDir.getParent(), p.getPath()); String[] tags = null; if (tag != null) { tags = new String[] { tag }; } if (COConfigurationManager.getBooleanParameter("oneswarm.directory.tags") == false) { tags = null; } create_swarm_synchronously(p, tags); logger.fine("done create"); } else { logger.fine("Skipping create of: " + p.getAbsolutePath() + " exists? " + (p.exists()) + " len? " + p.length()); } } } boolean cancelled = false; private void create_swarm_synchronously(final File file, String[] tags) throws Exception { /** * Trying to create these causes a TOTorrentException */ if (file.isDirectory() == false) { if (file.length() == 0) { return; } } TOTorrentCreator currentCreator = null; cancelled = false; try { currentCreator = TOTorrentFactory.createFromFileOrDirWithComputedPieceLength(file, new URL("http://tracker.invalid/announce"), true); } catch (java.net.MalformedURLException e) { throw new TOTorrentException("malformed tracker url (should _never_ happen)", 0); } final TOTorrentCreator creator_shadow = currentCreator; final BackendTaskManager tasks = BackendTaskManager.get(); final int task_id = tasks.createTask("Hashing...", new CancellationListener() { public void cancelled(int inID) { mExclusions.add(file.getAbsolutePath()); creator_shadow.cancel(); } }); tasks.getTask(task_id).setSummary("Watch directory hash: " + file.getName()); currentCreator.addListener(new TOTorrentProgressListener() { public void reportCurrentTask(String task_description) { System.out.println("creating: " + task_description); } public void reportProgress(int percent_complete) { if ((percent_complete % 10) == 0) { logger.fine("progress: " + percent_complete); } if (tasks.getTask(task_id) != null) { tasks.getTask(task_id).setProgress(percent_complete + "%"); } if (stopping && !cancelled) { creator_shadow.cancel(); cancelled = true; } } }); TOTorrent created = null; try { created = currentCreator.create(); } catch (TOTorrentException e) { if (e.getReason() == TOTorrentException.RT_ZERO_LENGTH) { logger.warning("Skipping creation of zero-length swarm: " + file.getAbsolutePath()); return; } throw e; } logger.finer("create finished, removing task_id: " + task_id); tasks.removeTask(task_id); if (created == null || cancelled) { System.err.println("created == null, canceled?"); return; } String configSavePath = COConfigurationManager .getStringParameter("General_sDefaultTorrent_Directory"); File outTorrent = null; if (configSavePath == null) { outTorrent = new File(file.getParentFile().getAbsolutePath(), file.getName() + ".torrent"); } else { outTorrent = new File(configSavePath, file.getName() + ".torrent"); } logger.finer("saving to: " + outTorrent.getAbsolutePath()); try { LocaleTorrentUtil.setDefaultTorrentEncoding(created); } catch (LocaleUtilEncodingException e1) { e1.printStackTrace(); } logger.finer("setdefaultencoding, serializing..."); created.serialiseToBEncodedFile(outTorrent); logger.finest("done that"); /** * very small chance of this happening -- most of the time the quit will * come during the hashing (which will cancel it, which will result in * null and immediate return) */ if (!stopping) { generate_preview_for_torrent(created, file); logger.finer("settings perms"); ArrayList<GroupBean> typed = new ArrayList<GroupBean>(); typed.add(GroupBean.ALL_FRIENDS); PermissionsDAO.get().setGroupsForHash(ByteFormatter.encodeString(created.getHash()), typed, true); /** * Finally add that swarm and make sure the permissions are f2f only */ GlobalManager gm = AzureusCoreImpl.getSingleton().getGlobalManager(); logger.finer("calling add download manager, file: " + file.getAbsolutePath() + " save: " + file.getParentFile().getAbsolutePath()); try { final DownloadManager dm = gm.addDownloadManager(outTorrent.getAbsolutePath(), created.getHash(), file.getAbsolutePath(), org.gudy.azureus2.core3.download.DownloadManager.STATE_WAITING, true, true, null); if (tags != null) { DownloadManagerState dmState = dm.getDownloadState(); if (dmState != null) { dm.getDownloadState().setListAttribute( FileCollection.ONESWARM_TAGS_ATTRIBUTE, tags); logger.finer("set tags: " + tags.length + " first: " + tags[0]); } } dm.addListener(new DownloadManagerListener() { public void completionChanged(DownloadManager manager, boolean completed) { } public void downloadComplete(DownloadManager manager) { } public void filePriorityChanged(DownloadManager download, DiskManagerFileInfo file) { } public void positionChanged(DownloadManager download, int oldPosition, int newPosition) { } public void stateChanged(DownloadManager manager, int state) { if (state == org.gudy.azureus2.core3.download.DownloadManager.STATE_SEEDING) { logger.fine("binding audio data for: " + dm.getDisplayName()); MagicDirectoryManager.bind_audio_xml(dm); dm.stopIt( org.gudy.azureus2.core3.download.DownloadManager.STATE_STOPPED, false, false); dm.removeListener(this); } } }); dm.setForceStart(true); } catch (Exception e) { logger.warning(e.toString()); e.printStackTrace(); throw e; } // logger.finest("force start"); // dm.setForceStart(true); } else { logger.finer("was stopping"); } // coreInterface.getF2FInterface().setTorrentPrivacy(dm.getTorrent().getHash(), // false, true); // start doesn't matter here, f2f should start it automatically if // there's a request } public static void generate_preview_for_torrent(TOTorrent created, File file) throws TOTorrentException { logger.finer("generating preview..2."); try { File largestFile = null; long largest = 0; for (TOTorrentFile f : created.getFiles()) { if (f == null) { continue; } if (InOrderType.getType(f.getRelativePath()) == null) { continue; } if (f.getLength() > largest) { largest = f.getLength(); if (file.isDirectory() == false) { largestFile = new File(file.getParent(), f.getRelativePath()); } else { largestFile = new File(file, f.getRelativePath()); } } } if (largestFile != null) { logger.finer("largest is: " + largestFile.getAbsolutePath()); } try { FFMpegAsyncOperationManager.getInstance().getPreviewImage(created.getHash(), largestFile, 10, TimeUnit.SECONDS); } catch (TorrentException e) { // this should never happen... e.printStackTrace(); } catch (DataNotAvailableException e) { logger.finest("unable to create preview for file: " + largest); } } catch (NullPointerException e) { logger.warning("Preview generation null pointer: " + e.toString()); e.printStackTrace(); } } // public static boolean generate_audio_preview( File largestFile, File // imageFile ) { // try { // System.out.println(largestFile.getAbsolutePath()); // AudioFile f = AudioFileIO.read(largestFile); // logger.finest("read audio file"); // byte [] binaryData = f.getTag().getFirstArtwork().getBinaryData(); // // try { // FFMpegTools.writeTransformedImage(new ByteArrayInputStream(binaryData), // imageFile, false); // logger.fine("wrote preview from " + largestFile.getName() + " to " + // imageFile.getAbsolutePath()); // return true; // } catch( Exception e ) { // logger.warning("error writing out preview: " + e.toString()); // throw e; // } // } catch( Exception e ) { // logger.warning("error reading audio tags during preview generation: " + // e.toString()); // } // // return false; // } public static boolean generate_audio_info_xml(File saveLocation, TOTorrent inTorrent, File metaFile) { Map<String, Properties> audio_file_properties = new HashMap<String, Properties>(); if (inTorrent.isSimpleTorrent()) { Properties p = new Properties(); InOrderType type = InOrderType.getType(saveLocation.getName()); if (type != null) { if (type.getFileTypeFilter().equals(FileTypeFilter.Audio)) { try { AudioFile f = AudioFileIO.read(saveLocation); Tag tag = f.getTag(); if (tag != null) { AudioHeader audioHeader = f.getAudioHeader(); setPropsFromTagAndHeader(p, audioHeader, tag); } if (p.size() > 0) { audio_file_properties.put(saveLocation.getName(), p); } } catch (Exception e) { System.err.println("audio tag parse error: " + e.toString()); } } } } else { for (TOTorrentFile torrent_file : inTorrent.getFiles()) { Properties p = new Properties(); audio_file_properties.put(torrent_file.getRelativePath(), p); InOrderType type = InOrderType.getType(torrent_file.getRelativePath()); if (type != null) { if (type.getFileTypeFilter().equals(FileTypeFilter.Audio)) { try { File file = new File(saveLocation, torrent_file.getRelativePath()); AudioFile f = AudioFileIO.read(file); Tag tag = f.getTag(); if (tag != null) { AudioHeader audioHeader = f.getAudioHeader(); setPropsFromTagAndHeader(p, audioHeader, tag); if (p.size() > 0) { audio_file_properties.put(saveLocation.getName(), p); } } } catch (Exception e) { System.err.println("audio tag parse error: " + e.toString()); } } // if it's an audio type } // if this file has a recognizable type } // for over torrent files } if (audio_file_properties.size() > 0) { try { XMLEncoder encoder = new XMLEncoder(new BufferedOutputStream(new FileOutputStream( metaFile))); encoder.writeObject(audio_file_properties); encoder.close(); logger.fine("wrote audio properties xml for: " + (new String(inTorrent.getName(), "UTF-8"))); } catch (Exception e) { try { logger.warning("error writing audio properties for: " + new String(inTorrent.getName(), "UTF-8") + " / " + e.toString()); } catch (UnsupportedEncodingException e1) { e1.printStackTrace(); } e.printStackTrace(); return false; } return true; } return false; } /** * TODO: all this code needs massively refactored (put all the extra xml * generating info functions in the same place) */ public static void bind_audio_xml(DownloadManager real_dl) { bind_audio_xml(real_dl, false); } public static void bind_audio_xml(DownloadManager real_dl, boolean force) { try { /** * Don't do this multiple times. */ if (real_dl.getDownloadState() != null && force == false) { if (real_dl.getDownloadState() .getAttribute(FileCollection.ONESWARM_ALBUM_ATTRIBUTE) != null) { return; } if (real_dl.getDownloadState().getAttribute( FileCollection.ONESWARM_ARTIST_ATTRIBUTE) != null) { return; } } File metaInfoDir = CoreInterface.getMetaInfoDir(real_dl.getTorrent().getHash()); File audio_xml = new File(metaInfoDir, PreviewImageGenerator.AUDIO_INFO_FILE); if (audio_xml.exists() == false) { logger.finest("no xml data for: " + real_dl.getDisplayName() + " / skipping audio info bind"); return; } XMLDecoder decoder = new XMLDecoder(new BufferedInputStream(new FileInputStream( audio_xml))); Map<String, Properties> audio_file_properties = (Map<String, Properties>) decoder .readObject(); decoder.close(); boolean setArtist = false, setAlbum = false; for (Properties p : audio_file_properties.values()) { for (String attrib : new String[] { FileCollection.ONESWARM_ARTIST_ATTRIBUTE }) { if (p.getProperty(attrib) != null) { real_dl.getDownloadState().setAttribute( FileCollection.ONESWARM_ARTIST_ATTRIBUTE, p.getProperty(attrib)); logger.fine("bound artist: " + p.getProperty(attrib) + " for " + real_dl.getDisplayName()); setArtist = true; if (COConfigurationManager.getBooleanParameter("oneswarm.add.id3.tags")) { logger.fine("adding id3 tag for album: " + p.getProperty(attrib).replaceAll("/", "-")); String[] tags = real_dl.getDownloadState().getListAttribute( FileCollection.ONESWARM_TAGS_ATTRIBUTE); if (tags == null) { tags = new String[0]; } String[] neu = new String[tags.length + 1]; System.arraycopy(tags, 0, neu, 0, tags.length); neu[tags.length] = "Artists/" + p.getProperty(attrib).replaceAll("/", "-"); real_dl.getDownloadState().setListAttribute( FileCollection.ONESWARM_TAGS_ATTRIBUTE, neu); } break; } } for (String attrib : new String[] { FileCollection.ONESWARM_ALBUM_ATTRIBUTE }) // to // deal // with // old // versions { if (p.getProperty(attrib) != null) { real_dl.getDownloadState().setAttribute( FileCollection.ONESWARM_ALBUM_ATTRIBUTE, p.getProperty(attrib)); logger.fine("bound album: " + p.getProperty(attrib) + " for " + real_dl.getDisplayName()); setAlbum = true; if (COConfigurationManager.getBooleanParameter("oneswarm.add.id3.tags")) { logger.fine("adding id3 tag for album: " + p.getProperty(attrib).replaceAll("/", "-")); String[] tags = real_dl.getDownloadState().getListAttribute( FileCollection.ONESWARM_TAGS_ATTRIBUTE); if (tags == null) { tags = new String[0]; } String[] neu = new String[tags.length + 1]; System.arraycopy(tags, 0, neu, 0, tags.length); neu[tags.length] = "Albums/" + p.getProperty(attrib).replaceAll("/", "-"); real_dl.getDownloadState().setListAttribute( FileCollection.ONESWARM_TAGS_ATTRIBUTE, neu); } break; } } if (setArtist && setAlbum) { break; } } /** * Need to regenerate our file list to incorporate this new info */ if (setArtist || setAlbum) { PluginInterface f2fIf = AzureusCoreImpl.getSingleton().getPluginManager() .getPluginInterfaceByID("osf2f"); if (f2fIf != null) { if (f2fIf.isOperational() == true) { IPCInterface ipc = f2fIf.getIPC(); if (ipc != null) { ipc.invoke("refreshFileLists", new Object[0]); } else { logger.warning("f2f IPC is null, couldn't regenerate file list after audio binding"); } } else { logger.warning("Couldn't regenerate file list after audio info binding, f2f plugin is not operational"); } } else { logger.warning("Couldn't regenerate file list after audio info binding, f2f plugin interface is null"); } } } catch (Exception e) { logger.warning("error binding audio xml to download manager: " + real_dl.getDisplayName() + " / " + e.toString()); } } private static void setPropsFromTagAndHeader(Properties p, AudioHeader audioHeader, Tag tag) { String artist = tag.getFirstArtist(); if (artist != null) { p.setProperty(FileCollection.ONESWARM_ARTIST_ATTRIBUTE, tag.getFirstArtist()); } String album = tag.getFirstAlbum(); if (album != null) { p.setProperty(FileCollection.ONESWARM_ALBUM_ATTRIBUTE, album); } String firstYear = tag.getFirstYear(); if (firstYear != null) { p.setProperty("year", firstYear); } String firstGenre = tag.getFirstGenre(); if (firstGenre != null) { p.setProperty("genre", firstGenre); } String firstTitle = tag.getFirstTitle(); if (firstTitle != null) { p.setProperty("title", firstTitle); } if (audioHeader != null) { int trackLength = audioHeader.getTrackLength(); p.setProperty("length", Integer.toString(trackLength)); String format = audioHeader.getFormat(); if (format != null) { p.setProperty("format", format); } long bitRateAsNumber = audioHeader.getBitRateAsNumber(); p.setProperty("bitrate", Long.toString(bitRateAsNumber)); String encodingType = audioHeader.getEncodingType(); if (encodingType != null) { p.setProperty("encoding", encodingType); } } } public void deleteFileObserved(DirectoryWatcher watcher, UpdatingFileTree inAbsolutePath) { logger.finest("delete observed: " + inAbsolutePath.thisFile.getAbsolutePath()); try { removalsToProcess.put(new FileChange(inAbsolutePath, watcher.getWatchType(), new File( watcher.getPath()))); } catch (InterruptedException e) { e.printStackTrace(); } } public void newFileObserved(DirectoryWatcher watcher, UpdatingFileTree inAbsolutePath) { logger.finest("change event: " + inAbsolutePath.thisFile.getAbsolutePath()); /** * TODO: optimization: use prefix tree */ synchronized (mExclusions) { for (String pre : mExclusions) { if (inAbsolutePath.thisFile.getAbsolutePath().startsWith(pre)) { // System.out.println("excluded by " + pre); return; } } } try { additionsToProcess.put(new FileChange(inAbsolutePath, watcher.getWatchType(), new File( watcher.getPath()))); logger.finer("put into to check out q: " + inAbsolutePath.thisFile.getAbsolutePath()); } catch (Exception e) { e.printStackTrace(); } } public static final void main(String[] args) throws Exception { LogManager.getLogManager().readConfiguration(new FileInputStream("./logging.properties")); UpdatingFileTree tree = new UpdatingFileTree(new File("/Volumes/x/watch_test"), new UpdatingFileTreeListener() { public void broadcastChange(UpdatingFileTree path, boolean isDelete) { System.out.println("change: " + path.getThisFile().getName() + " " + isDelete); } }); while (true) { tree.update(); Thread.sleep(5 * 1000); } } public static String computeTags(String baseParent, String path) { try { StringBuilder tagPath = null; if (path.startsWith(baseParent) && path.equals(baseParent) == false) { // String subbed = path.substring(basePath.length(), // path.length()); String subbed = path.substring(baseParent.length(), path.length()); StringTokenizer toks = new StringTokenizer(subbed, File.separator); int howmany = toks.countTokens(); // System.out.println(subbed + " sep: " + File.separator + // " has " + toks.countTokens() + " toks: "); tagPath = new StringBuilder(); for (int i = 0; i < howmany - 1; i++) { tagPath.append(toks.nextToken()); if (i < howmany - 2) { tagPath.append("/"); } } System.out.println("for " + path + " tagPath: " + tagPath); if (tagPath.length() == 0) { tagPath = null; } } if (tagPath != null) { return tagPath.toString(); } } catch (Exception e) { e.printStackTrace(); } return null; } }