joliclic

home

  1  /* ***** BEGIN LICENSE BLOCK *****
  2   * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3   *
  4   * The contents of this file are subject to the Mozilla Public License Version
  5   * 1.1 (the "License"); you may not use this file except in compliance with
  6   * the License. You may obtain a copy of the License at
  7   * http://www.mozilla.org/MPL/
  8   *
  9   * Software distributed under the License is distributed on an "AS IS" basis,
 10   * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 11   * for the specific language governing rights and limitations under the
 12   * License.
 13   *
 14   * The Original Code is Khromaxul
 15   *
 16   * The Initial Developer of the Original Code is
 17   * Nicolas Martin.
 18   * Portions created by the Initial Developer are Copyright (C) 2009
 19   * the Initial Developer. All Rights Reserved.
 20   *
 21   * Contributor(s):
 22   *   Nicolas Martin <joliclic@gmail.com>
 23   *
 24   * Alternatively, the contents of this file may be used under the terms of
 25   * either the GNU General Public License Version 2 or later (the "GPL"), or
 26   * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 27   * in which case the provisions of the GPL or the LGPL are applicable instead
 28   * of those above. If you wish to allow use of your version of this file only
 29   * under the terms of either the GPL or the LGPL, and not to allow others to
 30   * use your version of this file under the terms of the MPL, indicate your
 31   * decision by deleting the provisions above and replace them with the notice
 32   * and other provisions required by the GPL or the LGPL. If you do not delete
 33   * the provisions above, a recipient may use your version of this file under
 34   * the terms of any one of the MPL, the GPL or the LGPL.
 35   *
 36   * ***** END LICENSE BLOCK ***** */
 37  
 38  if (!Cc) var Cc = Components.classes;
 39  if (!Ci) var Ci = Components.interfaces;
 40  
 41  if (!kh) var kh = {};
 42  
 43  /**
 44   * @namespace
 45   */
 46  kh.file = {
 47      _ios: null,
 48      /**
 49       * @static
 50       * @returns {nsIIOService} readonly
 51       */
 52      get ios() {
 53          if (!this._ios)
 54              this._ios = Cc["@mozilla.org/network/io-service;1"]
 55                          .getService(Ci.nsIIOService);
 56          return this._ios;
 57      },
 58      
 59      /**
 60       * @constructor
 61       * @param {String} aName name of the palette, not the filename
 62       * @param {String} aSpec
 63       */
 64      PaletteInfo: function(aName, aSpec) {
 65          this.name = aName;
 66          this.spec = aSpec;
 67      },
 68      
 69      /**
 70       * @param {String} aSpec
 71       * @returns {nsIFile}
 72       */
 73      getFile: function kh_file_getFile(aSpec) {
 74          var uri = this.ios.newURI(aSpec, "", null);
 75          var file = uri.QueryInterface(Ci.nsIFileURL).file;
 76          
 77          return file;
 78      },
 79      
 80      /**
 81       * @param {nsiFile} aFile
 82       * @returns {String} spec of the file
 83       */
 84      getSpecOfFile: function kh_file_getSpecOfFile(aFile) {
 85          return this.ios.newFileURI(aFile).spec;
 86      },
 87      
 88      /**
 89       * this is synchronous, it will block the main UI.
 90       * But we use it to get chrome files and local xml files
 91       *
 92       * @param {String} aSpec
 93       * @param {Object} aObj. An obj = {} should be passed by reference,
 94       *     aObj.value will contain the xml document of the request file.
 95       * @returns {Boolean} inform that the request is done.
 96       */
 97      getSyncRemoteDoc: function kh_file_getRemoteFile(aSpec, aObj) {
 98          var req = new XMLHttpRequest();
 99          req.open("GET", aSpec, false);
100          
101          req.send(null);
102          
103          if(req.status == 0 || req.status == 200) {
104              if (aObj !== undefined) {
105                  //try {
106                      aObj.value = req.responseXML;
107                      
108                      return true;
109                  //} catch (e) {
110                  //    //alert(e);
111                  //}
112              }
113          }
114          return false;
115      },
116      
117      /**
118       * Get asynchronously a remote XMLDocument.
119       *
120       * @param {String} aSpec
121       * @param {Object} aObj. An obj = {} should be passed by reference,
122       *     aObj.value will contain the xml document of the request file.
123       * @param {Function} aCallbackFn. A function to execute after that the
124       *     document has been received.
125       * @returns {void}
126       */
127      getAsyncRemoteDoc:
128      function kh_file_getAsyncRemoteDoc(aSpec, aObj, aCallbackFn) {
129          var req = new XMLHttpRequest();
130          req.open("GET", aSpec, true);
131          
132          req.onreadystatechange = function (aEvt) {  
133              if (req.readyState != 4 || req.status != 200)
134                  return;
135              if (aObj !== undefined)
136                  aObj.value = req.responseXML;
137              if (aCallbackFn instanceof Function)
138                  aCallbackFn();
139          };
140          
141          req.send(null);
142      },
143      
144      /**
145       * @returns {nsIFile} the directory of the personal palettes.
146       */
147      getPersonalPalettesDir: function kh_file_getPersonalPalettesDir() {
148          var file = this.getKHDir();
149          file.append("palettes");
150          
151          if (!file.exists()) {
152              file.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
153          }
154          
155          return file;
156      },
157      
158      /**
159       * Return a special file determined by a name.
160       * <br /> for a list of available name,
161       * see http://mxr.mozilla.org/seamonkey/source/xpcom/io/nsDirectoryServiceDefs.h
162       * 
163       * @param {String} aName
164       * @returns {nsIFile}
165       */
166      getSpecialFile: function kh_file_getSpecialFile(aName) {
167          const dirService = "@mozilla.org/file/directory_service;1";
168          return Cc[dirService].getService(Ci.nsIProperties)
169                               .get(aName, Ci.nsIFile);
170      },
171      
172      /**
173       * @returns {nsIFile} the current profile directory
174       */
175      getProfileDir: function kh_file_getProfileDir() {
176          return this.getSpecialFile("ProfD");
177      },
178      
179      /**
180       * @returns {nsIFile} the khromaxul directory in the current profile
181       */
182      getKHDir: function kh_file_getKHDir() {
183          var file = this.getProfileDir();
184          file.append("khromaxul");
185          if (!file.exists()) {
186              file.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
187          }
188          
189          return file;
190      },
191      
192      /**
193       * @param {nsIFile} aDir
194       * @returns {PaletteInfo[]} ({name, spec}), containing all the palette
195       *     files contained in aDir. name refer to the name of the palette
196       *     (not the filename), and spec the spec of the file.
197       */
198      listPaletteFiles: function kh_file_listPaletteFiles(aDir) {
199          var ios = Cc["@mozilla.org/network/io-service;1"]
200                    .getService(Ci.nsIIOService);
201          
202          var list = [];
203          
204          if (!(aDir.exists() && aDir.isReadable() && aDir.isDirectory()))
205              return list;
206          
207          var entries = aDir.directoryEntries;
208          while(entries.hasMoreElements()) {
209              var entry = entries.getNext();
210              entry.QueryInterface(Ci.nsIFile);
211              
212              var filename = entry.leafName;
213              if (filename.substr(-4, 4) != ".gpl")
214                  continue;
215              
216              var paletteName = {};
217              if (!this.sniffGplPalette(entry, paletteName))
218                  continue;
219              
220              var pname = paletteName.value;
221              if (pname == "") {
222                  // todo remove illegal character (is there any?)
223                  pname = filename.substr(0, filename.length - 4);
224              }
225              
226              var spec = ios.newFileURI(entry).spec
227              var item = new this.PaletteInfo(pname, spec);
228              list.push(item);
229          }
230          
231          return list.sort(this.comparePaletteName);
232      },
233      
234      /**
235       * Used to sort an array of palette by their names
236       *
237       * @param {PaletteInfo} aPal ( PaletteInfo = {name, spec} )
238       * @param {PaletteInfo} bPal
239       * @returns {Int}
240       */
241      comparePaletteName: function kh_file_comparePaletteName(aPal, bPal) {
242          if (aPal.name < bPal.name)
243              return -1;
244          if (aPal.name > bPal.name)
245              return 1;
246          return 0;
247      },
248      
249      /**
250       * @param {nsIFile} aFile
251       * @param {Object}  aNameOb. Optional, if passed by reference, aNameObj.value
252       *     will contains the internal palette name.
253       * @returns {Boolean} True if the file is a Gimp Palette.
254       */
255      sniffGplPalette: function kh_file_sniffGplPalette(aFile, aNameObj) {
256          var data = "";
257          var fstream = Cc["@mozilla.org/network/file-input-stream;1"].
258                        createInstance(Ci.nsIFileInputStream);
259          fstream.init(aFile, -1, 0, 0);
260          fstream.QueryInterface(Ci.nsILineInputStream);
261      
262          var line = {}, hasmore, data;
263          
264          hasmore = fstream.readLine(line);
265          data = line.value;
266          if (data.substr(0, 12) != "GIMP Palette"/* || !hasmore*/) {
267              fstream.close();
268              return false;
269          }
270          
271          // no object was passed as parameter
272          if (aNameObj === undefined) {
273              fstream.close();
274              return true;
275          }
276          
277          // old Gimp Palette format (without name)
278          aNameObj.value = "";
279          
280          if (hasmore) {
281              hasmore = fstream.readLine(line);
282              data = line.value;
283              
284              if (data.substr(0, 6) == "Name: ") {
285                  // TODO: UTF-8 conversion if necessary
286                  aNameObj.value = data.substr(6);
287              }
288          }
289          
290          fstream.close();
291          
292          return true;
293      },
294      
295      /**
296       * @returns {XMLDocument}
297       */
298      createEmptyPalette: function kh_file_createEmptyPalette() {
299          var doc = document.implementation.createDocument("","palette", null);
300          var root = doc.documentElement;
301          var nameElt = doc.createElement("name");
302          root.appendChild(nameElt);
303          var columnsElt = doc.createElement("columns");
304          root.appendChild(columnsElt);
305          var commentsElt = doc.createElement("comments");
306          root.appendChild(commentsElt);
307          var colorsElt = doc.createElement("colors");
308          root.appendChild(colorsElt);
309          
310          return doc;
311      },
312      
313      /**
314       * @param {String} aSpec
315       * @param {Object} aInfoObj optiona, if passed by reference, aInfoObj will
316       *     contains a boolean that indicate if the targetted file is writable.
317       * @return {XMLDocument}
318       */
319      openPaletteBySpec: function kh_file_openPaletteBySpec(aSpec, aInfoObj) {
320          var file = kh.file.getFile(aSpec);
321          if (!(file.exists() && file.isReadable() && file.isFile()))
322              return doc;
323          
324          if (aInfoObj !== undefined) {
325              aInfoObj.value = file.isWritable();
326          }
327          
328          return this.openPaletteFile(file);
329      },
330      
331      /**
332       * @param {nsIFile} aFile
333       * @return {XMLDocument}
334       */
335      openPaletteFile: function kh_file_openPaletteFile(aFile) {
336          var doc = kh.file.createEmptyPalette();
337          
338          var nameElt = doc.getElementsByTagName("name")[0];
339          var specElt = doc.getElementsByTagName("spec")[0];
340          var columnsElt = doc.getElementsByTagName("columns")[0];
341          var commentsElt = doc.getElementsByTagName("comments")[0];
342          var colorsElt = doc.getElementsByTagName("colors")[0];
343          
344          // open an input stream from file
345          var istream = Cc["@mozilla.org/network/file-input-stream;1"].
346                        createInstance(Ci.nsIFileInputStream);
347          istream.init(aFile, 0x01, 0444, 0);
348          istream.QueryInterface(Ci.nsILineInputStream);
349          
350          // read line
351          var line = {}, hasmore, data;
352          
353          // Gimp Palette Magic Header
354          hasmore = istream.readLine(line);
355          data = line.value;
356          if (data.substr(0, 12) != "GIMP Palette" || !hasmore)
357              return doc;
358          
359          var defaultColumns = 16;
360          var maxColumns = 64; //internally Gimp use 256, it's 64 in the gimp UI.
361          
362          hasmore = istream.readLine(line);
363          data = line.value;
364          if (data.substr(0, 6) == "Name: ") {
365              // TODO: UTF-8 conversion if necessary
366              nameElt.appendChild(doc.createTextNode(data.substr(6)));
367              
368              if (!hasmore)
369                  return doc;
370              
371              hasmore = istream.readLine(line);
372              data = line.value;
373              if (data.substr(0, 9) == "Columns: ") {
374                  var nb = parseInt(data.substr(9));
375                  if (isNaN(nb) || nb < 1 || nb > 256) {
376                      nb = defaultColumns;
377                  }
378                  columnsElt.appendChild(doc.createTextNode(nb));
379                  
380                  if (!hasmore)
381                      return doc;
382                  
383                  hasmore = istream.readLine(line);
384                  data = line.value;
385              } else {
386                  columnsElt.appendChild(doc.createTextNode(defaultColumns));
387              }
388          } else {
389              // old format
390              nameElt.appendChild(doc.createTextNode(file.leafName));
391              columnsElt.appendChild(doc.createTextNode(defaultColumns));
392          }
393          
394          if (!hasmore)
395              return doc;
396          
397          if (data.substr(0, 1) == "#") {
398              while (hasmore && data.substr(0, 1) == "#") {
399                  var txt = data.substr(1);
400                  var cm = doc.createElement("comment");
401                  cm.appendChild(doc.createTextNode(txt));
402                  commentsElt.appendChild(cm);
403                  
404                  hasmore = istream.readLine(line);
405                  data = line.value;
406              }
407          }
408          
409          if (!hasmore)
410              return doc;
411          
412          while(hasmore || data != "") {
413              if (data.substr(0, 1) == "#")
414                  continue;
415              
416              var colorData = /\s*(\d{1,3})\s*(\d{1,3})\s*(\d{1,3})\s*([\s\S]*)/
417                              .exec(data);
418              
419              if (!colorData)
420                  continue;
421              
422              var r = parseInt(colorData[1]);
423              var g = parseInt(colorData[2]);
424              var b = parseInt(colorData[3]);
425              var colorName = colorData[4];
426              
427              var color = doc.createElement("color");
428              color.setAttribute("r", r);
429              color.setAttribute("g", g);
430              color.setAttribute("b", b);
431              color.setAttribute("name", colorName);
432              colorsElt.appendChild(color);
433              
434              hasmore = istream.readLine(line);
435              data = line.value;
436          }
437          
438          istream.close();
439          
440          return doc;    
441      },
442      
443      /**
444      * Return a filename not used in a directory.
445      * 
446      * @param {String} aName
447      * @param {nsiFile} aDir
448      * @param {String} aExt
449      *
450      * @returns {String} the same filename if doesn't exist, otherwise append a integer
451      */
452      ensureFileName: function kh_file_ensureFileName(aName, aDir, aExt) {
453          var filename = aName.replace(/[^\w_.-]/g, "");
454          if (filename == "")
455              filename = "kh_palette";
456          
457          if (aExt === undefined)
458              aExt = "";
459          
460          var dir = aDir.clone();
461          var clone = aDir.clone()
462          
463          clone.append(filename + aExt);
464          var exist = clone.exists();
465          if (!exist)
466              return filename + aExt;
467          
468          var othername;
469          var i = 0;
470          while (exist) {
471              othername = "" + filename + i;
472              clone = aDir.clone();
473              clone.append(othername + aExt);
474              exist = clone.exists();
475              i++;
476          }
477         
478          return othername + aExt;
479      },
480      
481      /**
482       * Convert a XML palette to a Gimp Palette format
483       *
484       * @param {XMLDocument} aDoc
485       * @param {String} aName optional
486       * @param {Int} aColumns optional
487       * @param {String[]} aComments optional
488       *
489       * @returns {String}
490       */
491      xmlPaletteToGimp:
492      function kh_file_xmlPaletteToGimp(aDoc, aName, aColumns, aComments) {
493          if (aName === undefined || aName === null) {
494              aName = aDoc.getElementsByTagName("name")[0].textContent;
495          }
496          
497          if (aColumns === undefined || aColumns === null) {
498              aColumns = aDoc.getElementsByTagName("columns")[0].textContent;
499          }
500          
501          var txt = "GIMP Palette\n" +
502                    "Name: " + aName + "\n" +
503                    "Columns: " + aColumns + "\n";
504          
505          if (aComments === undefined || aComments === null) {
506              aComments = [];
507              var comments = aDoc.getElementsByTagName("comments")[0]
508                                 .getElementsByTagName("comment");
509              for (let i = 0, l = comments.length; i < l; i++) {
510                  aComments.push(comments[i].textContent);
511              }
512          } else {
513              aComments = aComments.split("\n");
514          }
515          // we want a "#" bfore the list of colors
516          if (aComments.length > 0 && aComments[aComments.length -1] !=  "")
517              aComments.push("");
518          
519          var commentsTxt = "";
520          for (let i = 0, l = aComments.length; i < l; i++) {
521              let comment = aComments[i];
522              if (comment.length > 0 && comment[0] != " ")
523                  comment = " " + comment;
524              commentsTxt += "#" + comment + "\n";
525          }
526          txt += (commentsTxt == "") ? "#\n" : commentsTxt;
527          
528          var colors = aDoc.getElementsByTagName("color");
529          for (let i = 0, l = colors.length; i < l; i++) {
530              let color = colors[i];
531              let r = "" + color.getAttribute("r");
532              if (r.length == 1) {
533                  r = "  " + r;
534              } else if (r.length == 2) {
535                  r = " " + r;
536              }
537              let g = "" + color.getAttribute("g");
538              if (g.length == 1) {
539                  g = "  " + g;
540              } else if (g.length == 2) {
541                  g = " " + g;
542              }
543              let b = "" + color.getAttribute("b");
544              if (b.length == 1) {
545                  b = "  " + b;
546              } else if (b.length == 2) {
547                  b = " " + b;
548              }
549              let colorName = color.getAttribute("name");
550              
551              txt += r + " " + g + " " + b;
552              
553              if (colorName && colorName != "")
554                  txt += " " + colorName;
555              
556              txt += "\n";
557          }
558          
559          return txt;
560      },
561      
562      /**
563       * write to a nsIFile. The file must exists and be writable, otherwise it
564       * will not be created, and the method will just returns false.
565       *
566       * @param {nsIFile} aFile
567       * @param {String} aStr
568       * @param {Int} aFlags default: 0x02 | 0x08 | 0x20
569       * @param {Int} aPerm default: 0644
570       *
571       * @returns {Boolean} true if success
572       */
573      writeFile: function kh_file_writeFile(aFile, aStr, aFlags, aPerm) {
574          if (!aFile.exists() || ! aFile.isWritable())
575              return false;
576          
577          if (typeof(aFlags) == "undefined")
578              // default = overwrite. Use 0x02 | 0x10 for appending
579              aFlags = 0x02 | 0x08 | 0x20;
580          
581          if (typeof(aPerm) == "undefined")
582              aPerm = 0644;
583          
584          var foStream = Cc["@mozilla.org/network/file-output-stream;1"].
585                         createInstance(Ci.nsIFileOutputStream);
586          foStream.init(aFile, aFlags, aPerm, 0);
587          foStream.write(aStr, aStr.length);
588          foStream.close();
589          
590          return true;
591      },
592      
593      /**
594       * remove illegal characters in a filename on various plateform.
595       * If the result is empty, aDefault is returned.
596       * <pre>
597       * white characters at beginning and end are removed
598       * \ / | " * : ? < > ~ \n \r are replaced by _
599       * \0 \b \t \v \f \r \n are removed
600       * If the filename begins by ., it's remove too (hidden files on nix)
601       * </pre>
602       * 
603       * see function validateFileName(aFileName) in
604       * toolkit/content/contentAreaUtils.js
605       * 
606       * @param {String} aStr
607       * @param {String} aDefault
608       * 
609       * @returns {String}
610       */
611      escapeFileName: function kh_file_escapeFileName(aStr, aDefault) {
612          if (aDefault === undefined)
613              aDefault = "myfile";
614          
615          var re = /^\s+/;
616          var name = aStr.replace(re, "");
617          
618          re = /\s+$/;
619          name = name.replace(re, "");
620          
621          re = /[\\\/\|\"\*\:\?\<\>~]+/g;
622          name = name.replace(re, "_");
623          
624          re = /[\0\b\t\v\f\r\n]+/g;
625          name = name.replace(re, "");
626          
627          re = /^\.+/;
628          name = aStr.replace(re, "");
629          
630          if (name == "")
631              name = aDefault;
632          
633          return name;
634      }
635  };
636