ec5f163b980c25a11947a088a785614d6a65bc78
[debian/jabref.git] / src / java / net / sf / jabref / external / DownloadExternalFile.java
1 package net.sf.jabref.external;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.net.MalformedURLException;
6 import java.net.URL;
7
8 import javax.swing.JOptionPane;
9 import javax.swing.SwingUtilities;
10
11 import net.sf.jabref.*;
12 import net.sf.jabref.gui.FileListEntry;
13 import net.sf.jabref.gui.FileListEntryEditor;
14 import net.sf.jabref.net.URLDownload;
15
16 /**
17  * This class handles the download of an external file. Typically called when the user clicks
18  * the "Download" button in a FileListEditor shown in an EntryEditor.
19  * <p/>
20  * The FileListEditor constructs the DownloadExternalFile instance, then calls the download()
21  * method passing a reference to itself as a callback. The download() method asks for the URL,
22  * then starts the download. When the download is completed, it calls the downloadCompleted()
23  * method on the callback FileListEditor, which then needs to take care of linking to the file.
24  * The local filename is passed as an argument to the downloadCompleted() method.
25  * <p/>
26  * If the download is cancelled, or failed, the user is informed. The callback is never called.
27  */
28 public class DownloadExternalFile {
29     private JabRefFrame frame;
30     private MetaData metaData;
31     private String bibtexKey;
32     private FileListEntryEditor editor;
33     private boolean downloadFinished = false;
34
35     public DownloadExternalFile(JabRefFrame frame, MetaData metaData, String bibtexKey) {
36
37         this.frame = frame;
38         this.metaData = metaData;
39         this.bibtexKey = bibtexKey;
40     }
41
42     /**
43      * Start a download.
44      *
45      * @param callback The object to which the filename should be reported when download
46      *                 is complete.
47      */
48     public void download(final DownloadCallback callback) throws IOException {
49
50         final String res = JOptionPane.showInputDialog(frame,
51                 Globals.lang("Enter URL to download"));
52
53         if (res == null || res.trim().length() == 0)
54             return;
55
56         // First of all, start the download itself in the background to a temporary file:
57         final File tmp = File.createTempFile("jabref_download", "tmp");
58         tmp.deleteOnExit();
59         (new Thread() {
60             public void run() {
61
62                 try {
63
64                     URL url = new URL(res);
65                     URLDownload udl = new URLDownload(frame, url, tmp);
66                     try {
67                         udl.download();
68                     } catch (IOException e2) {
69                         JOptionPane.showMessageDialog(frame, Globals.lang("Invalid URL")+": "
70                                 + e2.getMessage(), Globals.lang("Download file"),
71                                 JOptionPane.ERROR_MESSAGE);
72                         Globals.logger("Error while downloading " + url.toString());
73                         return;
74                     }
75
76                     // Download finished: call the method that stops the progress bar etc.:
77                     SwingUtilities.invokeLater(new Runnable() {
78                         public void run() {
79                             downloadFinished();
80                         }
81                     });
82
83
84                 } catch (MalformedURLException e1) {
85                     JOptionPane.showMessageDialog(frame, Globals.lang("Invalid URL"), Globals
86                             .lang("Download file"), JOptionPane.ERROR_MESSAGE);
87                 }
88             }
89         }).start();
90
91         // Then, while the download is proceeding, let the user choose the details of the file:
92         String suffix = getSuffix(res);
93         String suggestedName = bibtexKey != null ? getSuggestedFileName(res, suffix) : "";
94         String fDirectory = getFileDirectory(res);
95         if (fDirectory.trim().equals(""))
96             fDirectory = null;
97         final String directory = fDirectory;
98         final String suggestDir = directory != null ? directory : System.getProperty("user.home");
99         File file = new File(new File(suggestDir), suggestedName);
100         FileListEntry entry = new FileListEntry("", bibtexKey != null ? file.getPath() : "",
101                 Globals.prefs.getExternalFileTypeByExt(suffix));
102         editor = new FileListEntryEditor(frame, entry, true, false, metaData);
103         editor.getProgressBar().setIndeterminate(true);
104         editor.setOkEnabled(false);
105         editor.setExternalConfirm(new ConfirmCloseFileListEntryEditor() {
106             public boolean confirmClose(FileListEntry entry) {
107                 File f = directory != null ? expandFilename(directory, entry.getLink())
108                         : new File(entry.getLink());
109                 if (f.isDirectory()) {
110                     JOptionPane.showMessageDialog(frame,
111                             Globals.lang("Target file cannot be a directory."), Globals.lang("Download file"),
112                             JOptionPane.ERROR_MESSAGE);
113                     return false;
114                 }
115                 if (f.exists()) {
116                     return JOptionPane.showConfirmDialog
117                         (frame, "'"+f.getName()+"' "+Globals.lang("exists. Overwrite file?"),
118                         Globals.lang("Download file"), JOptionPane.OK_CANCEL_OPTION)
119                             == JOptionPane.OK_OPTION;
120                 } else
121                     return true;
122             }
123         });
124         editor.setVisible(true);
125         // Editor closed. Go on:
126         if (editor.okPressed()) {
127             File toFile = directory != null ? expandFilename(directory, entry.getLink())
128                     : new File(entry.getLink());
129             String dirPrefix;
130             if (directory != null) {
131                 if (!directory.endsWith(System.getProperty("file.separator")))
132                     dirPrefix = directory+System.getProperty("file.separator");
133                 else
134                     dirPrefix = directory;
135             } else
136                 dirPrefix = null;
137
138             try {
139                 boolean success = Util.copyFile(tmp, toFile, true);
140                 if (!success) {
141                     // OOps, the file exists!
142                     System.out.println("File already exists! DownloadExternalFile.download()");
143                 }
144
145                 // If the local file is in or below the main file directory, change the
146                 // path to relative:
147                 if ((directory != null) && entry.getLink().startsWith(directory) &&
148                         (entry.getLink().length() > dirPrefix.length())) {
149                     entry.setLink(entry.getLink().substring(dirPrefix.length()));
150                 }
151
152                 callback.downloadComplete(entry);
153             } catch (IOException ex) {
154                 ex.printStackTrace();
155             }
156
157             tmp.delete();
158         }
159         else {
160             // Cancelled. Just delete the temp file:
161             if (downloadFinished)
162                 tmp.delete();
163         }
164
165     }
166
167     /**
168      * Construct a File object pointing to the file linked, whether the link is
169      * absolute or relative to the main directory.
170      * @param directory The main directory.
171      * @param link The absolute or relative link.
172      * @return The expanded File.
173      */
174     private File expandFilename(String directory, String link) {
175         File toFile = new File(link);
176         // If this is a relative link, we should perhaps append the directory:
177         String dirPrefix = directory+System.getProperty("file.separator");
178         if (!toFile.isAbsolute()) {
179             toFile = new File(dirPrefix+link);
180         }
181         return toFile;
182     }
183
184     /**
185      * This is called by the download thread when download is completed.
186      */
187     public void downloadFinished() {
188         downloadFinished = true;
189         editor.getProgressBar().setVisible(false);
190         editor.getProgressBarLabel().setVisible(false);
191         editor.setOkEnabled(true);
192         editor.getProgressBar().setValue(editor.getProgressBar().getMaximum());
193     }
194
195     public String getSuggestedFileName(String res, String suffix) {
196         
197         String plannedName = bibtexKey;
198         if (suffix.length() > 0)
199             plannedName += "." + suffix;
200
201         /*
202         * [ 1548875 ] download pdf produces unsupported filename
203         *
204         * http://sourceforge.net/tracker/index.php?func=detail&aid=1548875&group_id=92314&atid=600306
205         *
206         */
207         if (Globals.ON_WIN) {
208             plannedName = plannedName.replaceAll(
209                     "\\?|\\*|\\<|\\>|\\||\\\"|\\:|\\.$|\\[|\\]", "");
210         } else if (Globals.ON_MAC) {
211             plannedName = plannedName.replaceAll(":", "");
212         }
213
214         return plannedName;
215     }
216
217     /**
218      * Look for the last '.' in the link, and returnthe following characters.
219      * This gives the extension for most reasonably named links.
220      *
221      * @param link The link
222      * @return The suffix, excluding the dot (e.g. ".pdf")
223      */
224     public String getSuffix(final String link) {
225         String strippedLink = link;
226         try {
227             // Try to strip the query string, if any, to get the correct suffix:
228             URL url = new URL(link);
229             if ((url.getQuery() != null) && (url.getQuery().length() < link.length()-1)) {
230                 strippedLink = link.substring(0, link.length()-url.getQuery().length()-1);
231             }
232         } catch (MalformedURLException e) {
233             // Don't report this error, since this getting the suffix is a non-critical
234             // operation, and this error will be triggered and reported elsewhere.
235         }
236         // First see if the stripped link gives a reasonable suffix:
237         String suffix;
238         int index = strippedLink.lastIndexOf('.');
239         if ((index <= 0) || (index == strippedLink.length() - 1)) // No occurence, or at the end
240             suffix = null;
241         else suffix = strippedLink.substring(index + 1);
242         if (Globals.prefs.getExternalFileTypeByExt(suffix) != null) {
243             return suffix;
244         }
245         else {
246             // If the suffix doesn't seem to give any reasonable file type, try
247             // with the non-stripped link:
248             index = link.lastIndexOf('.');
249             if ((index <= 0) || (index == strippedLink.length() - 1)) {
250                 // No occurence, or at the end
251                 // Check if there are path separators in the suffix - if so, it is definitely
252                 // not a proper suffix, so we should give up:
253                 if (suffix.indexOf('/') > 0)
254                     return "";
255                 else
256                     return suffix; // return the first one we found, anyway.
257             }
258             else {
259                  // Check if there are path separators in the suffix - if so, it is definitely
260                 // not a proper suffix, so we should give up:
261                 if (link.substring(index + 1).indexOf('/') > 0)
262                     return "";
263                 else
264                     return link.substring(index + 1);
265             }
266         }
267
268     }
269
270     public String getFileDirectory(String link) {
271         return metaData.getFileDirectory(GUIGlobals.FILE_FIELD);
272     }
273
274     /**
275      * Callback interface that users of this class must implement in order to receive
276      * notification when download is complete.
277      */
278     public interface DownloadCallback {
279         public void downloadComplete(FileListEntry file);
280     }
281 }