home *** CD-ROM | disk | FTP | other *** search
/ Revista do CD-ROM 123 / cdrom123.iso / essenc / extens / efore / ForecastFox.xpi / components / nsWeatherfox.js < prev   
Encoding:
Text File  |  2004-10-16  |  48.3 KB  |  1,410 lines

  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 weatherfox.mozdev.org code.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  * Jon Stritar <jstritar@MIT.EDU>.
  18.  * Portions created by the Initial Developer are Copyright (C) 2004
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Jon Stritar <jstritar@MIT.EDU>
  23.  *   Richard Klein <richwklein@mchsi.com>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. /* Frequency Constants */
  40. const FREQ_CURRENT = 30;
  41. const FREQ_FORECAST = 120;
  42. const FREQ_LINKS = 720;
  43.  
  44. /* Last Update Prefs */
  45. const PREF_CURRENT_LAST = "current.last";
  46. const PREF_FORECAST_LAST = "forecast.last";
  47. const PREF_LINKS_LAST = "links.last";
  48.  
  49. /* Cache Prefs */
  50. const PREF_CURRENT_CACHE = "current.cache";
  51. const PREF_FORECAST_CACHE = "forecast.cache";
  52. const PREF_LINKS_CACHE = "links.cache";
  53.  
  54. /* Notification Prefs */
  55. const PREF_SLIDER_FREQ = "slider.freq";
  56. const PREF_SLIDER_COUNT = "slider.count";
  57.  
  58. /* Profile Location */
  59. const PROFILE_FILE = "profiles.xml";
  60.  
  61. /******************************************************************************
  62.  * nsWfUpdateService class constructor
  63.  ******************************************************************************/
  64. function nsWeatherfoxUpdate()
  65. {
  66.   //internal variables
  67.   this.mProductCode = "xoap";
  68.   this.mPartnerId     = "1005561467";
  69.   this.mLicenseKey     = "a388d0d4a8a3f714";
  70.   this.mServer         = "http://xoap.weather.com/weather/local/";
  71.   this.mTimer         = null;
  72.   this.mTimeOut        = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
  73.   this.mHttpRequest    = null;
  74.   this.mPrefs        = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("weatherfox.");
  75.   this.mObsService    = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
  76.   this.mDOMParser    = Components.classes["@mozilla.org/xmlextras/domparser;1"].createInstance(Components.interfaces.nsIDOMParser);
  77.   this.mFileIO      = new fileIO();
  78.   this.mLoadingProf = false;
  79.  
  80.   //set internal arrays for storage
  81.   this.mLastError   = {};
  82.   this.mLoc         = {};
  83.   this.mCurrent     = {};
  84.   this.mForecast     = new Array();
  85.   this.mPartDay        = new Array();
  86.   this.mPartNight    = new Array();
  87.   this.mLinks         = new Array();
  88.  
  89.  
  90.   //migrate old preferences
  91.   this.migratePrefs();
  92.  
  93.   //add shutdown observer
  94.   this.mObsService.addObserver(this, "xpcom-shutdown", false);
  95.   this.mObsService.addObserver(this, "weatherfox:profile:start", false);
  96.   this.mObsService.addObserver(this, "weatherfox:profile:end", false);
  97.   this.mObsService.addObserver(this, "weatherfox:profile:error", false);
  98.  
  99.   //add prefernce observer
  100.   var pbi = this.mPrefs.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
  101.   pbi.addObserver("", this, false);
  102.  
  103.   //load data
  104.   this.updateData();
  105. }
  106.  
  107. nsWeatherfoxUpdate.prototype =
  108. {
  109.   //readonly attributes fo UI
  110.   get productCode()    { return this.mProductCode; },
  111.   get partnerId()      { return this.mPartnerId;   },
  112.   get licenseKey()     { return this.mLicenseKey;  },
  113.   get server()         { return this.mServer;      },
  114.  
  115.   getKeys: function(aName, aCount)
  116.   {
  117.     var asKeys = new Array();
  118.     var sKey = null;
  119.     switch (aName) {
  120.       case "lastError":
  121.         for (sKey in this.mLastError) asKeys.push(sKey);
  122.         aCount.value = asKeys.length;
  123.         break;
  124.       case "location":
  125.         for (sKey in this.mLoc) asKeys.push(sKey);
  126.         aCount.value = asKeys.length;
  127.         break;
  128.       case "current":
  129.         for (sKey in this.mCurrent) asKeys.push(sKey);
  130.         aCount.value = asKeys.length;
  131.         break;
  132.       case "forecast":
  133.         for (sKey in this.mForecast[0]) asKeys.push(sKey);
  134.         aCount.value = asKeys.length;
  135.         break;
  136.       case "day":
  137.         for (sKey in this.mPartDay[0]) asKeys.push(sKey);
  138.         aCount.value = asKeys.length;
  139.         break;
  140.       case "night":
  141.         for (sKey in this.mPartNight[0]) asKeys.push(sKey);
  142.         aCount.value = asKeys.length;
  143.         break;
  144.       case "links":
  145.         for (sKey in this.mLinks[0]) asKeys.push(sKey);
  146.         aCount.value = asKeys.length;
  147.         break;
  148.     }
  149.     asKeys.sort();
  150.     return asKeys;
  151.   },
  152.  
  153.   getLinkCount: function()
  154.   {
  155.     return this.mLinks.length;
  156.   },
  157.  
  158.   getValue: function(aName, aKey, aIndex)
  159.   {
  160.     var val = "";
  161.     switch (aName) {
  162.       case "lastError":
  163.         val = this.mLastError[aKey];
  164.             break;
  165.       case "location":
  166.         val = this.mLoc[aKey];
  167.             break;
  168.       case "current":
  169.         val = this.mCurrent[aKey];
  170.             break;
  171.       case "forecast":
  172.         val = this.mForecast[aIndex][aKey];
  173.             break;
  174.       case "day":
  175.         val = this.mPartDay[aIndex][aKey];
  176.             break;
  177.       case "night":
  178.         val = this.mPartNight[aIndex][aKey];
  179.             break;
  180.       case "links":
  181.         val = this.mLinks[aIndex][aKey];
  182.             break;
  183.     }
  184.     return val;
  185.   },
  186.  
  187.   load: function(aForced)
  188.   {
  189.     //reset last prefs if forced
  190.     if (aForced) {
  191.       this.mPrefs.clearUserPref(PREF_CURRENT_LAST);
  192.       this.mPrefs.clearUserPref(PREF_FORECAST_LAST);
  193.       this.mPrefs.clearUserPref(PREF_LINKS_LAST);
  194.     }
  195.  
  196.     //update the data
  197.     this.updateData();
  198.   },
  199.  
  200.   //main internal function to load data from server
  201.   updateData: function()
  202.   {
  203.  
  204.     // If the locid hasn't been set yet, prompt for it and bail out.
  205.     if (this.mPrefs.getCharPref("locid") == "00000") {
  206.       var windowManager = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService();
  207.       var windowManagerInterface = windowManager.QueryInterface( Components.interfaces.nsIWindowMediator);    
  208.       var topWindow = windowManagerInterface.getMostRecentWindow("navigator:browser");
  209.       if (!topWindow)
  210.         topWindow = window.openDialog("chrome://browser/content/browser.xul", "_blank", "chrome,all,dialog=no", "about:blank", null, null);
  211.       topWindow.openDialog("chrome://weatherfox/content/options.xul", "options", "chrome");
  212.     }
  213.     else {
  214.       debug("Updating the data...");
  215.  
  216.       // Notify observers that updating has begun.
  217.       this.mObsService.notifyObservers(null, "weatherfox:update:start", "");
  218.  
  219.       //clear old errors
  220.       this.mLastError = {};
  221.  
  222.       // The array contains true if the data is being refreshed from the server.
  223.       // If false, load the data from the cache.
  224.       var refresh_needed             = new Array();
  225.       refresh_needed["CURRENT"]     = this.shouldRefresh("CURRENT");
  226.       refresh_needed["FORECAST"]     = this.shouldRefresh("FORECAST");
  227.       refresh_needed["LINKS"]     = this.shouldRefresh("LINKS");
  228.  
  229.       // Get the cached data and the server data.
  230.       this.getCachedData(refresh_needed);
  231.       this.getServerData(refresh_needed);
  232.  
  233.       if (refresh_needed["CURRENT"] || refresh_needed["FORECAST"] || refresh_needed["LINKS"])
  234.         this.mTimeOut.initWithCallback(this, 10 * 1000, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
  235.  
  236.     }
  237.   },
  238.  
  239.   getCachedData: function(refresh_needed)
  240.   {
  241.     // First find out what files we need to load data from.
  242.     var file_array     = new Array();
  243.     var failed        = new Array();
  244.     for (var value in refresh_needed) {
  245.       if (!refresh_needed[value]) {
  246.         var file = this.mPrefs.getCharPref(eval("PREF_" + value + "_CACHE"));
  247.         if (!file_array[file])
  248.           file_array[file] = new Array();
  249.         file_array[file][value] = true;
  250.       }
  251.     }
  252.  
  253.     // Then read the files.
  254.     for (file in file_array) {
  255.       var text     = this.mFileIO.read(file);
  256.  
  257.       if (!text) {
  258.         for (value in file_array[file])
  259.           if (file_array[file][value])
  260.             failed[value] = true;
  261.         continue;
  262.       }
  263.  
  264.       var doc = this.mDOMParser.parseFromString(text, "text/xml");
  265.       this.parseResponse(doc, file_array[file], false);
  266.  
  267.       debug("Refreshing " + ((file_array[file]["CURRENT"]) ? "current" : "") + " " + ((file_array[file]["FORECAST"]) ? "forecast" : "") + " " + ((file_array[file]["LINKS"]) ? "links" : "") + " from " + file);
  268.     }
  269.  
  270.     if (failed["CURRENT"] || failed["FORECAST"] || failed["LINKS"]) {
  271.       debug("Read failed for " + ((failed["CURRENT"]) ? "current" : "") + " " + ((failed["FORECAST"]) ? "forecast" : "") + " " + ((failed["LINKS"]) ? "links" : "") + " ...");
  272.       this.getServerData(failed);
  273.     } else if (!refresh_needed["CURRENT"] && !refresh_needed["FORECAST"] && !refresh_needed["LINKS"]) {
  274.       debug("Done updating.");
  275.       this.scheduleUpdate();
  276.     }
  277.   },
  278.  
  279.   getServerData: function(refresh_needed)
  280.   {
  281.     var mComponent = this;
  282.  
  283.     // Bail out if nothing needs to be refreshed.
  284.     if (!refresh_needed["CURRENT"] && !refresh_needed["FORECAST"] && !refresh_needed["LINKS"])
  285.       return;
  286.  
  287.     // Construct the query url.
  288.     var query = this.mServer + this.mPrefs.getCharPref("locid") + "?";
  289.  
  290.     // Only query the server for things that we need.
  291.     if (refresh_needed["CURRENT"])
  292.       query += "cc=*&";
  293.     if (refresh_needed["FORECAST"])
  294.       query += "dayf=10&";
  295.     if (refresh_needed["LINKS"])
  296.       query += "link=xoap&";
  297.  
  298.     // Add the unit and the license info.
  299.     query += "unit=" + (this.mPrefs.getBoolPref("celcius") ? "m&" : "s&");
  300.     query += "prod=" + this.mProductCode + "&par=" + this.mPartnerId + "&key=" + this.mLicenseKey;
  301.  
  302.  
  303.     // Replace query with test location:
  304.     //query = "http://web.mit.edu/jstritar/www/test.xml";
  305.  
  306.     debug("Updating " + ((refresh_needed["CURRENT"]) ? "current" : "") + " " + ((refresh_needed["FORECAST"]) ? "forecast" : "") + " " + ((refresh_needed["LINKS"]) ? "links" : "") + " from server.");
  307.  
  308.     this.mHttpRequest = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Components.interfaces.nsIXMLHttpRequest);
  309.     this.mHttpRequest.overrideMimeType("text/xml");
  310.     this.mHttpRequest.onload = function () {
  311.       // It didn't timeout, so cancel that timer.
  312.       mComponent.mTimeOut.cancel();
  313.  
  314.       var doc  = this.responseXML;
  315.  
  316.       if (mComponent.handleError(doc, this.status))
  317.         return;
  318.  
  319.       // Cache the response, then parse it and update our data.
  320.       mComponent.saveResponse(this.responseText, refresh_needed);
  321.       mComponent.parseResponse(doc, refresh_needed, true);
  322.  
  323.       debug("Done updating");
  324.  
  325.       // Then schedule an update.
  326.       mComponent.scheduleUpdate();
  327.  
  328.       return;
  329.     }
  330.     this.mHttpRequest.open("GET", query, true);
  331.     // Should insert some error handling for when the send timeouts.
  332.     this.mHttpRequest.send(null);
  333.  
  334.   },
  335.  
  336.   handleError: function(doc, status)
  337.   {
  338.     if (status > 200) {
  339.       this.mLastError["type"] = "0";
  340.       this.mLastError["msg"] = "Network Error";
  341.  
  342.       debug ("Error: " + this.mLastError["type"] + this.mLastError["msg"]);
  343.  
  344.           // Then notify the observers of the error.
  345.       this.mObsService.notifyObservers(null, "weatherfox:update:error", "");
  346.       return true;
  347.     }
  348.  
  349.     if (doc.getElementsByTagName("error").length > 0) {
  350.       var error = doc.getElementsByTagName("err")[0];
  351.       debug("Error: " + error.getAttribute("type") + " " + error.firstChild.nodeValue);
  352.  
  353.       // Record error.
  354.       this.mLastError["type"] = error.getAttribute("type");
  355.       this.mLastError["msg"]  = error.firstChild.nodeValue;
  356.  
  357.       debug ("Error: " + this.mLastError["type"] + this.mLastError["msg"]);
  358.  
  359.       // Then notify the observers of the error.
  360.       this.mObsService.notifyObservers(null, "weatherfox:update:error", "");
  361.  
  362.       return true;
  363.     }
  364.  
  365.     return false;
  366.   },
  367.  
  368.   saveResponse: function(text, refresh_needed)
  369.   {
  370.     var file;
  371.     var time = (new Date()).valueOf();
  372.     var profile = this.mPrefs.getCharPref("profile");
  373.  
  374.     // Assign file names this way so that the links one gets held longest.
  375.     if (refresh_needed["LINKS"])
  376.       file = "linkscache-" + profile + ".xml";
  377.     else if (refresh_needed["FORECAST"])
  378.       file = "forecastcache-" + profile + ".xml";
  379.     else if (refresh_needed["CURRENT"])
  380.       file = "currentcache-" + profile + ".xml";
  381.  
  382.     // Then save the file...
  383.     this.mFileIO.write(file, text);
  384.  
  385.     // And update the CACHE prefs.
  386.     for (var value in refresh_needed) {
  387.       if (refresh_needed[value]) {
  388.         this.mPrefs.setCharPref(eval("PREF_" + value + "_CACHE"), file);
  389.         this.mPrefs.setCharPref(eval("PREF_" + value + "_LAST"), time);
  390.       }
  391.     }
  392.   },
  393.  
  394.   parseResponse: function(doc, contains, server)
  395.   {
  396.     //XXX should have a better way of parsing this without having to know the structure of the XML
  397.     var w,x,y,z;
  398.     var time          = (new Date()).valueOf();
  399.  
  400.     // TODO: clear this.mLoc
  401.     // Parse loc info.
  402.     this.mLoc = {};
  403.     var data_needed = new Array("locale", "ut", "ud", "us", "up", "ur", "dnam", "tm", "lat", "lon", "sunr", "suns", "zone");
  404.  
  405.     for (var value in data_needed)
  406.       this.mLoc[data_needed[value]] = doc.getElementsByTagName(data_needed[value])[0].firstChild.nodeValue;
  407.  
  408.     this.addUnits("loc");
  409.  
  410.     if (contains["CURRENT"]) {
  411.       // Clear old data:
  412.       this.mCurrent = {};
  413.  
  414.       // Then parse in the new data:
  415.       var cc = doc.getElementsByTagName("cc")[0].childNodes;
  416.  
  417.       for (x = 0; x < cc.length; x++) {
  418.         if (cc[x].nodeName == "#text")
  419.           continue;
  420.         if (cc[x].nodeName == "bar" || cc[x].nodeName == "wind" || cc[x].nodeName == "uv") {
  421.           var next_nodes = cc[x].childNodes;
  422.           for (w=0; w < next_nodes.length; w++) {
  423.             if (next_nodes[w].nodeName == "#text")
  424.               continue;
  425.             this.mCurrent[cc[x].nodeName + next_nodes[w].nodeName] = next_nodes[w].firstChild.nodeValue;
  426.           }
  427.         }
  428.         else
  429.           this.mCurrent[cc[x].nodeName] = cc[x].firstChild.nodeValue;
  430.       }
  431.  
  432.       this.addUnits("current");
  433.       this.mObsService.notifyObservers(null, "weatherfox:update:end:current", "");
  434.  
  435.       //do alert notification
  436.       try {
  437.         var sliderfreq = this.mPrefs.getIntPref("slider.freq");
  438.         var slidercount = this.mPrefs.getIntPref("slider.count");
  439.         if (sliderfreq != 0 && server) {
  440.           this.mPrefs.setIntPref("slider.count", (slidercount + 1));
  441.           if (slidercount >= sliderfreq) {
  442.             // Show the slider and reset it's count.
  443.             var alertTitle = this.mCurrent["t"] + ", " + this.mCurrent["tmp"];
  444.             var alertText = this.mLoc["dnam"];
  445.             var alerts = Components.classes["@mozilla.org/alerts-service;1"].getService(Components.interfaces.nsIAlertsService);
  446.             alerts.showAlertNotification("chrome://weatherfox/skin/images/large/" + this.mCurrent["icon"] + ".png", alertTitle, alertText, true, "", this);
  447.             this.mPrefs.setIntPref("slider.count", "1");
  448.           }
  449.         }
  450.       } catch(e) {}
  451.     }
  452.  
  453.     if (contains["FORECAST"]) {
  454.       // Clear old forecast data:
  455.       this.mForecast = new Array();
  456.       this.mPartDay = new Array();
  457.       this.mPartNight = new Array();
  458.  
  459.       //get all the days in the forecast
  460.       var days = doc.getElementsByTagName("day");
  461.       for (x=0; x < days.length; x++) {
  462.         var day_nodes = days[x].childNodes;
  463.         var data = {};
  464.         next_nodes = null;
  465.  
  466.         data["day"] = days[x].getAttribute("t");
  467.         data["date"] = days[x].getAttribute("dt");
  468.  
  469.         //loop through nodes in day
  470.         for (y=0; y < day_nodes.length; y++) {
  471.           if (day_nodes[y].nodeName == "#text")
  472.             continue;
  473.           //get part data
  474.           if (day_nodes[y].nodeName == "part") {
  475.             var part_nodes = day_nodes[y].childNodes;
  476.             var part = {};
  477.             if (day_nodes[y].getAttribute("p") == "d") {
  478.               //loop through nodes in part
  479.               for (z=0; z < part_nodes.length; z++) {
  480.                 if (part_nodes[z].nodeName == "#text")
  481.                   continue;
  482.                 if (part_nodes[z].nodeName == "bar" || part_nodes[z].nodeName == "wind" || part_nodes[z].nodeName == "uv") {
  483.                   next_nodes = part_nodes[z].childNodes;
  484.                   for (w=0; w < next_nodes.length; w++) {
  485.                     if (next_nodes[w].nodeName == "#text")
  486.                       continue;
  487.                     part[part_nodes[z].nodeName + next_nodes[w].nodeName] = next_nodes[w].firstChild.nodeValue;
  488.                   }
  489.                 }
  490.                 else
  491.                   part[part_nodes[z].nodeName] = part_nodes[z].firstChild.nodeValue;
  492.               }
  493.               this.mPartDay[x] = part;
  494.             }
  495.             else {
  496.               //loop through nodes in part
  497.               for (z=0; z < part_nodes.length; z++) {
  498.                 if (part_nodes[z].nodeName == "#text")
  499.                   continue;
  500.                 if (part_nodes[z].nodeName == "bar" || part_nodes[z].nodeName == "wind" || part_nodes[z].nodeName == "uv") {
  501.                   next_nodes = part_nodes[z].childNodes;
  502.                   for (w=0; w < next_nodes.length; w++) {
  503.                     if (next_nodes[w].nodeName == "#text")
  504.                       continue;
  505.                     part[part_nodes[z].nodeName + next_nodes[w].nodeName] = next_nodes[w].firstChild.nodeValue;
  506.                   }
  507.                 }
  508.                 else
  509.                   part[part_nodes[z].nodeName] = part_nodes[z].firstChild.nodeValue;
  510.               }
  511.               this.mPartNight[x] = part;
  512.             }
  513.           }
  514.           else {
  515.             if (day_nodes[y].nodeName == "bar" || day_nodes[y].nodeName == "wind" || day_nodes[y].nodeName == "uv") {
  516.               next_nodes = day_nodes[y].childNodes;
  517.               for (w=0; w < next_nodes.length; w++) {
  518.                 if (next_nodes[w].nodeName == "#text")
  519.                   continue;
  520.                 data[day_nodes[y].nodeName + next_nodes[w].nodeName] = next_nodes[w].firstChild.nodeValue;
  521.               }
  522.             }
  523.             else
  524.               data[day_nodes[y].nodeName] = day_nodes[y].firstChild.nodeValue;
  525.           }
  526.         }
  527.         this.mForecast[x] = data;
  528.       }
  529.  
  530.       this.addUnits("forecast");
  531.       this.mObsService.notifyObservers(null, "weatherfox:update:end:forecast", "");
  532.     }
  533.  
  534.     if (contains["LINKS"]) {
  535.       // Parse links info.
  536.       this.mLinks = new Array();
  537.  
  538.       var links = doc.getElementsByTagName("lnks");
  539.  
  540.       if (links[0].getAttribute("type") == "prmo")
  541.         links = links[0].getElementsByTagName("link");
  542.  
  543.       for (x = 0; x < links.length; x++) {
  544.         // Create the array for this link
  545.         var link_array         = {};
  546.         link_array["url"]     = links[x].getElementsByTagName("l")[0].firstChild.nodeValue;
  547.         link_array["title"] = links[x].getElementsByTagName("t")[0].firstChild.nodeValue;
  548.  
  549.         // Then insert it into our internal array that holds the links.
  550.         this.mLinks[x] = link_array;
  551.       }
  552.       this.addUnits("links");
  553.       this.mObsService.notifyObservers(null, "weatherfox:update:end:links", "");
  554.     }
  555.   },
  556.  
  557.   addUnits: function(type)
  558.   {
  559.     var deg = String.fromCharCode(176);
  560.  
  561.  
  562.     switch (type) {
  563.       case "loc" :
  564.         this.mLoc["lat"] = addUnit(this.mLoc["lat"], deg);
  565.         this.mLoc["lon"] = addUnit(this.mLoc["lon"], deg);
  566.         break;
  567.       case "current" :
  568.         this.mCurrent["tmp"]       = addUnit(this.mCurrent["tmp"], deg + this.mLoc["ut"]);
  569.         this.mCurrent["flik"]       = addUnit(this.mCurrent["flik"], deg + this.mLoc["ut"]);
  570.         this.mCurrent["barr"]       = addUnit(this.mCurrent["barr"], this.mLoc["ur"]);
  571.         this.mCurrent["winds"]       = addUnit(this.mCurrent["winds"], this.mLoc["us"]);
  572.         this.mCurrent["windgust"] = addUnit(this.mCurrent["windgust"], this.mLoc["us"]);
  573.         this.mCurrent["windd"]       = addUnit(this.mCurrent["windd"], deg);
  574.         this.mCurrent["hmid"]       = addUnit(this.mCurrent["hmid"], "%");
  575.         this.mCurrent["vis"]       = addUnit(this.mCurrent["vis"], this.mLoc["ud"]);
  576.         this.mCurrent["dewp"]       = addUnit(this.mCurrent["dewp"], deg + this.mLoc["ut"]);
  577.         break;
  578.       case "forecast" :
  579.         for (var x = 0; x < this.mForecast.length; x++) {
  580.           this.mForecast[x]["hi"]         = addUnit(this.mForecast[x]["hi"], deg + this.mLoc["ut"]);
  581.           this.mForecast[x]["low"]         = addUnit(this.mForecast[x]["low"], deg + this.mLoc["ut"]);
  582.           this.mPartDay[x]["winds"]         = addUnit(this.mPartDay[x]["winds"], this.mLoc["us"]);
  583.           this.mPartDay[x]["windgust"]     = addUnit(this.mPartDay[x]["windgust"], this.mLoc["us"]);
  584.           this.mPartDay[x]["windd"]         = addUnit(this.mPartDay[x]["daywindd"], deg);
  585.           this.mPartDay[x]["ppcp"]         = addUnit(this.mPartDay[x]["ppcp"], "%");
  586.           this.mPartDay[x]["hmid"]         = addUnit(this.mPartDay[x]["hmid"], "%");
  587.           this.mPartNight[x]["winds"]     = addUnit(this.mPartNight[x]["winds"], this.mLoc["us"]);
  588.           this.mPartNight[x]["windgust"]     = addUnit(this.mPartNight[x]["windgust"], this.mLoc["us"]);
  589.           this.mPartNight[x]["windd"]     = addUnit(this.mPartNight[x]["windd"], deg);
  590.           this.mPartNight[x]["ppcp"]         = addUnit(this.mPartNight[x]["ppcp"], "%");
  591.           this.mPartNight[x]["hmid"]         = addUnit(this.mPartNight[x]["hmid"], "%");
  592.         }
  593.         break;
  594.       case "links" :
  595.         break;
  596.     }
  597.   },
  598.  
  599.   migratePrefs: function()
  600.   {
  601.     if (this.mPrefs.getBoolPref("migrated"))
  602.       return;
  603.  
  604.     try {
  605.       //only clear last do not migrate
  606.       if (this.mPrefs.prefHasUserValue("lastccr")) {
  607.         this.mPrefs.clearUserPref("lastccr");
  608.       }
  609.       if (this.mPrefs.prefHasUserValue("lastdayfr")) {
  610.         this.mPrefs.clearUserPref("lastdayfr");
  611.       }
  612.       if (this.mPrefs.prefHasUserValue("ccache")) {
  613.         this.mPrefs.setCharPref("current.cache", this.mPrefs.getCharPref("ccache"));
  614.         this.mPrefs.clearUserPref("ccache");
  615.       }
  616.       if (this.mPrefs.prefHasUserValue("cc")) {
  617.         this.mPrefs.setBoolPref("current.display", this.mPrefs.getBoolPref("cc"));
  618.         this.mPrefs.clearUserPref("cc");
  619.       }
  620.       if (this.mPrefs.prefHasUserValue("dayfcache")) {
  621.         this.mPrefs.setCharPref("forecast.cache", this.mPrefs.getCharPref("dayfcache"));
  622.         this.mPrefs.clearUserPref("dayfcache");
  623.       }
  624.       if (this.mPrefs.prefHasUserValue("cccache")) {
  625.         this.mPrefs.setCharPref("current.cache", this.mPrefs.getCharPref("cccache"));
  626.         this.mPrefs.clearUserPref("cccache");
  627.       }
  628.       if (this.mPrefs.prefHasUserValue("days")) {
  629.         this.mPrefs.setIntPref("forecast.days", parseInt(this.mPrefs.getCharPref("days")));
  630.         this.mPrefs.clearUserPref("days");
  631.       }
  632.       if (this.mPrefs.prefHasUserValue("bar")) {
  633.         this.mPrefs.setCharPref("display.bar", this.mPrefs.getCharPref("bar"));
  634.         this.mPrefs.clearUserPref("bar");
  635.       }
  636.       if (this.mPrefs.prefHasUserValue("nextnode")) {
  637.         this.mPrefs.setCharPref("display.nextnode", this.mPrefs.getCharPref("nextnode"));
  638.         this.mPrefs.clearUserPref("nextnode");
  639.       }
  640.       if (this.mPrefs.prefHasUserValue("insertafter")) {
  641.         this.mPrefs.setBoolPref("display.insertafter", this.mPrefs.getBoolPref("insertafter"));
  642.         this.mPrefs.clearUserPref("insertafter");
  643.       }
  644.       if (this.mPrefs.prefHasUserValue("sliderfreq")) {
  645.         this.mPrefs.setIntPref("slider.freq", parseInt(this.mPrefs.getCharPref("sliderfreq")));
  646.         this.mPrefs.clearUserPref("sliderfreq");
  647.       }
  648.       if (this.mPrefs.prefHasUserValue("slidercount")) {
  649.         this.mPrefs.setIntPref("slider.count", parseInt(this.mPrefs.getCharPref("slidercount")));
  650.         this.mPrefs.clearUserPref("slidercount");
  651.       }
  652.  
  653.       this.mPrefs.setBoolPref("migrated",true);
  654.     }
  655.     catch(e) {}
  656.   },
  657.  
  658.   scheduleUpdate: function(force_time)
  659.   {
  660.     var timer_time = null;
  661.  
  662.     if (force_time)
  663.       timer_time = force_time;
  664.     else {
  665.  
  666.       // Schedules the next updateData call.
  667.       var real_time    = (new Date()).valueOf();
  668.  
  669.       var next         = new Array();
  670.       // Get time till next refresh for each category.
  671.       next[0]        = Number(this.mPrefs.getCharPref(PREF_CURRENT_LAST)) + FREQ_CURRENT * 60 * 1000 - real_time;
  672.       next[1]         = Number(this.mPrefs.getCharPref(PREF_FORECAST_LAST)) + FREQ_FORECAST * 60 * 1000 - real_time;
  673.       next[2]        = Number(this.mPrefs.getCharPref(PREF_LINKS_LAST)) + FREQ_LINKS * 60 * 1000 - real_time;
  674.  
  675.  
  676.       next.sort(function (a, b) { return (a - b) });
  677.  
  678.       // If there will be another refresh within 1 min of the scheduled one, refresh
  679.       // at that point instead --> to clump them together.
  680.  
  681.       if ((next[2] - next[0]) < 15 * 60 * 1000) {
  682.         debug("Timer delayed " + ((next[2] - next[0]) / 60 / 1000).toFixed(1) + " minutes to catch multiple queries.");
  683.         timer_time = next[2];
  684.       }
  685.       else if ((next[1] - next[0]) < 10 * 60 * 1000) {
  686.         debug("Timer delayed " + ((next[1] - next[0]) / 60 / 1000).toFixed(1) + " minutes to catch multiple queries.");
  687.         timer_time = next[1];
  688.       }
  689.       else
  690.         timer_time = next[0];
  691.  
  692.       if (timer_time < 0)
  693.         timer_time = 100;
  694.  
  695.       timer_time += 100;
  696.  
  697.     }
  698.  
  699.     // Create the timer if its not already made
  700.     if (!this.mTimer)
  701.       this.mTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
  702.  
  703.     this.mTimer.cancel();
  704.  
  705.     // Then schedule the timeout.
  706.     this.mTimer.initWithCallback(this, timer_time, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
  707.  
  708.     debug("Next refresh scheduled for " + (timer_time / 1000 / 60).toFixed(1) + " minutes.");
  709.   },
  710.  
  711.   shouldRefresh: function(data)
  712.   {
  713.     var time = (new Date()).valueOf();
  714.  
  715.     // Make sure data is a valid element.
  716.     if ((data != "CURRENT") && (data != "FORECAST") && (data != "LINKS"))
  717.       return false;
  718.  
  719.     // Then get the time for the next refresh.
  720.     var next = Number(this.mPrefs.getCharPref(eval("PREF_" + data + "_LAST"))) + eval("FREQ_" + data) * 60 * 1000;
  721.  
  722.     return ((next - time) <= 0);
  723.   },
  724.  
  725.   ///////////////////////////
  726.   // nsITimerCallback
  727.   notify: function(timer)
  728.   {
  729.     if (timer == this.mTimeOut) {
  730.       debug("Query timed out...");
  731.  
  732.       this.scheduleUpdate(2 * 60 * 1000);
  733.  
  734.       this.mLastError["type"] = "0";
  735.       this.mLastError["msg"] = "Timed out";
  736.  
  737.       // Then notify the observers of the error.
  738.       this.mObsService.notifyObservers(null, "weatherfox:update:error", "");
  739.     }
  740.     else if (timer == this.mTimer) {
  741.       // update the data.
  742.       this.updateData();
  743.     }
  744.   },
  745.  
  746.   ///////////////////////////
  747.   // nsIAlertListener
  748.   onAlertFinished: function() {},
  749.   onAlertClickCallback: function(aCookie)
  750.   {
  751.     var windowManager = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService();
  752.     var windowManagerInterface = windowManager.QueryInterface( Components.interfaces.nsIWindowMediator);
  753.     var topWindow = windowManagerInterface.getMostRecentWindow("navigator:browser");
  754.     if (!topWindow) {
  755.         topWindow = window.openDialog("chrome://browser/content/browser.xul", "_blank", "chrome,all,dialog=no", "about:blank", null, null);
  756.     }
  757.     var browser = topWindow.document.getElementById("content");
  758.     var url = "http://www.weather.com/weather/local/" + this.mPrefs.getCharPref("locid");
  759.     url = url + "?prod=" + this.mProductCode + "&par=" + this.mPartnerId;
  760.     browser.loadURI(url);
  761.   },
  762.  
  763.   /////////////////////////////
  764.   // nsIObserver
  765.   observe: function(aSubject, aTopic, aData)
  766.   {
  767.     switch (aTopic) {
  768.       case "weatherfox:profile:start":
  769.         this.mLoadingProf = true;
  770.         break;
  771.       case "weatherfox:profile:end":
  772.         this.mLoadingProf = false;
  773.         this.scheduleUpdate(100);
  774.         break;
  775.       case "weatherfox:profile:error":
  776.         this.mLoadingProf = false;
  777.         break;
  778.       case "nsPref:changed":
  779.         if (!this.mLoadingProf) {
  780.           if (aData == "locid" || aData == "celcius") {
  781.             if (this.mPrefs.prefHasUserValue(PREF_CURRENT_LAST))
  782.               this.mPrefs.clearUserPref(PREF_CURRENT_LAST);
  783.             if (this.mPrefs.prefHasUserValue(PREF_FORECAST_LAST))
  784.               this.mPrefs.clearUserPref(PREF_FORECAST_LAST);
  785.             if (this.mPrefs.prefHasUserValue(PREF_LINKS_LAST))
  786.               this.mPrefs.clearUserPref(PREF_LINKS_LAST);
  787.             this.scheduleUpdate(250);
  788.           }
  789.         }
  790.         break;
  791.       case "xpcom-shutdown":
  792.  
  793.         // Clean up held observers etc to avoid leaks.
  794.         this.mObsService.removeObserver(this, "xpcom-shutdown");
  795.         this.mObsService.removeObserver(this, "weatherfox:profile:start", false);
  796.         this.mObsService.removeObserver(this, "weatherfox:profile:end", false);
  797.         this.mObsService.removeObserver(this, "weatherfox:profile:error", false);
  798.         var pbi = this.mPrefs.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
  799.         pbi.removeObserver("", this);
  800.  
  801.         // Release strongly held services.
  802.         this.mPrefs        = null;
  803.         this.mObsService   = null;
  804.         this.mHttpRequest  = null;
  805.         this.mDOMParser    = null;
  806.  
  807.         this.mFileIO.destroy();
  808.         this.mFileIO       = null;
  809.         if (this.mTimer) {
  810.           this.mTimer.cancel();
  811.           this.mTimer = null;
  812.         }
  813.         break;
  814.     }
  815.   },
  816.  
  817.   QueryInterface: function (iid)
  818.   {
  819.     if (!iid.equals(Components.interfaces.nsIWeatherfoxUpdate) &&
  820.           !iid.equals(Components.interfaces.nsIAlertListener)
  821.           && !iid.equals(Components.interfaces.nsISupports))
  822.       throw Components.results.NS_ERROR_NO_INTERFACE;
  823.     return this;
  824.   }
  825. }
  826.  
  827. function nsWeatherfoxProfile()
  828. {
  829.   //set up base variables
  830.   this.mPrefs        = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("weatherfox.");
  831.   this.mObsService    = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
  832.   this.mDOMParser    = Components.classes["@mozilla.org/xmlextras/domparser;1"].createInstance(Components.interfaces.nsIDOMParser);
  833.   this.mSerializer  = Components.classes["@mozilla.org/xmlextras/xmlserializer;1"].createInstance(Components.interfaces.nsIDOMSerializer);
  834.   this.mFileIO      = new fileIO();
  835.   this.mDirService     = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties);
  836.  
  837.   this.mPrefList = [["locid", "Char"],
  838.                     ["celcius", "Bool"],
  839.                     ["display.bar", "Char"],
  840.                     ["display.nextnode", "Char"],
  841.                     ["display.insertafter", "Bool"],
  842.                     ["current.display", "Bool"],
  843.                     ["current.mode", "Int"],
  844.                     ["current.last", "Char"],
  845.                     ["current.cache", "Char"],
  846.                     ["current.label", "Char"],
  847.                     ["current.tooltip", "Char"],
  848.                     ["forecast.days", "Int"],
  849.                     ["forecast.mode", "Int"],
  850.                     ["forecast.parts", "Int"],
  851.                     ["forecast.last", "Char"],
  852.                     ["forecast.cache", "Char"],
  853.                     ["forecast.label", "Char"],
  854.                     ["forecast.tooltip", "Char"],
  855.                     ["links.last", "Char"],
  856.                     ["links.cache", "Char"],
  857.                     ["slider.freq", "Int"],
  858.                     ["slider.count", "Int"],
  859.                     ["current.tipicon", "Bool"],
  860.                     ["forecast.tipicon", "Bool"]];
  861.   this.mDoc = this.getProfiles();
  862.   this.mProfile = this.mPrefs.getCharPref("profile");
  863.   //add shutdown observer
  864.   this.mObsService.addObserver(this, "xpcom-shutdown", false);
  865.  
  866.   //add prefernce observer for locid
  867.   var pbi = this.mPrefs.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
  868.   pbi.addObserver("", this, false);
  869.  
  870.   return this;
  871. }
  872.  
  873. nsWeatherfoxProfile.prototype =
  874. {
  875.   saveCurrent: function()
  876.   {
  877.     this.saveProfile(this.mProfile);
  878.   },
  879.  
  880.   saveProfile: function(name)
  881.   {
  882.     name = this.stripName(name);
  883.     debug("Saving profile " + name);
  884.  
  885.     var profile = this.getProfileNode(name);
  886.     var profiles = this.mDoc.getElementsByTagName("profiles")[0];
  887.     var new_profile = false;
  888.     var pref;
  889.     
  890.     if (profile)
  891.       profile.parentNode.removeChild(profile);
  892.     else {
  893.       // set new_profile so we can rename the cache files down there after the profile node has been created
  894.       debug("Creating a NEW profile");
  895.       new_profile = true;
  896.       this.mObsService.notifyObservers(null, "weatherfox:profile:new", name);
  897.     }
  898.  
  899.     profile = this.mDoc.createElement("profile");
  900.     profile.setAttribute("id", name);
  901.  
  902.     for (var x = 0; x < this.mPrefList.length; x++) {
  903.       pref = this.mDoc.createElement("pref");
  904.       pref.setAttribute("name", this.mPrefList[x][0]);
  905.       pref.setAttribute("type", this.mPrefList[x][1]);
  906.       pref.setAttribute("value", eval("this.mPrefs.get" + this.mPrefList[x][1] + "Pref('" + this.mPrefList[x][0] + "')"));
  907.       profile.appendChild(pref);
  908.     }
  909.     
  910.     profiles.appendChild(profile);
  911.     
  912.     if (new_profile)
  913.       this.copyCacheFiles(profile.getAttribute("id"), profile);
  914.  
  915.     this.saveProfiles();
  916.   },
  917.   
  918.   copyCacheFiles: function( p1, p2 )
  919.   {
  920.     //copies p1's cache files to p2 names... and redoes p2's cache prefs
  921.     // p1 = name of profile 1
  922.     // p2 = profile node of one to copy to
  923.     
  924.     var to_copy = new Array();
  925.     var new_file, file, x, pref;
  926.     var current_prof = (this.mProfile == p1);
  927.     
  928.     if (current_prof)
  929.       file = this.mPrefs.getCharPref(PREF_LINKS_CACHE);
  930.     else
  931.       file = this.getProfilePref(p1, PREF_LINKS_CACHE);
  932.       
  933.     if (!to_copy[file])
  934.       to_copy[file] = new Array();
  935.     to_copy[file][PREF_LINKS_CACHE] = true;
  936.     
  937.     if (current_prof)
  938.       file = this.mPrefs.getCharPref(PREF_FORECAST_CACHE);
  939.     else
  940.       file = this.getProfilePref(p1, PREF_FORECAST_CACHE);
  941.   
  942.     if (!to_copy[file])
  943.       to_copy[file] = new Array();
  944.     to_copy[file][PREF_FORECAST_CACHE] = true;
  945.     
  946.     if (current_prof)
  947.       file = this.mPrefs.getCharPref(PREF_CURRENT_CACHE);
  948.     else
  949.       file = this.getProfilePref(p1, PREF_CURRENT_CACHE);
  950.       
  951.     if (!to_copy[file])
  952.       to_copy[file] = new Array();
  953.     to_copy[file][PREF_CURRENT_CACHE] = true;
  954.     
  955.     for (file in to_copy) {
  956.       if (to_copy[file][PREF_LINKS_CACHE])
  957.         new_file = "linkscache-" + p2.getAttribute("id") + ".xml";
  958.       else if (to_copy[file][PREF_FORECAST_CACHE])
  959.         new_file = "forecastcache-" + p2.getAttribute("id") + ".xml";
  960.       else if (to_copy[file][PREF_CURRENT_CACHE])
  961.         new_file = "currentcache-" + p2.getAttribute("id") + ".xml";
  962.       
  963.     
  964.       for (pref in to_copy[file]) {
  965.         if (to_copy[file][pref]) {
  966.           debug("Pref: " + pref + " value: " + new_file);
  967.           if (current_prof)
  968.             this.mPrefs.setCharPref(pref, new_file);
  969.           else
  970.             this.setProfilePref(p2, pref, new_file);
  971.         }
  972.       }
  973.     
  974.       // copy the file
  975.       this.mFileIO.copyFile(file, new_file);
  976.       
  977.       // then change the file in the array so
  978.       to_copy[new_file] = to_copy[file];
  979.       to_copy[file] = null;
  980.     }
  981.     
  982.     // update the prefs
  983.     var prefs = p2.getElementsByTagName("pref");
  984.     
  985.     for (x = 0; x < prefs.length; x++) {
  986.       pref = prefs[x].getAttribute("name");
  987.       if ((pref == PREF_CURRENT_CACHE) || (pref == PREF_FORECAST_CACHE) || (pref == PREF_LINKS_CACHE)) {
  988.         // set the file that this pref is now associated iwth
  989.         for (file in to_copy) {
  990.           if (to_copy[file] && to_copy[file][pref])
  991.             prefs[x].setAttribute("value", file);
  992.         }
  993.       }
  994.     }
  995.   },
  996.   
  997.   getProfilePref: function(profile, pref)
  998.   {
  999.     var profile_node = this.getProfileNode(profile);
  1000.     var prefs = profile_node.getElementsByTagName("pref");
  1001.     for (var x = 0; x < prefs.length; x++) {
  1002.       if (prefs[x].getAttribute("name") == pref) {
  1003.         return prefs[x].getAttribute("value");
  1004.       }
  1005.     }
  1006.     return false;
  1007.   },
  1008.   
  1009.   setProfilePref: function(profile, pref, value)
  1010.   {
  1011.     var prefs = profile.getElementsByTagName("pref");
  1012.     var pref_node = null;
  1013.     
  1014.     for (var x = 0; x < prefs.length; x++) {
  1015.       if (prefs[x].getAttribute("name") == pref) {
  1016.         prefs[x].setAttribute("value", value);
  1017.         return;
  1018.       }
  1019.     }
  1020.   },
  1021.   
  1022.   getNames: function(aCount)
  1023.   {
  1024.     var asKeys = new Array();
  1025.     var sKey   = null;
  1026.     var profiles = this.mDoc.getElementsByTagName("profile");
  1027.  
  1028.     if (profiles.length == 0) {
  1029.       this.saveCurrent();
  1030.       profiles = this.mDoc.getElementsByTagName("profile");
  1031.     }
  1032.  
  1033.     for (var x = 0; x < profiles.length; x++)
  1034.       asKeys.push(profiles[x].getAttribute("id"));
  1035.  
  1036.     aCount.value = asKeys.length;
  1037.     asKeys.sort();
  1038.     return asKeys;
  1039.   },
  1040.  
  1041.   getProfileNode: function(name)
  1042.   {
  1043.     var profiles = this.mDoc.getElementsByTagName("profile");
  1044.     var val;
  1045.     for (var x = 0; x < profiles.length; x++) {
  1046.       if (profiles[x].getAttribute("id") == name) {
  1047.         val = profiles[x];
  1048.         break;
  1049.       }
  1050.     }
  1051.     return val;
  1052.   },
  1053.   
  1054.   loadProfile: function(name)
  1055.   {
  1056.     this.saveCurrent();
  1057.     
  1058.     debug("Loading profile " + name);
  1059.  
  1060.     this.mObsService.notifyObservers(null, "weatherfox:profile:start", "");
  1061.  
  1062.     var profile = this.getProfileNode(name);
  1063.  
  1064.     if (!profile) {
  1065.       //profile doesn't exist - error
  1066.       this.mObsService.notifyObservers(null, "weatherfox:profile:error", "Profile not found");
  1067.       return;
  1068.     }
  1069.  
  1070.     var prefs   = profile.getElementsByTagName("pref");
  1071.     var pref, type, value;
  1072.  
  1073.     for (var x = 0; x < prefs.length; x++) {
  1074.       pref  = prefs[x].getAttribute("name");
  1075.       type  = prefs[x].getAttribute("type");
  1076.       value = prefs[x].getAttribute("value");
  1077.       switch (type) {
  1078.         case "Bool":
  1079.           value = (value == "true") ? true : false;
  1080.           break;
  1081.         case "Int":
  1082.           value = Number(value);
  1083.           break;
  1084.         case "Char":
  1085.           value = "'" + value + "'";
  1086.       }
  1087.  
  1088.       //now set the pref
  1089.       eval("this.mPrefs.set" + type + "Pref('" + pref + "', " + value + ")");
  1090.     }
  1091.     
  1092.     this.mProfile = name;
  1093.     this.mObsService.notifyObservers(null, "weatherfox:profile:end", name);
  1094.   },
  1095.  
  1096.   renameProfile: function(old_name, new_name)
  1097.   {
  1098.     new_name = this.stripName(new_name);
  1099.     debug("Renaming profile " + old_name + " to " + new_name);
  1100.     this.getProfileNode(old_name).setAttribute("id", new_name);
  1101.     
  1102.     // Change the profile pref if we renamed the current profile.
  1103.     if (this.mPrefs.getCharPref("profile") == old_name) {
  1104.       this.mProfile = new_name;
  1105.       this.mPrefs.setCharPref("profile", new_name);
  1106.     }
  1107.  
  1108.     // copy cache files to new name
  1109.     this.copyCacheFiles(new_name, this.getProfileNode(new_name));
  1110.  
  1111.     // and remove all possibilities
  1112.     this.mFileIO.removeFile("currentcache-" + old_name + ".xml");
  1113.     this.mFileIO.removeFile("forecastcache-" + old_name + ".xml");
  1114.     this.mFileIO.removeFile("linkscache-" + old_name + ".xml");
  1115.     
  1116.     this.saveProfiles();
  1117.   },
  1118.  
  1119.   stripName: function(name)
  1120.   {
  1121.     // remove illegal characters
  1122.     return name.replace(/[\\\/:*?<>|\'\"]/g, "")
  1123.   },
  1124.   
  1125.   deleteProfile: function(name)
  1126.   {
  1127.  
  1128.     var node = this.getProfileNode(name);
  1129.     
  1130.     // remove cached files
  1131.     this.mFileIO.removeFile("currentcache-" + name + ".xml");
  1132.     this.mFileIO.removeFile("forecastcache-" + name + ".xml");
  1133.     this.mFileIO.removeFile("linkscache-" + name + ".xml");
  1134.     
  1135.     //remove the node
  1136.     node.parentNode.removeChild(node);
  1137.  
  1138.     debug("Removed profile " + name);
  1139.  
  1140.     //save it
  1141.     this.saveProfiles();
  1142.   },
  1143.  
  1144.   getProfiles: function()
  1145.   {
  1146.     debug("Reading profiles from disk");
  1147.     var text = this.mFileIO.read(PROFILE_FILE);
  1148.  
  1149.     if (!text)
  1150.       return this.migrateProfiles();
  1151.  
  1152.     return this.mDOMParser.parseFromString(text, "text/xml");
  1153.   },
  1154.   
  1155.  
  1156.   migrateProfiles: function()
  1157.   {
  1158.     // In this function, we copy the profiles.xml file from the defaults folder to the profile directory
  1159.     var file = this.mDirService.get("ProfD", Components.interfaces.nsIFile);
  1160.     var target = file.clone();
  1161.     target.append("weatherfox");
  1162.  
  1163.     file.append("extensions");
  1164.     file.append("{BA8E053E-2867-4772-B9F0-26A5979AFA09}");
  1165.     file.append("defaults");
  1166.     file.append(PROFILE_FILE);
  1167.     file.copyTo(target, PROFILE_FILE);
  1168.     
  1169.     target.append(PROFILE_FILE);
  1170.     if (!target.isWritable()) 
  1171.       target.permissions = 0644;
  1172.       
  1173.     return this.getProfiles();
  1174.   },
  1175.  
  1176.   saveProfiles: function()
  1177.   {
  1178.     debug("Writing profiles to disk...")
  1179.  
  1180.     //save the doc to file
  1181.     this.mFileIO.write(PROFILE_FILE, this.mSerializer.serializeToString(this.mDoc));
  1182.   },
  1183.  
  1184.   /////////////////////////////
  1185.   // nsIObserver
  1186.   observe: function(aSubject, aTopic, aData)
  1187.   {
  1188.     switch (aTopic) {
  1189.       case "nsPref:changed":
  1190.         var new_prof = this.mPrefs.getCharPref("profile");
  1191.         if ((aData == "profile") && (new_prof != this.mProfile))
  1192.           this.loadProfile(this.mPrefs.getCharPref("profile"));
  1193.         break;
  1194.       case "xpcom-shutdown":
  1195.         //save the current profile
  1196.         this.saveCurrent();
  1197.  
  1198.         this.mObsService.removeObserver(this, "xpcom-shutdown");
  1199.         var pbi = this.mPrefs.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
  1200.         pbi.removeObserver("", this);
  1201.  
  1202.         // Release strongly held services.
  1203.         this.mPrefs            = null;
  1204.         this.mObsService     = null;
  1205.         this.mDOMParser     = null;
  1206.         this.mSerializer     = null;
  1207.  
  1208.         this.mFileIO.destroy();
  1209.         this.mFileIO         = null;
  1210.         break;
  1211.     }
  1212.   },
  1213.  
  1214.   QueryInterface: function (iid)
  1215.   {
  1216.     if (!iid.equals(Components.interfaces.nsIWeatherfoxProfile) &&
  1217.         !iid.equals(Components.interfaces.nsISupports))
  1218.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1219.     return this;
  1220.   }
  1221. };
  1222.  
  1223. /******************************************************************************
  1224.  * XPCOM Functions for construction and registration
  1225.  ******************************************************************************/
  1226.  
  1227. var gModule =
  1228. {
  1229.     mFirstTime: true,
  1230.     registerSelf: function (aCompMgr, aFileSpec, aLocation, aType)
  1231.     {
  1232.     if (this.mFirstTime) {
  1233.       this.mFirstTime = false;
  1234.       throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
  1235.     }
  1236.     aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  1237.     for (var key in this.mObjects) {
  1238.       var obj = this.mObjects[key];
  1239.       aCompMgr.registerFactoryLocation(obj.CID, obj.className, obj.contractID,
  1240.                                        aFileSpec, aLocation, aType);
  1241.     }
  1242.   },
  1243.  
  1244.   getClassObject: function (aCompMgr, aCID, aIID)
  1245.   {
  1246.     if (!aIID.equals(Components.interfaces.nsIFactory))
  1247.       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  1248.  
  1249.     for (var key in this.mObjects) {
  1250.       if (aCID.equals(this.mObjects[key].CID))
  1251.         return this.mObjects[key].factory;
  1252.     }
  1253.  
  1254.     throw Components.results.NS_ERROR_NO_INTERFACE;
  1255.   },
  1256.  
  1257.   mObjects: {
  1258.     updateService: {
  1259.       CID: Components.ID("{6F7A0252-FFF1-49f4-A175-CC7F077AB85C}"),
  1260.       contractID: "@mozdev.org/weatherfox/update;1",
  1261.       className: "Weatherfox Update Service",
  1262.       factory: {
  1263.         createInstance: function (aOuter, aIID)
  1264.         {
  1265.           if (aOuter != null)
  1266.             throw Components.results.NS_ERROR_NO_AGGREGATION;
  1267.           return (new nsWeatherfoxUpdate()).QueryInterface(aIID);
  1268.         }
  1269.       }
  1270.     },
  1271.  
  1272.     profileService: {
  1273.       CID: Components.ID("{6CF34FFC-830E-4abf-AAA9-6A41CF66F2A0}"),
  1274.       contractID: "@mozdev.org/weatherfox/profile;1",
  1275.       className: "Weatherfox Profile Service",
  1276.       factory: {
  1277.         createInstance: function (aOuter, aIID)
  1278.         {
  1279.           if (aOuter != null)
  1280.             throw Components.results.NS_ERROR_NO_AGGREGATION;
  1281.           return (new nsWeatherfoxProfile()).QueryInterface(aIID);
  1282.         }
  1283.       }
  1284.     }
  1285.   },
  1286.  
  1287.   canUnload: function(compMgr)
  1288.   {
  1289.       return true;
  1290.   }
  1291. };
  1292.  
  1293. function NSGetModule(compMgr, fileSpec) { return gModule; }
  1294.  
  1295. /******************************************************************************
  1296.  * Utility functions
  1297.  ******************************************************************************/
  1298.  
  1299. function debug(message) {
  1300.   var time = new Date();
  1301.   var time_string = time.getHours() + ":" + time.getMinutes() + ":" + time.getSeconds();
  1302.  
  1303.   dump("[WeatherFox " + time_string + "]: " + message + "\n");
  1304. }
  1305.  
  1306. function addUnit(base, unit) {
  1307.   if ((base == "N/A") || ((!Number(base)) && (base != 0)))
  1308.     return base;
  1309.   else
  1310.     return (base + unit);
  1311. }
  1312.  
  1313. function fileIO()
  1314. {
  1315.   this.mDirService         = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties);
  1316.   this.mFoStream         = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
  1317.   this.mFiStream         = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
  1318.   this.mSiStream         = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
  1319.  
  1320.   return this;
  1321. }
  1322.  
  1323. fileIO.prototype = {
  1324.   destroy: function()
  1325.   {
  1326.     this.mDirService     = null;
  1327.     this.mFoStream         = null;
  1328.     this.mFiStream         = null;
  1329.     this.mSiStream         = null;
  1330.   },
  1331.   
  1332.   write: function(name, contents)
  1333.   {
  1334.     var file = this.mDirService.get("ProfD", Components.interfaces.nsIFile);
  1335.  
  1336.     //put in weatherfox subdirectory
  1337.     file.append("weatherfox");
  1338.     if (!file.exists())
  1339.       file.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
  1340.  
  1341.     file.append(name);
  1342.  
  1343.     var flags = 0x02 | 0x08 | 0x20;
  1344.     this.mFoStream.init(file, flags, 0664, 0);
  1345.     this.mFoStream.write(contents, contents.length);
  1346.     this.mFoStream.close();
  1347.  
  1348.     return true;
  1349.   },
  1350.  
  1351.   read: function(name)
  1352.   {
  1353.     var file = this.mDirService.get("ProfD", Components.interfaces.nsIFile)
  1354.  
  1355.     //get from weatherfox subdirectory
  1356.     file.append("weatherfox");
  1357.     if (!file.exists())
  1358.       return false;
  1359.  
  1360.     file.append(name);
  1361.  
  1362.     var contents = new String();
  1363.  
  1364.     try {
  1365.       this.mFiStream.init(file, 1, 0, false);
  1366.       this.mSiStream.init(this.mFiStream);
  1367.       contents += this.mSiStream.read(-1);
  1368.  
  1369.       this.mSiStream.close();
  1370.       this.mFiStream.close();
  1371.       return contents;
  1372.     }
  1373.     catch(e) {
  1374.       return false;
  1375.     }
  1376.   },
  1377.   
  1378.   copyFile: function(file, newfile)
  1379.   {
  1380.     var old_file = this.mDirService.get("ProfD", Components.interfaces.nsIFile);
  1381.     old_file.append("weatherfox");
  1382.     
  1383.     var target = old_file.clone();
  1384.     
  1385.     old_file.append(file);
  1386.     
  1387.     // remove the file if it already exists
  1388.     var file_check = target.clone();
  1389.     file_check.append(newfile);
  1390.     if (file_check.exists())
  1391.       file_check.remove(false);
  1392.     
  1393.     // copy the old one to this location
  1394.     old_file.copyTo(target, newfile);
  1395.  
  1396.   },
  1397.   
  1398.   removeFile: function(file)
  1399.   {
  1400.     var target = this.mDirService.get("ProfD", Components.interfaces.nsIFile);
  1401.     target.append("weatherfox");
  1402.     target.append(file);
  1403.     debug("Deleting " + file);
  1404.     try {
  1405.       target.remove(false);
  1406.     } catch (e) { }
  1407.     
  1408.   }
  1409.   
  1410. };