package net.filebot.ui.subtitle; import static java.util.Collections.*; import static net.filebot.MediaTypes.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import javax.swing.SwingWorker; import javax.swing.event.SwingPropertyChangeSupport; import net.filebot.Language; import net.filebot.util.FileUtilities; import net.filebot.vfs.ArchiveType; import net.filebot.vfs.MemoryFile; import net.filebot.web.SubtitleDescriptor; import net.filebot.web.SubtitleProvider; public class SubtitlePackage { private final SubtitleProvider provider; private final SubtitleDescriptor subtitle; private final Language language; private Download download; public SubtitlePackage(SubtitleProvider provider, SubtitleDescriptor subtitle) { this.provider = provider; this.subtitle = subtitle; // resolve language name this.language = Language.findLanguage(subtitle.getLanguageName()); // initialize download worker download = new Download(subtitle); // forward phase events download.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals("phase")) { pcs.firePropertyChange("download.phase", evt.getOldValue(), evt.getNewValue()); } } }); } public SubtitleProvider getProvider() { return provider; } public String getName() { return subtitle.getName(); } public Language getLanguage() { return language; } public String getType() { return subtitle.getType(); } public Download getDownload() { return download; } public void reset() { // cancel old download download.cancel(false); // create new download Download old = download; download = new Download(subtitle); // transfer listeners for (PropertyChangeListener listener : old.getPropertyChangeSupport().getPropertyChangeListeners()) { old.removePropertyChangeListener(listener); download.addPropertyChangeListener(listener); } pcs.firePropertyChange("download.phase", old.getPhase(), download.getPhase()); } @Override public String toString() { return subtitle.getName(); } private final PropertyChangeSupport pcs = new SwingPropertyChangeSupport(this, true); public void addPropertyChangeListener(PropertyChangeListener listener) { pcs.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { pcs.removePropertyChangeListener(listener); } public static class Download extends SwingWorker<List<MemoryFile>, Void> { enum Phase { PENDING, WAITING, DOWNLOADING, EXTRACTING, DONE } private final SubtitleDescriptor subtitle; private Phase current = Phase.PENDING; private Download(SubtitleDescriptor descriptor) { this.subtitle = descriptor; } public void start() { setPhase(Phase.WAITING); // enqueue worker execute(); } @Override protected List<MemoryFile> doInBackground() throws Exception { setPhase(Phase.DOWNLOADING); // fetch archive ByteBuffer data = subtitle.fetch(); // abort if download has been cancelled if (isCancelled()) return null; setPhase(Phase.EXTRACTING); ArchiveType archiveType = ArchiveType.forName(subtitle.getType()); if (archiveType == ArchiveType.UNKOWN) { // cannot extract files from archive return singletonList(new MemoryFile(subtitle.getPath(), data)); } // extract contents of the archive List<MemoryFile> vfs = extract(archiveType, data); // if we can't extract files from a rar archive, it might actually be a zip file with the wrong extension if (vfs.isEmpty() && archiveType != ArchiveType.ZIP) { vfs = extract(ArchiveType.ZIP, data); } if (vfs.isEmpty()) { throw new IOException("Cannot extract files from archive"); } // return file contents return vfs; } private List<MemoryFile> extract(ArchiveType archiveType, ByteBuffer data) throws IOException { List<MemoryFile> vfs = new ArrayList<MemoryFile>(); for (MemoryFile file : archiveType.fromData(data)) { if (SUBTITLE_FILES.accept(file.getName())) { // add subtitle files, ignore non-subtitle files vfs.add(file); } else { // check if file is a supported archive ArchiveType type = ArchiveType.forName(FileUtilities.getExtension(file.getName())); if (type != ArchiveType.UNKOWN) { // extract nested archives recursively vfs.addAll(extract(type, file.getData())); } } } return vfs; } @Override protected void done() { setPhase(Phase.DONE); } private void setPhase(Phase phase) { Phase old = current; current = phase; firePropertyChange("phase", old, phase); } public boolean isStarted() { return current != Phase.PENDING; } public Phase getPhase() { return current; } } }