Bug 178506: Preserve original last-modified date on downloads r=sdwilsh
authorAndrey Ivanov <andrey.v.ivanov@gmail.com>
Sat, 30 Jan 2010 12:58:01 -0500
changeset 37653 931dc23fa6130f5db1ee2a3a9829f7642ebc24f6
parent 37652 4d0717cc3f577ed82135b4fed598c816bdda617a
child 37654 bbc58a03cab9bc7d94f08f777b853ee11f61cfe5
push id11423
push userme@kylehuey.com
push dateSat, 30 Jan 2010 17:58:29 +0000
treeherdermozilla-central@931dc23fa613 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssdwilsh
bugs178506
milestone1.9.3a1pre
Bug 178506: Preserve original last-modified date on downloads r=sdwilsh
netwerk/protocol/ftp/public/nsIFTPChannel.idl
netwerk/protocol/ftp/src/nsFTPChannel.h
netwerk/protocol/ftp/src/nsFtpConnectionThread.cpp
toolkit/components/downloads/src/nsDownloadManager.cpp
toolkit/components/downloads/src/nsDownloadManager.h
toolkit/components/downloads/test/unit/test_lastmodified_time_preservation.js
--- a/netwerk/protocol/ftp/public/nsIFTPChannel.idl
+++ b/netwerk/protocol/ftp/public/nsIFTPChannel.idl
@@ -35,19 +35,20 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 /**
  * This interface may be used to determine if a channel is a FTP channel.
  */
-[scriptable, uuid(2315d831-8b40-446a-9138-fe09ebb1b720)]
+[scriptable, uuid(07f0d5cd-1fd5-4aa3-b6fc-665bdc5dbf9f)]
 interface nsIFTPChannel : nsISupports
 {
+    attribute PRTime lastModifiedTime;
 };
 
 /**
  * This interface may be defined as a notification callback on the FTP
  * channel.  It allows a consumer to receive a log of the FTP control
  * connection conversation.
  */
 [scriptable, uuid(455d4234-0330-43d2-bbfb-99afbecbfeb0)]
--- a/netwerk/protocol/ftp/src/nsFTPChannel.h
+++ b/netwerk/protocol/ftp/src/nsFTPChannel.h
@@ -80,16 +80,17 @@ public:
     NS_DECL_NSIUPLOADCHANNEL
     NS_DECL_NSIRESUMABLECHANNEL
     NS_DECL_NSIPROXIEDCHANNEL
     
     nsFtpChannel(nsIURI *uri, nsIProxyInfo *pi)
         : mProxyInfo(pi)
         , mStartPos(0)
         , mResumeRequested(PR_FALSE)
+        , mLastModifiedTime(0)
     {
         SetURI(uri);
     }
 
     nsIProxyInfo *ProxyInfo() {
         return mProxyInfo;
     }
 
@@ -102,16 +103,26 @@ public:
     // ID of the entity to resume downloading
     const nsCString &EntityID() {
         return mEntityID;
     }
     void SetEntityID(const nsCSubstring &entityID) {
         mEntityID = entityID;
     }
 
+    NS_IMETHODIMP GetLastModifiedTime(PRTime* lastModifiedTime) {
+        *lastModifiedTime = mLastModifiedTime;
+        return NS_OK;
+    }
+
+    NS_IMETHODIMP SetLastModifiedTime(PRTime lastModifiedTime) {
+        mLastModifiedTime = lastModifiedTime;
+        return NS_OK;
+    }
+
     // Data stream to upload
     nsIInputStream *UploadStream() {
         return mUploadStream;
     }
 
     // Helper function for getting the nsIFTPEventSink.
     void GetFTPEventSink(nsCOMPtr<nsIFTPEventSink> &aResult);
 
@@ -124,11 +135,12 @@ protected:
 
 private:
     nsCOMPtr<nsIProxyInfo>    mProxyInfo; 
     nsCOMPtr<nsIFTPEventSink> mFTPEventSink;
     nsCOMPtr<nsIInputStream>  mUploadStream;
     PRUint64                  mStartPos;
     nsCString                 mEntityID;
     PRPackedBool              mResumeRequested;
+    PRTime                    mLastModifiedTime;
 };
 
 #endif /* nsFTPChannel_h___ */
--- a/netwerk/protocol/ftp/src/nsFtpConnectionThread.cpp
+++ b/netwerk/protocol/ftp/src/nsFtpConnectionThread.cpp
@@ -1031,16 +1031,44 @@ nsFtpState::R_mdtm() {
     if (mResponseCode == 213) {
         mResponseMsg.Cut(0,4);
         mResponseMsg.Trim(" \t\r\n");
         // yyyymmddhhmmss
         if (mResponseMsg.Length() != 14) {
             NS_ASSERTION(mResponseMsg.Length() == 14, "Unknown MDTM response");
         } else {
             mModTime = mResponseMsg;
+
+            // Save lastModified time for downloaded files.
+            nsCAutoString timeString;
+            PRInt32 error;
+            PRExplodedTime exTime;
+
+            mResponseMsg.Mid(timeString, 0, 4);
+            exTime.tm_year  = timeString.ToInteger(&error, 10);
+            mResponseMsg.Mid(timeString, 4, 2);
+            exTime.tm_month = timeString.ToInteger(&error, 10) - 1; //january = 0
+            mResponseMsg.Mid(timeString, 6, 2);
+            exTime.tm_mday  = timeString.ToInteger(&error, 10);
+            mResponseMsg.Mid(timeString, 8, 2);
+            exTime.tm_hour  = timeString.ToInteger(&error, 10);
+            mResponseMsg.Mid(timeString, 10, 2);
+            exTime.tm_min   = timeString.ToInteger(&error, 10);
+            mResponseMsg.Mid(timeString, 12, 2);
+            exTime.tm_sec   = timeString.ToInteger(&error, 10);
+            exTime.tm_usec  = 0;
+
+            exTime.tm_params.tp_gmt_offset = 0;
+            exTime.tm_params.tp_dst_offset = 0;
+
+            PR_NormalizeTime(&exTime, PR_GMTParameters);
+            exTime.tm_params = PR_LocalTimeParameters(&exTime);
+
+            PRTime time = PR_ImplodeTime(&exTime);
+            (void)mChannel->SetLastModifiedTime(time);
         }
     }
 
     nsCString entityID;
     entityID.Truncate();
     entityID.AppendInt(PRInt64(mFileSize));
     entityID.Append('/');
     entityID.Append(mModTime);
--- a/toolkit/components/downloads/src/nsDownloadManager.cpp
+++ b/toolkit/components/downloads/src/nsDownloadManager.cpp
@@ -22,16 +22,18 @@
  * Contributor(s):
  *   Blake Ross <blaker@netscape.com> (Original Author)
  *   Ben Goodger <ben@netscape.com> (Original Author)
  *   Shawn Wilsher <me@shawnwilsher.com>
  *   Srirang G Doddihal <brahmana@doddihal.com>
  *   Edward Lee <edward.lee@engineering.uiuc.edu>
  *   Graeme McCutcheon <graememcc_firefox@graeme-online.co.uk>
  *   Ehsan Akhgari <ehsan.akhgari@gmail.com>
+ *   Michal Sciubidlo <michal.sciubidlo@gmail.com>
+ *   Andrey Ivanov <andrey.v.ivanov@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -59,16 +61,19 @@
 
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsArrayEnumerator.h"
 #include "nsCExternalHandlerService.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsDownloadManager.h"
 #include "nsNetUtil.h"
 
+#include "nsIHttpChannel.h"
+#include "nsIFileChannel.h"
+#include "nsIFTPChannel.h"
 #include "mozStorageCID.h"
 #include "nsDocShellCID.h"
 #include "nsEmbedCID.h"
 #include "nsToolkitCompsCID.h"
 
 #if defined(XP_WIN) && !defined(WINCE)
 #include <shlobj.h>
 #ifdef DOWNLOAD_SCANNER
@@ -2192,70 +2197,74 @@ nsDownload::SetState(DownloadState aStat
               // the items they downloaded will have been removed.
               alerts->ShowAlertNotification(
                   NS_LITERAL_STRING(DOWNLOAD_MANAGER_ALERT_ICON), title,
                   message, !removeWhenDone, EmptyString(), mDownloadManager,
                   EmptyString());
             }
         }
       }
-#if (defined(XP_WIN) && !defined(WINCE)) || defined(XP_MACOSX)
+
       nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mTarget);
-      nsCOMPtr<nsIFile> file;
-      nsAutoString path;
-
-      if (fileURL &&
-          NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(file))) &&
-          file &&
-          NS_SUCCEEDED(file->GetPath(path))) {
+      if (fileURL) {
+        nsCOMPtr<nsIFile> file;
+        if (NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(file))) && file ) {
+
+#if (defined(XP_WIN) && !defined(WINCE)) || defined(XP_MACOSX)
+          nsAutoString path;
+          if (NS_SUCCEEDED(file->GetPath(path))) {
 
 #ifdef XP_WIN
-        // On windows, add the download to the system's "recent documents"
-        // list, with a pref to disable.
-        {
-          PRBool addToRecentDocs = PR_TRUE;
-          if (pref)
-            pref->GetBoolPref(PREF_BDM_ADDTORECENTDOCS, &addToRecentDocs);
-
-          if (addToRecentDocs &&
-              !nsDownloadManager::gDownloadManagerService->mInPrivateBrowsing)
-            ::SHAddToRecentDocs(SHARD_PATHW, path.get());
-        }
+            // On windows, add the download to the system's "recent documents"
+            // list, with a pref to disable.
+            PRBool addToRecentDocs = PR_TRUE;
+            if (pref)
+              pref->GetBoolPref(PREF_BDM_ADDTORECENTDOCS, &addToRecentDocs);
+
+            if (addToRecentDocs &&
+                !nsDownloadManager::gDownloadManagerService->mInPrivateBrowsing) {
+               ::SHAddToRecentDocs(SHARD_PATHW, path.get());
+            }
 #endif
 #ifdef XP_MACOSX
-        // On OS X, make the downloads stack bounce.
-        CFStringRef observedObject = ::CFStringCreateWithCString(kCFAllocatorDefault,
-                                             NS_ConvertUTF16toUTF8(path).get(),
-                                             kCFStringEncodingUTF8);
-        CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
-        ::CFNotificationCenterPostNotification(center, CFSTR("com.apple.DownloadFileFinished"),
-                                               observedObject, NULL, TRUE);
-        ::CFRelease(observedObject);
+            // On OS X, make the downloads stack bounce.
+            CFStringRef observedObject = ::CFStringCreateWithCString(kCFAllocatorDefault,
+                                                 NS_ConvertUTF16toUTF8(path).get(),
+                                                 kCFStringEncodingUTF8);
+            CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
+            ::CFNotificationCenterPostNotification(center, CFSTR("com.apple.DownloadFileFinished"),
+                                                   observedObject, NULL, TRUE);
+            ::CFRelease(observedObject);
 #endif
-      }
+          }
 
 #ifdef XP_WIN
-      // Adjust file attributes so that by default, new files are indexed
-      // by desktop search services. Skip off those that land in the temp
-      // folder.
-      nsCOMPtr<nsIFile> tempDir, fileDir;
-      rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tempDir));
-      NS_ENSURE_SUCCESS(rv, rv);
-      (void)file->GetParent(getter_AddRefs(fileDir));
-
-      PRBool isTemp = PR_FALSE;
-      if (fileDir)
-        (void)fileDir->Equals(tempDir, &isTemp);
-
-      nsCOMPtr<nsILocalFileWin> localFileWin(do_QueryInterface(file));
-      if (!isTemp && localFileWin)
-        (void)localFileWin->SetFileAttributesWin(nsILocalFileWin::WFA_SEARCH_INDEXED);
-
+          // Adjust file attributes so that by default, new files are indexed
+          // by desktop search services. Skip off those that land in the temp
+          // folder.
+          nsCOMPtr<nsIFile> tempDir, fileDir;
+          rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tempDir));
+          NS_ENSURE_SUCCESS(rv, rv);
+          (void)file->GetParent(getter_AddRefs(fileDir));
+
+          PRBool isTemp = PR_FALSE;
+          if (fileDir)
+            (void)fileDir->Equals(tempDir, &isTemp);
+
+          nsCOMPtr<nsILocalFileWin> localFileWin(do_QueryInterface(file));
+          if (!isTemp && localFileWin)
+            (void)localFileWin->SetFileAttributesWin(nsILocalFileWin::WFA_SEARCH_INDEXED);
 #endif
 #endif
+          // After all operations with file, its last modification time needs to
+          // be updated from request
+          (void)file->SetLastModifiedTime(GetLastModifiedTime(mRequest));
+        }
+      }
+
       // Now remove the download if the user's retention policy is "Remove when Done"
       if (mDownloadManager->GetRetentionBehavior() == 0)
         mDownloadManager->RemoveDownload(mID);
     }
     break;
   default:
     break;
   }
@@ -2305,17 +2314,17 @@ NS_IMETHODIMP
 nsDownload::OnProgressChange64(nsIWebProgress *aWebProgress,
                                nsIRequest *aRequest,
                                PRInt64 aCurSelfProgress,
                                PRInt64 aMaxSelfProgress,
                                PRInt64 aCurTotalProgress,
                                PRInt64 aMaxTotalProgress)
 {
   if (!mRequest)
-    mRequest = aRequest; // used for pause/resume
+    mRequest = aRequest; // used for pause/resume/last modification time
 
   if (mDownloadState == nsIDownloadManager::DOWNLOAD_QUEUED) {
     // Obtain the referrer
     nsresult rv;
     nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
     nsCOMPtr<nsIURI> referrer = mReferrer;
     if (channel)
       (void)NS_GetReferrerFromChannel(channel, getter_AddRefs(mReferrer));
@@ -3051,8 +3060,57 @@ nsDownload::FailDownload(nsresult aStatu
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Show alert
   nsCOMPtr<nsIPromptService> prompter =
     do_GetService("@mozilla.org/embedcomp/prompt-service;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
   return prompter->Alert(dmWindow, title, message);
 }
+
+NS_IMETHODIMP_(PRInt64)
+nsDownload::GetLastModifiedTime(nsIRequest *aRequest)
+{
+  if (!aRequest) {
+    return PR_Now() / PR_USEC_PER_MSEC;
+  }
+
+  PRInt64 timeLastModified = 0;
+
+  // HTTP channels may have a Last-Modified header that we'll use to get the
+  // last modified time.
+  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+  if (httpChannel) {
+    nsCAutoString refreshHeader;
+    if (NS_SUCCEEDED(httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Last-Modified"), refreshHeader))) {
+      PRStatus result = PR_ParseTimeString(PromiseFlatCString(refreshHeader).get(), PR_FALSE, &timeLastModified);
+      if (result == PR_SUCCESS)
+        return timeLastModified / PR_USEC_PER_MSEC;
+    }
+    return PR_Now() / PR_USEC_PER_MSEC;
+  }
+
+  // File channels have a lastModifiedTime attribute that we can get the last
+  // modified time from.
+  nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aRequest);
+  if (fileChannel) {
+    nsCOMPtr<nsIFile> file;
+    fileChannel->GetFile(getter_AddRefs(file));
+    if (file && NS_SUCCEEDED(file->GetLastModifiedTime(&timeLastModified)))
+      return timeLastModified;
+    return PR_Now() / PR_USEC_PER_MSEC;
+  }
+
+  // FTP channels have a lastModifiedTime attribute that we can get the last
+  // modified time from.
+  nsCOMPtr<nsIFTPChannel> ftpChannel = do_QueryInterface(aRequest);
+  if (ftpChannel) {
+    if (NS_SUCCEEDED(ftpChannel->GetLastModifiedTime(&timeLastModified)) &&
+        timeLastModified != 0) {
+      return timeLastModified / PR_USEC_PER_MSEC;
+    }
+    return PR_Now() / PR_USEC_PER_MSEC;
+  }
+
+  // For this request, we do not know how to get the last modified time, so
+  // return the current time.
+  return PR_Now() / PR_USEC_PER_MSEC;
+}
--- a/toolkit/components/downloads/src/nsDownloadManager.h
+++ b/toolkit/components/downloads/src/nsDownloadManager.h
@@ -21,16 +21,18 @@
  *
  * Contributor(s):
  *   Blake Ross <blaker@netscape.com>
  *   Ben Goodger <ben@netscape.com>
  *   Shawn Wilsher <me@shawnwilsher.com>
  *   Srirang G Doddihal <brahmana@doddihal.com>
  *   Edward Lee <edward.lee@engineering.uiuc.edu>
  *   Ehsan Akhgari <ehsan.akhgari@gmail.com>
+ *   Michal Sciubidlo <michal.sciubidlo@gmail.com>
+ *   Andrey Ivanov <andrey.v.ivanov@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -396,16 +398,29 @@ protected:
    * This also adds the temporary file to the "To be deleted on Exit" list, if
    * the corresponding user preference is set (except on OS X).
    *
    * This function was adopted from nsExternalAppHandler::OpenWithApplication
    * (uriloader/exthandler/nsExternalHelperAppService.cpp).
    */
   nsresult OpenWithApplication();
 
+  /**
+   * Funciton extracts last modification time from passed request. If request 
+   * does not support last modification time then current time is returned.
+   * If request does not have information about last modification time then
+   * current time is returned as well.
+   *
+   * @param aRequest 
+   *        The request to extract last modification time.
+   * @return Last modification time for file. The value is milliseconds since 
+   *         midnight (00:00:00), January 1, 1970 Greenwich Mean Time (GMT).
+   */
+  NS_IMETHOD_(PRInt64) GetLastModifiedTime(nsIRequest *aRequest);
+
   nsDownloadManager *mDownloadManager;
   nsCOMPtr<nsIURI> mTarget;
 
 private:
   nsString mDisplayName;
   nsCString mEntityID;
 
   nsCOMPtr<nsIURI> mSource;
new file mode 100644
--- /dev/null
+++ b/toolkit/components/downloads/test/unit/test_lastmodified_time_preservation.js
@@ -0,0 +1,96 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Download Manager Test Code.
+ *
+ * The Initial Developer of the Original Code is
+ * Michal Sciubidlo <michal.sciubidlo@gmail.com>
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Andrey Ivanov <andrey.v.ivanov@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * Test last modification time saving for downloaded manager (bug 178506)
+ */
+
+const nsIF = Ci.nsIFile;
+const nsIWBP = Ci.nsIWebBrowserPersist;
+const nsIWPL = Ci.nsIWebProgressListener;
+const nsIDM = Ci.nsIDownloadManager;
+const dm = Cc["@mozilla.org/download-manager;1"].getService(nsIDM);
+const resultFileName = "test_178506" + Date.now() + ".txt";
+const timeHeader = "Sun, 09 Sep 2001 01:46:40 GMT";
+const timeValue = 1000000000 * 1000; //Sun, 09 Sep 2001 01:46:40 GMT in miliseconds
+
+function run_test()
+{
+  // Start the http server with any data.
+  var data = "test_178506";
+  var httpserv = new nsHttpServer();
+  httpserv.registerPathHandler("/test_178506", function(meta, resp) {
+    var body = data;
+    resp.setHeader("Content-Type", "text/html", false);
+    // The only Last-Modified header does matter for test.
+    resp.setHeader("Last-Modified", timeHeader, false);
+    resp.bodyOutputStream.write(body, body.length);
+  });
+  httpserv.start(4444);
+
+  do_test_pending();
+
+  // Setting up test when file downloading is finished.
+  var listener = {
+    onDownloadStateChange: function test_178506(aState, aDownload) {
+      if (aDownload.state == nsIDM.DOWNLOAD_FINISHED) {
+        do_check_eq(destFile.lastModifiedTime, timeValue);
+        httpserv.stop(do_test_finished);
+      }
+    },
+    onStateChange: function(a, b, c, d, e) { },
+    onProgressChange: function(a, b, c, d, e, f, g) { },
+    onSecurityChange: function(a, b, c, d) { }
+  };
+  dm.addListener(listener);
+
+  // Start download.
+  var destFile = dirSvc.get("ProfD", nsIF);
+  destFile.append(resultFileName);
+  if (destFile.exists())
+    destFile.remove(false);
+
+  var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].
+                createInstance(nsIWBP);
+
+  var dl = dm.addDownload(nsIDM.DOWNLOAD_TYPE_DOWNLOAD,
+                          createURI("http://localhost:4444/test_178506"),
+                          createURI(destFile), null, null,
+                          Math.round(Date.now() * 1000), null, persist);
+  persist.progressListener = dl.QueryInterface(nsIWPL);
+  persist.saveURI(dl.source, null, null, null, null, dl.targetFile);
+}