canadaduane (owner)

Forks

Revisions

gist: 62536 Download_button fork
public
Description:
Downloads files matching the regular expression pattern to a folder.
Public Clone URL: git://gist.github.com/62536.git
Embed All Files: show embed
download-files.js #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
/*
Ubiquity Command: download-files [regexp pattern] to [folder]
Author: Duane Johnson
Email: duane.johnson@gmail.com
Description: Downloads files matching the regular expression pattern to a folder.
2009-08-29: Updated for Ubiquity 0.5.x
See: http://github.com/canadaduane/ubiquity-download-files
*/
 
var use_file_extension = false;
var poorly_formed_regexp = false;
 
// Suggests file extensions from the current page, e.g. "png$" if there is a png image,
// or "js$" if there are javascript files.
var noun_type_file_extension_from_page = {
  _name: "file pattern",
  suggest: function( text, html ) {
    var suggestions = [CmdUtils.makeSugg(text)];
    
    if (!use_file_extension) {
      var exts = DownloadFiles.uniqueExtensions();
      for (i in exts) {
        if (exts[i].indexOf(text) >= 0) {
          suggestions.push(CmdUtils.makeSugg(exts[i] + "$"));
        }
      }
    }
    
    return suggestions;
  }
}
 
// Suggests autocompletion for directories on the local filesystem. For example, if
// you type ~/Li<tab> on a Mac, then it will find the "Library" subdirectory in your
// home directory and (supposing your username is 'duane'), it will complete the noun
// as "/Users/duane/Library".
var noun_type_local_directory = {
  _name: "directory on local system",
  suggest: function( text, html ) {
    var suggestions = [];
    
    // The tilde is an illegal directory name by itself, but it is legal with a
    // trailing slash
    if (text == "~") text = "~/";
    
    // Always accept whatever the user types, even if it's an invalid directory
    suggestions.push(CmdUtils.makeSugg(text));
 
    // Break the directory up into everything before and including the slash, and
    // everything after the last slash
    var parts = text.match(/^(.*\/)([^\/]*)$/);
    if (parts) {
      // The first part is the path
      var path = parts[1];
      // Everything after the last slash becomes a "possible" completion, depending on
      // subdirectory names
      var possible = parts[2];
      
      try {
        var folder = Components.
          classes["@mozilla.org/file/local;1"].
          createInstance(Components.interfaces.nsILocalFile);
        folder.initWithPath(path);
        
        if (folder.isDirectory()) {
          var enum = folder.directoryEntries;
          while (enum.hasMoreElements()) {
            var dirEntry = enum.getNext().QueryInterface(Components.interfaces.nsIFile);
            // Need exists() here so we can ignore symlinks to files that no longer exist
            if (dirEntry.exists() && dirEntry.isDirectory()) {
              // Does this sub-directory match the 'possible' substring?
              // To test, get the part of the path after the last '/' and compare with 'possible'
              var match = dirEntry.path.match(/\/([^\/]*)$/);
              if (match && match[1].indexOf(possible) == 0) {
                if (dirEntry.path != text) // Don't suggest twice the same thing they've explicitly typed
                  suggestions.push(CmdUtils.makeSugg(dirEntry.path));
              }
            }
          }
        }
      } catch(e) {
        // CmdUtils.log(e);
      }
    }
    
    return suggestions;
  }
}
 
var DownloadFiles = {
  // Given a javascript object with keys and values, return just the keys. In other words, use the
  // keys as a "set" so we don't have to do it ourselves.
  listFromSet: function(obj) {
    var newList = [];
    for (k in obj) newList.push(k);
    return newList;
  },
  
  // Returns true if a folder exists on the local file system, false otherwise
  folderExists: function(path) {
    try {
      var folder = Components.
        classes["@mozilla.org/file/local;1"].
        createInstance(Components.interfaces.nsILocalFile);
      folder.initWithPath(path);
      if (!folder.isDirectory()) {
        return false;
      }
      return true;
    } catch(e) {
      return false;
    }
  },
  
  // Pass in a "preferred" folder. If null or undefined, getFolder will let the user pick a folder.
  // Returns false on failure (e.g. the user cancelled), or the nsIFile object on success.
  getFolder: function(preferred) {
    var window = CmdUtils.getWindow();
    var path = preferred;
    var folder;
    if (!path) {
      var nsIFilePicker = Components.interfaces.nsIFilePicker;
      var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
      picker.init(window, "", nsIFilePicker.modeGetFolder);
      if (picker.show() == nsIFilePicker.returnOK) {
        path = picker.file.path;
      } else {
        displayMessage("Cancelled");
        return false;
      }
    }
    try {
      var err = function(msg) { displayMessage(msg + "('" + path + "')"); };
      var folder = Components.
        classes["@mozilla.org/file/local;1"].
        createInstance(Components.interfaces.nsILocalFile);
      folder.initWithPath(path);
      if (!folder.isDirectory()) {
        err("The destination is not a folder");
        return false;
      }
      return folder;
    } catch(e) {
      err("The destination folder does not exist");
      return false;
    }
  },
  
  // Looks for 'pattern' within the current page's HTML and any frames or iframes.
  // - Searches 'a' tags and 'link' tags for 'href' attributes;
  // - Searches 'img' and 'script' tags for 'src' attributes.
  // Returns: A list of URLs matching the pattern.
  matchFiles: function(pattern) {
    if (!pattern) pattern = "";
    var files = [];
    var i, j;
 
    var addFiles = function(doc) {
      files = files.concat(jQuery("a,link", doc.childNodes).map(function() { return this.getAttribute("href"); }).get());
      files = files.concat(jQuery("img,script", doc.childNodes).map(function() { return this.getAttribute("src"); }).get());
      
      // Search contents within frames, if any
      jQuery("frame,iframe", doc.body).each(function() {
        addFiles(this.contentDocument);
      });
      
      var IOService =
        Components.classes["@mozilla.org/network/io-service;1"].
        getService(Components.interfaces.nsIIOService);
      // Search style sheets
      for (i = 0; i < doc.styleSheets.length; i++) {
        var sheet = doc.styleSheets[i];
        if (sheet.href) { // Apparently stylesheets can be null sometimes
          var cssURI = IOService.newURI(sheet.href, null, null);
          // Loop through each rule in each stylesheet
          for (j = 0; j < sheet.cssRules.length; j++) {
            var style = sheet.cssRules[j].cssText;
 
            // Capture the url() portion of the rule
            var match = style.match(/url\(([^\)]+)\)/);
            if (match) {
              var relativePath = match[1];
              // Make sure the URI is relative to the CSS from which it was extracted
              var imageURI = IOService.newURI(relativePath, null, cssURI);
              files.push(imageURI.spec);
            }
          }
        }
      }
    };
    
    // Recursively add files from the main document
    addFiles(Application.activeWindow.activeTab.document);
    
    // Only add files that match our 'pattern' criterion, adding matches to a Set so
    // we get just one of each result (no duplicates).
    var matchedSet = {};
    for (i in files) {
      var file = files[i];
      if (file.match(pattern)) {
        matchedSet[file] = true;
      }
    }
    return DownloadFiles.listFromSet(matchedSet);
  },
  
  // Finds unique file extensions from the list of all URLs produced from matchFiles().
  // Returns the list, e.g. ["gif", "js"]
  uniqueExtensions: function() {
    var files = DownloadFiles.matchFiles();
    // Use an object's keys to maintain a unique list
    var extSet = {};
    for (i in files) {
      if (files[i]) {
        var ext = DownloadFiles.extFromURL(files[i]);
        if (ext) extSet[ext] = true;
      }
    }
    return DownloadFiles.listFromSet(extSet);
  },
  
  // Given a url string, returns a file extension if possible (e.g. "gif" or "jpg")
  extFromURL: function(url) {
    url = url.replace(/^https?:\/\//, "");
    url = url.replace(/\?.*$/, "");
    var m = url.match(/\/.*\.([^\.]*)$/);
    if (m) return m[1];
    else return false;
  },
  
  // Given a url string, returns the "leaf" part of the path,
  // e.g. "http://mysite.com/files/blah.jpg" becomes "blah.jpg"
  fileFromURL: function(url) {
    var m = url.match(/\/([^\/]*)$/);
    if (m) return m[1];
    else return url;
  },
  
  // Given a url string and an nsIFile 'folder' object that points to a local
  // directory, download the thing to the folder.
  downloadFile: function(file_url, folder) {
    try {
      var doc = Application.activeWindow.activeTab.document;
      var current = Utils.url(doc.documentURI);
      var uri = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService).newURI(file_url, null, current);
 
      // New file object & new file if necessary
      var target_file = folder.clone();
      target_file.append(DownloadFiles.fileFromURL(file_url));
      if(!target_file.exists()) { target_file.create(0x00, 0644); }
      
      //new persitence object
      var persist = Components.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].createInstance(Components.interfaces.nsIWebBrowserPersist);
 
      //save file to target
      persist.saveURI(uri, null, null, null, null, target_file);
      return true;
    } catch (e) {
      // alert(e);
    }
    return false;
  }
}
 
CmdUtils.CreateCommand({
  names: ["download files"],
  icon: "http://inquirylabs.com/downloads/download.png",
  homepage: "http://inquirylabs.com/",
  author: { name: "Duane Johnson", email: "duane.johnson@gmail.com"},
  license: "MIT",
  description: "Downloads all files of the given pattern to your computer.",
  help: "e.g. download-files png$ to ~/Desktop/Images",
  arguments: [
    {role: 'object', nountype: noun_type_file_extension_from_page, label: 'pattern'},
    {role: 'goal', nountype: noun_type_local_directory, label: 'directory'}
  ],
  preview: function( pblock, args ) {
    if (!args.object.text) {
      pblock.innerHTML = "<p>Downloads all files matching the given pattern.</p>";
      return;
    }
    
    var template = "<p>Download files matching /${pattern}/${dest}</p><ul>${list}</ul>";
    var path = args.goal.text;
    var folderHtml = "";
    var matchList = "";
    
    if (path) {
      // Take care of the folder icon. It should be "x"ed out if the destination does not exist
      folderHtml =
        "<p><img src='http://inquirylabs.com/downloads/folder" +
        (DownloadFiles.folderExists(path) ? "" : "-x") + ".png'" +
        " align='absmiddle' /> " + args.goal.html + "</p>";
      // Hackish way of telling our noun_type_file_extension_from_page to
      // stop suggesting things once a folder is specified
      use_file_extension = true;
    } else {
      use_file_extension = false;
    }
    
    try {
      fileUrls = DownloadFiles.matchFiles(args.object.text);
    } catch(e) {
      fileUrls = false;
    }
    
    if (fileUrls === false) {
      template = "<p>Download files matching [Incorrectly Formatted Regular Expression]</p>";
    } else if (fileUrls.length == 0) {
      template = "<p>No files match the regular expression /${pattern}/</p>";
    } else if (fileUrls.length > 0) {
      for (i in fileUrls) {
        var url = fileUrls[i];
        var filename = DownloadFiles.fileFromURL(url);
        var span = "&nbsp;&nbsp;<span style='color:#888'>" + unescape( url ) + "</span>";
      
        // Show the filename first, then the URL in grey if it is different than the filename
        matchList +=
          "<li style='width:1000em'>" +
            (filename == "" ? url : unescape( filename )) +
            (filename == url ? "" : span) +
          "</li>";
      }
    }
    
    // Finally, display the template with our substitutions
    pblock.innerHTML = CmdUtils.renderTemplate(template, {
      "pattern": args.object.html,
      "dest": folderHtml,
      "list": matchList
    });
  },
  execute: function(args) {
    var folder = DownloadFiles.getFolder(args.goal.text);
    if (folder) {
      fileUrls = DownloadFiles.matchFiles(args.object.text);
      displayMessage("Downloading " + fileUrls.length + " files to " + folder.path);
 
      var succeeded = 0;
      for (i in fileUrls) {
        if (DownloadFiles.downloadFile(fileUrls[i], folder)) succeeded += 1;
      }
      if (succeeded > 0) {
        if (succeeded == fileUrls.length)
          displayMessage("All " + succeeded + " files were saved to " + folder.path);
        else
          displayMessage("Only " + succeeded + " (of " + fileUrls.length + ") files were saved to " + folder.path);
      } else {
        displayMessage("No files were saved.");
      }
    }
  }
});