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 gContactSync.
 15  *
 16  * The Initial Developer of the Original Code is
 17  * Josh Geenen <gcontactsync@pirules.org>.
 18  * Portions created by the Initial Developer are Copyright (C) 2008-2009
 19  * the Initial Developer. All Rights Reserved.
 20  *
 21  * Contributor(s):
 22  *
 23  * Alternatively, the contents of this file may be used under the terms of
 24  * either the GNU General Public License Version 2 or later (the "GPL"), or
 25  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 26  * in which case the provisions of the GPL or the LGPL are applicable instead
 27  * of those above. If you wish to allow use of your version of this file only
 28  * under the terms of either the GPL or the LGPL, and not to allow others to
 29  * use your version of this file under the terms of the MPL, indicate your
 30  * decision by deleting the provisions above and replace them with the notice
 31  * and other provisions required by the GPL or the LGPL. If you do not delete
 32  * the provisions above, a recipient may use your version of this file under
 33  * the terms of any one of the MPL, the GPL or the LGPL.
 34  *
 35  * ***** END LICENSE BLOCK ***** */
 36 
 37 if (!com) var com = {}; // A generic wrapper variable
 38 // A wrapper for all GCS functions and variables
 39 if (!com.gContactSync) com.gContactSync = {};
 40 
 41 window.addEventListener("load",
 42   /** Initializes the FileIO class when the window has finished loading */
 43   function gCS_FileIOLoadListener(e) {
 44     com.gContactSync.FileIO.init();
 45   },
 46 false);
 47 
 48 /**
 49  * A class for reading, writing, and appending to files with an nsIFile for
 50  * storing data, authentication info, and logs.
 51  * @class
 52  */
 53 com.gContactSync.FileIO = {
 54   /** An nsIFile where the log is written */
 55   mLogFile: null,
 56   /** File names */
 57   fileNames: {
 58     /** Stores AB backups and the gContactSync log */
 59     FOLDER_NAME:       "gcontactsync",
 60     /** Stores the log from the last sync */
 61     LOG_FILE:          "gcontactsync_log.txt",
 62     /** The folder where AB backups are stored (inside FOLDER_NAME) */
 63     AB_BACKUP_DIR:     "address_book_backups",
 64     /** The folder where Google backups are stored (inside FOLDER_NAME) */
 65     GOOGLE_BACKUP_DIR: "google_feed_backups",
 66     /** The folder where preferences backups are stored (inside FOLDER_NAME) */
 67     PREFS_BACKUP_DIR:  "preferences_backups",
 68     /** The file where TB preferences are stored */
 69     PREFS_JS:          "prefs.js"
 70   },
 71   /**
 72    * Initializes the files contained in this class.
 73    * Also creates the log directory, if necessary.
 74    */
 75   init: function FileIO_init() {
 76     var directory      = this.getProfileDirectory(),
 77         abBackupDir    = this.getProfileDirectory(),
 78         gBackupDir     = this.getProfileDirectory(),
 79         prefsBackupDir = this.getProfileDirectory();
 80     // Make sure the profile directory exists (if not something is very wrong)
 81     this.checkDirectory(directory);
 82     // Append the gcontactsync folder onto the directory and make sure it exists
 83     directory.append(this.fileNames.FOLDER_NAME);
 84     this.checkDirectory(directory);
 85     // make sure the AB backup dir exists
 86     abBackupDir.append(this.fileNames.FOLDER_NAME);
 87     abBackupDir.append(this.fileNames.AB_BACKUP_DIR);
 88     this.checkDirectory(abBackupDir);
 89     // make sure the Google backup dir exists
 90     gBackupDir.append(this.fileNames.FOLDER_NAME);
 91     gBackupDir.append(this.fileNames.GOOGLE_BACKUP_DIR);
 92     this.checkDirectory(gBackupDir);
 93     // make sure the prefs backup dir exists
 94     prefsBackupDir.append(this.fileNames.FOLDER_NAME);
 95     prefsBackupDir.append(this.fileNames.PREFS_BACKUP_DIR);
 96     this.checkDirectory(prefsBackupDir);
 97     this.mLogFile   = directory;
 98     this.mLogFile.append(this.fileNames.LOG_FILE);
 99     if (this.mLogFile.exists() && !this.mLogFile.isWritable()) {
100       com.gContactSync.alertError(com.gContactSync.StringBundle.getStr("logNotWritable") +
101                                   "\n" + this.mLogFile.path);
102       throw "Error - cannot write to the log file: " +
103             com.gContactSync.FileIO.mLogFile.path;
104     }
105   },
106   /**
107    * Checks that a directory corresponding to an nsIFile exists, and creates it
108    * if necessary.
109    * This will throw an error if the directory could not be created, is not a
110    * directory, or if the directory is not writeable.
111    */
112   checkDirectory: function FileIO_checkDirectory(aDirectory) {
113     // If the directory doesn't exist yet then create it
114     if (!aDirectory.exists()) {
115       // create the directory (type = 1) - rw for the user and r for others
116       try { aDirectory.create("1", parseInt("755", 8)); } catch (e) {}
117       // if it still doesn't exist let the user know, then quit
118       if (!aDirectory.exists()) {
119         com.gContactSync.alertError(com.gContactSync.StringBundle.getStr("couldntMkDir") +
120                                     "\n" + aDirectory.path);
121         throw "Error - could not create the following directory: " +
122               aDirectory.path;
123       }
124     }
125     if (!aDirectory.isDirectory()) {
126       com.gContactSync.alertError(com.gContactSync.StringBundle.getStr("isNotDir") +
127                                   "\n" + aDirectory.path);
128       throw "Error - " + aDirectory.path + " is not a directory.";
129     }
130     if (!aDirectory.isWritable()) {
131       com.gContactSync.alertError(com.gContactSync.StringBundle.getStr("notWritable") +
132                                   "\n" + aDirectory.path);
133       throw "Error - Cannot write to the following directory: " +
134             aDirectory.path;
135     }
136   },
137   /**
138    * Gets the the file in the extension's directory with the given name.
139    * @param aName {string} The name of the file to get.
140    * @returns {nsIFile} The file with the given name in the extension's
141    *                   directory.
142    */
143   getFileInExtDir: function FileIO_getFileInExtDir(aName) {
144     var MY_ID = "gContactSync@pirules.net",
145         em    = Components.classes["@mozilla.org/extensions/manager;1"]
146                        .getService(Components.interfaces.nsIExtensionManager);
147     return em.getInstallLocation(MY_ID).getItemFile(MY_ID, aName);
148   },
149   /**
150    * Returns an nsIFile of the current profile directory of the application.
151    * @returns {nsIFile} The current profile directory of the application.
152    */
153   getProfileDirectory: function FileIO_getProfileDirectory() {
154     return Components.classes["@mozilla.org/file/directory_service;1"]
155                      .getService(Components.interfaces.nsIProperties)
156                      .get("ProfD", Components.interfaces.nsIFile);
157   },
158   /**
159    * Opens the given file and returns an array of the lines within it.
160    * @param aFile  {nsIFile} The nsIFile to read.
161    * @returns {array} An array of the lines in the file or [] if there is an
162    *                 error.
163    */
164   readFile: function FileIO_readFile(aFile) {
165     this.checkFile(aFile);
166     if (!aFile.exists())
167       return [];
168     try {
169       var line    = {},
170           lines   = [],
171           hasmore,
172           istream = Components.classes["@mozilla.org/network/file-input-stream;1"]
173                               .createInstance(Components.interfaces.nsIFileInputStream);
174       istream.init(aFile, 0x01, 0444, 0);
175       istream.QueryInterface(Components.interfaces.nsILineInputStream);
176 
177       do {
178         hasmore = istream.readLine(line);
179         lines.push(line.value);
180       } while (hasmore);
181 
182       istream.close();
183       return lines;
184     }
185     catch (e) {
186       throw "Unable to read from file: " + aFile + "\n" + e;
187     }
188   },
189   /**
190    * Writes the string data to the nsIFile aFile.
191    * NOTE: This will delete any existing text in the file.
192    * @param aFile  {nsIFile} The nsIFile to which the string is written.
193    * @param aData  {string}  The string of data to write to the file.
194    * @returns {boolean} True if there is no error.
195    */
196   writeToFile: function FileIO_writeToFile(aFile, aData) {
197     this.checkFile(aFile);
198     if (!aData)
199       return false;
200     try {
201       var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
202                                .createInstance(Components.interfaces.nsIFileOutputStream);
203       foStream.init(aFile, 0x02 | 0x08 | 0x20, 0666, 0);
204       foStream.write(aData, aData.length);
205       foStream.close();
206       return true;
207     }
208     catch (e) {
209       throw "Unable to write '" + aData + "' to file: " + aFile + "\n" + e;
210     }
211     return false;
212   },
213   /**
214    * Appends the string aData to the nsIFile aFile.
215    * @param aFile {nsIFile} The nsIFile to which the string is appended.
216    * @param aData {string} The string of data to append to the file.
217    * @returns {boolean} True if there is no error.
218    */
219   appendToFile: function FileIO_appendToFile(aFile, aData) {
220     if (!aData)
221       return false;
222     this.checkFile(aFile);
223     try {
224       var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
225                                .createInstance(Components.interfaces.nsIFileOutputStream);
226       if (aFile.exists())
227         foStream.init(aFile, 0x02 | 0x10, 0666, 0);
228       else
229         foStream.init(aFile, 0x02 | 0x08 | 0x20, 0666, 0);
230       foStream.write(aData, aData.length);
231       foStream.close();
232       return true;
233     }
234     catch (e) {
235       throw "Unable to append '" + aData + "' to file: " + aFile + + "\n" + aFile.path + "\n" + e;
236     }
237     return false;
238   },
239   /**
240    * Copies the contents of one file to another using the readFile and writeFile
241    * methods of this class.
242    * If there is an error reading the source file, or if one or more of the
243    * given files are invalid an error will be thrown.
244    * NOTE: The contents of the destination file are removed permanently.
245    *
246    * @param aSrc  {nsIFile} The source file.
247    * @param aDest {nsIFile} The destination file.  NOTE: the contents of this
248    *                        file before the copy will be permanently removed.
249    * @returns {boolean} True if the copy finished successfully
250    */
251   copyFile: function FileIO_copyFile(aSrc, aDest) {
252     this.checkFile(aSrc);
253     this.checkFile(aDest);
254     // read the file into an array
255     var lines = this.readFile(aSrc) || [];
256     // write the array into a file
257     this.writeToFile(aDest, lines.join("\n"));
258     return true;
259   },
260   /**
261    * Checks that an argument is not null, is an instance of nsIFile, and that,
262    * if it exists, that it is a file (not a directory).
263    * @param aFile   {nsIFile} The file to check.
264    * @param aCaller {string}  The name of the calling method.
265    */
266   checkFile: function FileIO_checkFile(aFile) {
267     if (!aFile || !aFile instanceof Components.interfaces.nsIFile || (aFile.exists() && !aFile.isFile()))
268       throw "Invalid File: " + aFile + " sent to the '" + this.checkFile.caller +
269             "' method" + com.gContactSync.StringBundle.getStr("pleaseReport");
270   }
271 };
272