Back to index

lightning-sunbird  0.9+nobinonly
nsDirectoryIndexStream.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* vim:set sw=4 sts=4 et cin: */
00003 /* ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is mozilla.org code.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications Corporation.
00020  * Portions created by the Initial Developer are Copyright (C) 1998
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Bradley Baetz <bbaetz@cs.mcgill.ca>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either the GNU General Public License Version 2 or later (the "GPL"), or
00028  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 
00041 /*
00042 
00043   The converts a filesystem directory into an "HTTP index" stream per
00044   Lou Montulli's original spec:
00045 
00046   http://www.mozilla.org/projects/netlib/dirindexformat.html
00047 
00048  */
00049 
00050 #include "nsEscape.h"
00051 #include "nsDirectoryIndexStream.h"
00052 #include "nsXPIDLString.h"
00053 #include "prio.h"
00054 #include "prlog.h"
00055 #include "prlong.h"
00056 #ifdef PR_LOGGING
00057 static PRLogModuleInfo* gLog;
00058 #endif
00059 
00060 #include "nsISimpleEnumerator.h"
00061 #include "nsICollation.h"
00062 #include "nsILocale.h"
00063 #include "nsILocaleService.h"
00064 #include "nsCollationCID.h"
00065 #include "nsIPlatformCharset.h"
00066 #include "nsReadableUtils.h"
00067 #include "nsURLHelper.h"
00068 #include "nsNetUtil.h"
00069 #include "nsCRT.h"
00070 #include "nsNativeCharsetUtils.h"
00071 
00072 static NS_DEFINE_CID(kCollationFactoryCID, NS_COLLATIONFACTORY_CID);
00073 
00074 // NOTE: This runs on the _file transport_ thread.
00075 // The problem is that now that we're actually doing something with the data,
00076 // we want to do stuff like i18n sorting. However, none of the collation stuff
00077 // is threadsafe.
00078 // So THIS CODE IS ASCII ONLY!!!!!!!! This is no worse than the current
00079 // behaviour, though. See bug 99382.
00080 // When this is fixed, #define THREADSAFE_I18N to get this code working
00081 
00082 //#define THREADSAFE_I18N
00083 
00084 nsDirectoryIndexStream::nsDirectoryIndexStream()
00085     : mOffset(0), mPos(0)
00086 {
00087 #ifdef PR_LOGGING
00088     if (! gLog)
00089         gLog = PR_NewLogModule("nsDirectoryIndexStream");
00090 #endif
00091 
00092     PR_LOG(gLog, PR_LOG_DEBUG,
00093            ("nsDirectoryIndexStream[%p]: created", this));
00094 }
00095 
00096 static int PR_CALLBACK compare(const void* aElement1,
00097                                const void* aElement2,
00098                                void* aData)
00099 {
00100     nsIFile* a = (nsIFile*)aElement1;
00101     nsIFile* b = (nsIFile*)aElement2;
00102 
00103     if (!NS_IsNativeUTF8()) {
00104         // don't check for errors, because we can't report them anyway
00105         nsAutoString name1, name2;
00106         a->GetLeafName(name1);
00107         b->GetLeafName(name2);
00108 
00109         // Note - we should do the collation to do sorting. Why don't we?
00110         // Because that is _slow_. Using TestProtocols to list file:///dev/
00111         // goes from 3 seconds to 22. (This may be why nsXULSortService is
00112         // so slow as well).
00113         // Does this have bad effects? Probably, but since nsXULTree appears
00114         // to use the raw RDF literal value as the sort key (which ammounts to an
00115         // strcmp), it won't be any worse, I think.
00116         // This could be made faster, by creating the keys once,
00117         // but CompareString could still be smarter - see bug 99383 - bbaetz
00118         // NB - 99393 has been WONTFIXed. So if the I18N code is ever made
00119         // threadsafe so that this matters, we'd have to pass through a
00120         // struct { nsIFile*, PRUint8* } with the pre-calculated key.
00121         return Compare(name1, name2);
00122     }
00123 
00124     nsCAutoString name1, name2;
00125     a->GetNativeLeafName(name1);
00126     b->GetNativeLeafName(name2);
00127 
00128     return Compare(name1, name2);
00129 }
00130 
00131 nsresult
00132 nsDirectoryIndexStream::Init(nsIFile* aDir)
00133 {
00134     nsresult rv;
00135     PRBool isDir;
00136     rv = aDir->IsDirectory(&isDir);
00137     if (NS_FAILED(rv)) return rv;
00138     NS_PRECONDITION(isDir, "not a directory");
00139     if (!isDir)
00140         return NS_ERROR_ILLEGAL_VALUE;
00141 
00142 #ifdef PR_LOGGING
00143     if (PR_LOG_TEST(gLog, PR_LOG_DEBUG)) {
00144         nsCAutoString path;
00145         aDir->GetNativePath(path);
00146         PR_LOG(gLog, PR_LOG_DEBUG,
00147                ("nsDirectoryIndexStream[%p]: initialized on %s",
00148                 this, path.get()));
00149     }
00150 #endif
00151 
00152     // Sigh. We have to allocate on the heap because there are no
00153     // assignment operators defined.
00154     nsCOMPtr<nsISimpleEnumerator> iter;
00155     rv = aDir->GetDirectoryEntries(getter_AddRefs(iter));
00156     if (NS_FAILED(rv)) return rv;
00157 
00158     // Now lets sort, because clients expect it that way
00159     // XXX - should we do so here, or when the first item is requested?
00160     // XXX - use insertion sort instead?
00161 
00162     PRBool more;
00163     nsCOMPtr<nsISupports> elem;
00164     while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
00165         rv = iter->GetNext(getter_AddRefs(elem));
00166         if (NS_SUCCEEDED(rv)) {
00167             nsCOMPtr<nsIFile> file = do_QueryInterface(elem);
00168             if (file) {
00169                 nsIFile* f = file;
00170                 NS_ADDREF(f);
00171                 mArray.AppendElement(f);
00172             }
00173         }
00174     }
00175 
00176 #ifdef THREADSAFE_I18N
00177     nsCOMPtr<nsILocaleService> ls = do_GetService(NS_LOCALESERVICE_CONTRACTID,
00178                                                   &rv);
00179     if (NS_FAILED(rv)) return rv;
00180 
00181     nsCOMPtr<nsILocale> locale;
00182     rv = ls->GetApplicationLocale(getter_AddRefs(locale));
00183     if (NS_FAILED(rv)) return rv;
00184     
00185     nsCOMPtr<nsICollationFactory> cf = do_CreateInstance(kCollationFactoryCID,
00186                                                          &rv);
00187     if (NS_FAILED(rv)) return rv;
00188 
00189     nsCOMPtr<nsICollation> coll;
00190     rv = cf->CreateCollation(locale, getter_AddRefs(coll));
00191     if (NS_FAILED(rv)) return rv;
00192 
00193     mArray.Sort(compare, coll);
00194 #else
00195     mArray.Sort(compare, nsnull);
00196 #endif
00197 
00198     mBuf.AppendLiteral("300: ");
00199     nsCAutoString url;
00200     rv = net_GetURLSpecFromFile(aDir, url);
00201     if (NS_FAILED(rv)) return rv;
00202     mBuf.Append(url);
00203     mBuf.Append('\n');
00204 
00205     mBuf.AppendLiteral("200: filename content-length last-modified file-type\n");
00206 
00207     return NS_OK;
00208 }
00209 
00210 nsDirectoryIndexStream::~nsDirectoryIndexStream()
00211 {
00212     PRInt32 i;
00213     for (i=0; i<mArray.Count(); ++i) {
00214         nsIFile* elem = (nsIFile*)mArray.ElementAt(i);
00215         NS_RELEASE(elem);
00216     }
00217 
00218     PR_LOG(gLog, PR_LOG_DEBUG,
00219            ("nsDirectoryIndexStream[%p]: destroyed", this));
00220 }
00221 
00222 nsresult
00223 nsDirectoryIndexStream::Create(nsIFile* aDir, nsIInputStream** aResult)
00224 {
00225     nsDirectoryIndexStream* result = new nsDirectoryIndexStream();
00226     if (! result)
00227         return NS_ERROR_OUT_OF_MEMORY;
00228 
00229     nsresult rv;
00230     rv = result->Init(aDir);
00231     if (NS_FAILED(rv)) {
00232         delete result;
00233         return rv;
00234     }
00235 
00236     *aResult = result;
00237     NS_ADDREF(*aResult);
00238     return NS_OK;
00239 }
00240 
00241 NS_IMPL_THREADSAFE_ISUPPORTS1(nsDirectoryIndexStream, nsIInputStream)
00242 
00243 // The below routines are proxied to the UI thread!
00244 NS_IMETHODIMP
00245 nsDirectoryIndexStream::Close()
00246 {
00247     return NS_OK;
00248 }
00249 
00250 NS_IMETHODIMP
00251 nsDirectoryIndexStream::Available(PRUint32* aLength)
00252 {
00253     // If there's data in our buffer, use that
00254     if (mOffset < (PRInt32)mBuf.Length()) {
00255         *aLength = mBuf.Length() - mOffset;
00256         return NS_OK;
00257     }
00258 
00259     // Returning one byte is not ideal, but good enough
00260     *aLength = (mPos < mArray.Count()) ? 1 : 0;
00261     return NS_OK;
00262 }
00263 
00264 NS_IMETHODIMP
00265 nsDirectoryIndexStream::Read(char* aBuf, PRUint32 aCount, PRUint32* aReadCount)
00266 {
00267     PRUint32 nread = 0;
00268 
00269     // If anything is enqueued (or left-over) in mBuf, then feed it to
00270     // the reader first.
00271     while (mOffset < (PRInt32)mBuf.Length() && aCount != 0) {
00272         *(aBuf++) = char(mBuf.CharAt(mOffset++));
00273         --aCount;
00274         ++nread;
00275     }
00276 
00277     // Room left?
00278     if (aCount > 0) {
00279         mOffset = 0;
00280         mBuf.Truncate();
00281 
00282         // Okay, now we'll suck stuff off of our iterator into the mBuf...
00283         while (PRUint32(mBuf.Length()) < aCount) {
00284             PRBool more = mPos < mArray.Count();
00285             if (!more) break;
00286             
00287             nsCOMPtr<nsIFile> current = (nsIFile*)mArray.ElementAt(mPos);
00288             ++mPos;
00289 
00290 #ifdef PR_LOGGING
00291             if (PR_LOG_TEST(gLog, PR_LOG_DEBUG)) {
00292                 nsCAutoString path;
00293                 current->GetNativePath(path);
00294                 PR_LOG(gLog, PR_LOG_DEBUG,
00295                        ("nsDirectoryIndexStream[%p]: iterated %s",
00296                         this, path.get()));
00297             }
00298 #endif
00299 
00300             // rjc: don't return hidden files/directories!
00301             // bbaetz: why not?
00302             nsresult rv;
00303 #ifndef XP_UNIX
00304             PRBool hidden = PR_FALSE;
00305             current->IsHidden(&hidden);
00306             if (hidden) {
00307                 PR_LOG(gLog, PR_LOG_DEBUG,
00308                        ("nsDirectoryIndexStream[%p]: skipping hidden file/directory",
00309                         this));
00310                 continue;
00311             }
00312 #endif
00313 
00314             PRInt64 fileSize = 0;
00315             current->GetFileSize( &fileSize );
00316 
00317             PRInt64 fileInfoModifyTime = 0;
00318             current->GetLastModifiedTime( &fileInfoModifyTime );
00319             fileInfoModifyTime *= PR_USEC_PER_MSEC;
00320 
00321             mBuf.AppendLiteral("201: ");
00322 
00323             // The "filename" field
00324             char* escaped = nsnull;
00325             if (!NS_IsNativeUTF8()) {
00326                 nsAutoString leafname;
00327                 rv = current->GetLeafName(leafname);
00328                 if (NS_FAILED(rv)) return rv;
00329                 if (!leafname.IsEmpty())
00330                     escaped = nsEscape(NS_ConvertUTF16toUTF8(leafname).get(), url_Path);
00331             } else {
00332                 nsCAutoString leafname;
00333                 rv = current->GetNativeLeafName(leafname);
00334                 if (NS_FAILED(rv)) return rv;
00335                 if (!leafname.IsEmpty())
00336                     escaped = nsEscape(leafname.get(), url_Path);
00337             }
00338             if (escaped) {
00339                 mBuf += escaped;
00340                 mBuf.Append(' ');
00341                 nsMemory::Free(escaped);
00342             }
00343 
00344             // The "content-length" field
00345             mBuf.AppendInt(fileSize, 10);
00346             mBuf.Append(' ');
00347 
00348             // The "last-modified" field
00349             PRExplodedTime tm;
00350             PR_ExplodeTime(fileInfoModifyTime, PR_GMTParameters, &tm);
00351             {
00352                 char buf[64];
00353                 PR_FormatTimeUSEnglish(buf, sizeof(buf), "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm);
00354                 mBuf.Append(buf);
00355             }
00356 
00357             // The "file-type" field
00358             PRBool isFile = PR_TRUE;
00359             current->IsFile(&isFile);
00360             if (isFile) {
00361                 mBuf.AppendLiteral("FILE ");
00362             }
00363             else {
00364                 PRBool isDir;
00365                 rv = current->IsDirectory(&isDir);
00366                 if (NS_FAILED(rv)) return rv; 
00367                 if (isDir) {
00368                     mBuf.AppendLiteral("DIRECTORY ");
00369                 }
00370                 else {
00371                     PRBool isLink;
00372                     rv = current->IsSymlink(&isLink);
00373                     if (NS_FAILED(rv)) return rv; 
00374                     if (isLink) {
00375                         mBuf.AppendLiteral("SYMBOLIC-LINK ");
00376                     }
00377                 }
00378             }
00379 
00380             mBuf.Append('\n');
00381         }
00382 
00383         // ...and once we've either run out of directory entries, or
00384         // filled up the buffer, then we'll push it to the reader.
00385         while (mOffset < (PRInt32)mBuf.Length() && aCount != 0) {
00386             *(aBuf++) = char(mBuf.CharAt(mOffset++));
00387             --aCount;
00388             ++nread;
00389         }
00390     }
00391 
00392     *aReadCount = nread;
00393     return NS_OK;
00394 }
00395 
00396 NS_IMETHODIMP
00397 nsDirectoryIndexStream::ReadSegments(nsWriteSegmentFun writer, void * closure, PRUint32 count, PRUint32 *_retval)
00398 {
00399     return NS_ERROR_NOT_IMPLEMENTED;
00400 }
00401 
00402 NS_IMETHODIMP
00403 nsDirectoryIndexStream::IsNonBlocking(PRBool *aNonBlocking)
00404 {
00405     *aNonBlocking = PR_FALSE;
00406     return NS_OK;
00407 }