From 8d6f5f1c6c8588c8254dee22448c0c16089763f0 Mon Sep 17 00:00:00 2001 From: Michal Sciubidlo Date: Sun, 10 Jan 2010 09:39:26 +0100 Subject: [PATCH] Bug 178506 - save original last-modified date on file downloads. r=sdwilsh --- netwerk/protocol/ftp/public/nsIFTPChannel.idl | 3 +- netwerk/protocol/ftp/src/nsFTPChannel.h | 12 ++ .../ftp/src/nsFtpConnectionThread.cpp | 28 +++++ .../downloads/src/nsDownloadManager.cpp | 118 +++++++++++++----- .../downloads/src/nsDownloadManager.h | 15 +++ .../test_lastmodified_time_preservation.js | 96 ++++++++++++++ 6 files changed, 239 insertions(+), 33 deletions(-) create mode 100644 toolkit/components/downloads/test/unit/test_lastmodified_time_preservation.js diff --git a/netwerk/protocol/ftp/public/nsIFTPChannel.idl b/netwerk/protocol/ftp/public/nsIFTPChannel.idl index b3fc851781d..4456c9ba6c9 100644 --- a/netwerk/protocol/ftp/public/nsIFTPChannel.idl +++ b/netwerk/protocol/ftp/public/nsIFTPChannel.idl @@ -40,9 +40,10 @@ /** * 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; }; /** diff --git a/netwerk/protocol/ftp/src/nsFTPChannel.h b/netwerk/protocol/ftp/src/nsFTPChannel.h index 314337e2952..f035f4e6feb 100644 --- a/netwerk/protocol/ftp/src/nsFTPChannel.h +++ b/netwerk/protocol/ftp/src/nsFTPChannel.h @@ -85,6 +85,7 @@ public: : mProxyInfo(pi) , mStartPos(0) , mResumeRequested(PR_FALSE) + , mLastModifiedTime(0) { SetURI(uri); } @@ -107,6 +108,16 @@ public: 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; @@ -129,6 +140,7 @@ private: PRUint64 mStartPos; nsCString mEntityID; PRPackedBool mResumeRequested; + PRTime mLastModifiedTime; }; #endif /* nsFTPChannel_h___ */ diff --git a/netwerk/protocol/ftp/src/nsFtpConnectionThread.cpp b/netwerk/protocol/ftp/src/nsFtpConnectionThread.cpp index 0af9f210785..72d6ae93394 100644 --- a/netwerk/protocol/ftp/src/nsFtpConnectionThread.cpp +++ b/netwerk/protocol/ftp/src/nsFtpConnectionThread.cpp @@ -1036,6 +1036,34 @@ nsFtpState::R_mdtm() { 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); } } diff --git a/toolkit/components/downloads/src/nsDownloadManager.cpp b/toolkit/components/downloads/src/nsDownloadManager.cpp index c19eeaece97..d23e2478d53 100644 --- a/toolkit/components/downloads/src/nsDownloadManager.cpp +++ b/toolkit/components/downloads/src/nsDownloadManager.cpp @@ -27,6 +27,8 @@ * Edward Lee * Graeme McCutcheon * Ehsan Akhgari + * Michal Sciubidlo + * Andrey Ivanov * * 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 @@ -64,6 +66,9 @@ #include "nsDownloadManager.h" #include "nsNetUtil.h" +#include "nsIHttpChannel.h" +#include "nsIFileChannel.h" +#include "nsIFTPChannel.h" #include "mozStorageCID.h" #include "nsDocShellCID.h" #include "nsEmbedCID.h" @@ -2197,45 +2202,47 @@ nsDownload::SetState(DownloadState aState) } } } -#if defined(XP_WIN) && !defined(WINCE) + nsCOMPtr fileURL = do_QueryInterface(mTarget); - nsCOMPtr file; - nsAutoString path; + if (fileURL) { + nsCOMPtr file; + if (NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(file))) && file ) { +#if defined(XP_WIN) && !defined(WINCE) + nsAutoString path; + if (NS_SUCCEEDED(file->GetPath(path))) { - if (fileURL && - NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(file))) && - file && - NS_SUCCEEDED(file->GetPath(path))) { + // 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); - // 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) + ::SHAddToRecentDocs(SHARD_PATHW, path.get()); + } - if (addToRecentDocs) - ::SHAddToRecentDocs(SHARD_PATHW, path.get()); + // 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 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 localFileWin(do_QueryInterface(file)); + if (!isTemp && localFileWin) + (void)localFileWin->SetFileAttributesWin(nsILocalFileWin::WFA_SEARCH_INDEXED); +#endif + // After all operations with file, its last modification time needs to + // be updated from request + (void)file->SetLastModifiedTime(GetLastModifiedTime(mRequest)); } } - // 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 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 localFileWin(do_QueryInterface(file)); - if (!isTemp && localFileWin) - (void)localFileWin->SetFileAttributesWin(nsILocalFileWin::WFA_SEARCH_INDEXED); - -#endif // Now remove the download if the user's retention policy is "Remove when Done" if (mDownloadManager->GetRetentionBehavior() == 0) mDownloadManager->RemoveDownload(mID); @@ -3041,3 +3048,50 @@ nsDownload::FailDownload(nsresult aStatus, const PRUnichar *aMessage) NS_ENSURE_SUCCESS(rv, rv); return prompter->Alert(dmWindow, title, message); } + +NS_IMETHODIMP_(PRInt64) +nsDownload::GetLastModifiedTime(nsIRequest *aRequest) +{ + NS_ASSERTION(aRequest, "Must not pass a NULL request in!"); + + PRInt64 timeLastModified = 0; + + // HTTP channels may have a Last-Modified header that we'll use to get the + // last modified time. + nsCOMPtr 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 fileChannel = do_QueryInterface(aRequest); + if (fileChannel) { + nsCOMPtr 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 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; +} diff --git a/toolkit/components/downloads/src/nsDownloadManager.h b/toolkit/components/downloads/src/nsDownloadManager.h index 9435bb0d890..5b3671b3618 100644 --- a/toolkit/components/downloads/src/nsDownloadManager.h +++ b/toolkit/components/downloads/src/nsDownloadManager.h @@ -26,6 +26,8 @@ * Srirang G Doddihal * Edward Lee * Ehsan Akhgari + * Michal Sciubidlo + * Andrey Ivanov * * 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 @@ -401,6 +403,19 @@ protected: */ 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 mTarget; diff --git a/toolkit/components/downloads/test/unit/test_lastmodified_time_preservation.js b/toolkit/components/downloads/test/unit/test_lastmodified_time_preservation.js new file mode 100644 index 00000000000..7f29f69abb0 --- /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 + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Andrey Ivanov + * + * 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); +}