package net.sf.jabref.external; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import net.sf.jabref.*; import net.sf.jabref.gui.FileListEntry; import net.sf.jabref.gui.FileListEntryEditor; import net.sf.jabref.net.URLDownload; /** * This class handles the download of an external file. Typically called when the user clicks * the "Download" button in a FileListEditor shown in an EntryEditor. * <p/> * The FileListEditor constructs the DownloadExternalFile instance, then calls the download() * method passing a reference to itself as a callback. The download() method asks for the URL, * then starts the download. When the download is completed, it calls the downloadCompleted() * method on the callback FileListEditor, which then needs to take care of linking to the file. * The local filename is passed as an argument to the downloadCompleted() method. * <p/> * If the download is cancelled, or failed, the user is informed. The callback is never called. */ public class DownloadExternalFile { private JabRefFrame frame; private MetaData metaData; private String bibtexKey; private FileListEntryEditor editor; private boolean downloadFinished = false; private boolean dontShowDialog = false; public DownloadExternalFile(JabRefFrame frame, MetaData metaData, String bibtexKey) { this.frame = frame; this.metaData = metaData; this.bibtexKey = bibtexKey; } /** * Start a download. * * @param callback The object to which the filename should be reported when download * is complete. */ public void download(final DownloadCallback callback) throws IOException { dontShowDialog = false; final String res = JOptionPane.showInputDialog(frame, Globals.lang("Enter URL to download")); if (res == null || res.trim().length() == 0) return; URL url = null; try { url = new URL(res); } catch (MalformedURLException ex1) { JOptionPane.showMessageDialog(frame, Globals.lang("Invalid URL"), Globals .lang("Download file"), JOptionPane.ERROR_MESSAGE); return; } download(url, callback); } /** * Start a download. * * @param callback The object to which the filename should be reported when download * is complete. */ public void download(URL url, final DownloadCallback callback) throws IOException { String res = url.toString(); URLDownload udl = null; // First of all, start the download itself in the background to a temporary file: final File tmp = File.createTempFile("jabref_download", "tmp"); tmp.deleteOnExit(); //long time = System.currentTimeMillis(); try { udl = new URLDownload(frame, url, tmp); // TODO: what if this takes long time? // TODO: stop editor dialog if this results in an error: udl.openConnectionOnly(); // Read MIME type } catch (IOException ex) { JOptionPane.showMessageDialog(frame, Globals.lang("Invalid URL")+": " + ex.getMessage(), Globals.lang("Download file"), JOptionPane.ERROR_MESSAGE); Globals.logger("Error while downloading " + "'" + res + "'"); return; } final URL urlF = url; final URLDownload udlF = udl; //System.out.println("Time: "+(System.currentTimeMillis()-time)); (new Thread() { public void run() { try { udlF.download(); } catch (IOException e2) { dontShowDialog = true; if ((editor != null) && (editor.isVisible())) editor.setVisible(false, false); JOptionPane.showMessageDialog(frame, Globals.lang("Invalid URL")+": " + e2.getMessage(), Globals.lang("Download file"), JOptionPane.ERROR_MESSAGE); Globals.logger("Error while downloading " + "'" + urlF.toString()+ "'"); return; } // Download finished: call the method that stops the progress bar etc.: SwingUtilities.invokeLater(new Runnable() { public void run() { downloadFinished(); } }); } }).start(); ExternalFileType suggestedType = null; if (udl.getMimeType() != null) { System.out.println("mimetype:"+udl.getMimeType()); suggestedType = Globals.prefs.getExternalFileTypeByMimeType(udl.getMimeType()); /*if (suggestedType != null) System.out.println("Found type '"+suggestedType.getName()+"' by MIME type '"+udl.getMimeType()+"'");*/ } // Then, while the download is proceeding, let the user choose the details of the file: String suffix; if (suggestedType != null) { suffix = suggestedType.getExtension(); } else { // If we didn't find a file type from the MIME type, try based on extension: suffix = getSuffix(res); suggestedType = Globals.prefs.getExternalFileTypeByExt(suffix); } String suggestedName = bibtexKey != null ? getSuggestedFileName(suffix) : ""; String fDirectory = getFileDirectory(res); if ((fDirectory != null) && fDirectory.trim().equals("")) fDirectory = null; final String directory = fDirectory; final String suggestDir = directory != null ? directory : System.getProperty("user.home"); File file = new File(new File(suggestDir), suggestedName); FileListEntry entry = new FileListEntry("", bibtexKey != null ? file.getCanonicalPath() : "", suggestedType); editor = new FileListEntryEditor(frame, entry, true, false, metaData); editor.getProgressBar().setIndeterminate(true); editor.setOkEnabled(false); editor.setExternalConfirm(new ConfirmCloseFileListEntryEditor() { public boolean confirmClose(FileListEntry entry) { File f = directory != null ? expandFilename(directory, entry.getLink()) : new File(entry.getLink()); if (f.isDirectory()) { JOptionPane.showMessageDialog(frame, Globals.lang("Target file cannot be a directory."), Globals.lang("Download file"), JOptionPane.ERROR_MESSAGE); return false; } if (f.exists()) { return JOptionPane.showConfirmDialog (frame, "'"+f.getName()+"' "+Globals.lang("exists. Overwrite file?"), Globals.lang("Download file"), JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION; } else return true; } }); if (!dontShowDialog) // If an error occured with the URL, this flag may have been set editor.setVisible(true, false); else return; // Editor closed. Go on: if (editor.okPressed()) { File toFile = directory != null ? expandFilename(directory, entry.getLink()) : new File(entry.getLink()); String dirPrefix; if (directory != null) { if (!directory.endsWith(System.getProperty("file.separator"))) dirPrefix = directory+System.getProperty("file.separator"); else dirPrefix = directory; } else dirPrefix = null; try { boolean success = Util.copyFile(tmp, toFile, true); if (!success) { // OOps, the file exists! System.out.println("File already exists! DownloadExternalFile.download()"); } // If the local file is in or below the main file directory, change the // path to relative: if ((directory != null) && entry.getLink().startsWith(directory) && (entry.getLink().length() > dirPrefix.length())) { entry.setLink(entry.getLink().substring(dirPrefix.length())); } callback.downloadComplete(entry); } catch (IOException ex) { ex.printStackTrace(); } tmp.delete(); } else { // Cancelled. Just delete the temp file: if (downloadFinished) tmp.delete(); } } /** * Construct a File object pointing to the file linked, whether the link is * absolute or relative to the main directory. * @param directory The main directory. * @param link The absolute or relative link. * @return The expanded File. */ private File expandFilename(String directory, String link) { File toFile = new File(link); // If this is a relative link, we should perhaps append the directory: String dirPrefix = directory+System.getProperty("file.separator"); if (!toFile.isAbsolute()) { toFile = new File(dirPrefix+link); } return toFile; } /** * This is called by the download thread when download is completed. */ public void downloadFinished() { downloadFinished = true; editor.getProgressBar().setVisible(false); editor.getProgressBarLabel().setVisible(false); editor.setOkEnabled(true); editor.getProgressBar().setValue(editor.getProgressBar().getMaximum()); } public String getSuggestedFileName(String suffix) { String plannedName = bibtexKey; if (suffix.length() > 0) plannedName += "." + suffix; /* * [ 1548875 ] download pdf produces unsupported filename * * http://sourceforge.net/tracker/index.php?func=detail&aid=1548875&group_id=92314&atid=600306 * */ if (Globals.ON_WIN) { plannedName = plannedName.replaceAll( "\\?|\\*|\\<|\\>|\\||\\\"|\\:|\\.$|\\[|\\]", ""); } else if (Globals.ON_MAC) { plannedName = plannedName.replaceAll(":", ""); } return plannedName; } /** * Look for the last '.' in the link, and returnthe following characters. * This gives the extension for most reasonably named links. * * @param link The link * @return The suffix, excluding the dot (e.g. "pdf") */ public String getSuffix(final String link) { String strippedLink = link; try { // Try to strip the query string, if any, to get the correct suffix: URL url = new URL(link); if ((url.getQuery() != null) && (url.getQuery().length() < link.length()-1)) { strippedLink = link.substring(0, link.length()-url.getQuery().length()-1); } } catch (MalformedURLException e) { // Don't report this error, since this getting the suffix is a non-critical // operation, and this error will be triggered and reported elsewhere. } // First see if the stripped link gives a reasonable suffix: String suffix; int index = strippedLink.lastIndexOf('.'); if ((index <= 0) || (index == strippedLink.length() - 1)) // No occurence, or at the end suffix = null; else suffix = strippedLink.substring(index + 1); if (Globals.prefs.getExternalFileTypeByExt(suffix) != null) { return suffix; } else { // If the suffix doesn't seem to give any reasonable file type, try // with the non-stripped link: index = link.lastIndexOf('.'); if ((index <= 0) || (index == strippedLink.length() - 1)) { // No occurence, or at the end // Check if there are path separators in the suffix - if so, it is definitely // not a proper suffix, so we should give up: if (suffix.indexOf('/') > 0) return ""; else return suffix; // return the first one we found, anyway. } else { // Check if there are path separators in the suffix - if so, it is definitely // not a proper suffix, so we should give up: if (link.substring(index + 1).indexOf('/') > 0) return ""; else return link.substring(index + 1); } } } public String getFileDirectory(String link) { return metaData.getFileDirectory(GUIGlobals.FILE_FIELD); } /** * Callback interface that users of this class must implement in order to receive * notification when download is complete. */ public interface DownloadCallback { public void downloadComplete(FileListEntry file); } }