LCOV - code coverage report
Current view: directory - content/media - nsMediaCache.cpp (source / functions) Found Hit Coverage
Test: app.info Lines: 1074 0 0.0 %
Date: 2012-06-02 Functions: 92 0 0.0 %

       1                 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2                 : /* vim:set ts=2 sw=2 sts=2 et cindent: */
       3                 : /* ***** BEGIN LICENSE BLOCK *****
       4                 :  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
       5                 :  *
       6                 :  * The contents of this file are subject to the Mozilla Public License Version
       7                 :  * 1.1 (the "License"); you may not use this file except in compliance with
       8                 :  * the License. You may obtain a copy of the License at
       9                 :  * http://www.mozilla.org/MPL/
      10                 :  *
      11                 :  * Software distributed under the License is distributed on an "AS IS" basis,
      12                 :  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
      13                 :  * for the specific language governing rights and limitations under the
      14                 :  * License.
      15                 :  *
      16                 :  * The Original Code is Mozilla code.
      17                 :  *
      18                 :  * The Initial Developer of the Original Code is the Mozilla Corporation.
      19                 :  * Portions created by the Initial Developer are Copyright (C) 2009
      20                 :  * the Initial Developer. All Rights Reserved.
      21                 :  *
      22                 :  * Contributor(s):
      23                 :  *  Robert O'Callahan <robert@ocallahan.org>
      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                 : #include "mozilla/ReentrantMonitor.h"
      40                 : #include "mozilla/XPCOM.h"
      41                 : 
      42                 : #include "nsMediaCache.h"
      43                 : #include "nsDirectoryServiceUtils.h"
      44                 : #include "nsDirectoryServiceDefs.h"
      45                 : #include "nsXULAppAPI.h"
      46                 : #include "nsNetUtil.h"
      47                 : #include "prio.h"
      48                 : #include "nsThreadUtils.h"
      49                 : #include "MediaResource.h"
      50                 : #include "nsMathUtils.h"
      51                 : #include "prlog.h"
      52                 : #include "nsIPrivateBrowsingService.h"
      53                 : #include "mozilla/Preferences.h"
      54                 : 
      55                 : using namespace mozilla;
      56                 : 
      57                 : #ifdef PR_LOGGING
      58                 : PRLogModuleInfo* gMediaCacheLog;
      59                 : #define LOG(type, msg) PR_LOG(gMediaCacheLog, type, msg)
      60                 : #else
      61                 : #define LOG(type, msg)
      62                 : #endif
      63                 : 
      64                 : // Readahead blocks for non-seekable streams will be limited to this
      65                 : // fraction of the cache space. We don't normally evict such blocks
      66                 : // because replacing them requires a seek, but we need to make sure
      67                 : // they don't monopolize the cache.
      68                 : static const double NONSEEKABLE_READAHEAD_MAX = 0.5;
      69                 : 
      70                 : // Assume that any replaying or backward seeking will happen
      71                 : // this far in the future (in seconds). This is a random guess/estimate
      72                 : // penalty to account for the possibility that we might not replay at
      73                 : // all.
      74                 : static const PRUint32 REPLAY_DELAY = 30;
      75                 : 
      76                 : // When looking for a reusable block, scan forward this many blocks
      77                 : // from the desired "best" block location to look for free blocks,
      78                 : // before we resort to scanning the whole cache. The idea is to try to
      79                 : // store runs of stream blocks close-to-consecutively in the cache if we
      80                 : // can.
      81                 : static const PRUint32 FREE_BLOCK_SCAN_LIMIT = 16;
      82                 : 
      83                 : #ifdef DEBUG
      84                 : // Turn this on to do very expensive cache state validation
      85                 : // #define DEBUG_VERIFY_CACHE
      86                 : #endif
      87                 : 
      88                 : // There is at most one media cache (although that could quite easily be
      89                 : // relaxed if we wanted to manage multiple caches with independent
      90                 : // size limits).
      91                 : static nsMediaCache* gMediaCache;
      92                 : 
      93                 : class nsMediaCacheFlusher : public nsIObserver,
      94                 :                             public nsSupportsWeakReference {
      95               0 :   nsMediaCacheFlusher() {}
      96                 :   ~nsMediaCacheFlusher();
      97                 : public:
      98                 :   NS_DECL_ISUPPORTS
      99                 :   NS_DECL_NSIOBSERVER
     100                 : 
     101                 :   static void Init();
     102                 : };
     103                 : 
     104                 : static nsMediaCacheFlusher* gMediaCacheFlusher;
     105                 : 
     106               0 : NS_IMPL_ISUPPORTS2(nsMediaCacheFlusher, nsIObserver, nsISupportsWeakReference)
     107                 : 
     108               0 : nsMediaCacheFlusher::~nsMediaCacheFlusher()
     109                 : {
     110               0 :   gMediaCacheFlusher = nsnull;
     111               0 : }
     112                 : 
     113               0 : void nsMediaCacheFlusher::Init()
     114                 : {
     115               0 :   if (gMediaCacheFlusher) {
     116               0 :     return;
     117                 :   }
     118                 : 
     119               0 :   gMediaCacheFlusher = new nsMediaCacheFlusher();
     120               0 :   NS_ADDREF(gMediaCacheFlusher);
     121                 : 
     122                 :   nsCOMPtr<nsIObserverService> observerService =
     123               0 :     mozilla::services::GetObserverService();
     124               0 :   if (observerService) {
     125               0 :     observerService->AddObserver(gMediaCacheFlusher, NS_PRIVATE_BROWSING_SWITCH_TOPIC, true);
     126                 :   }
     127                 : }
     128                 : 
     129                 : class nsMediaCache {
     130                 : public:
     131                 :   friend class nsMediaCacheStream::BlockList;
     132                 :   typedef nsMediaCacheStream::BlockList BlockList;
     133                 :   enum {
     134                 :     BLOCK_SIZE = nsMediaCacheStream::BLOCK_SIZE
     135                 :   };
     136                 : 
     137               0 :   nsMediaCache() : mNextResourceID(1),
     138                 :     mReentrantMonitor("nsMediaCache.mReentrantMonitor"),
     139                 :     mFD(nsnull), mFDCurrentPos(0), mUpdateQueued(false)
     140                 : #ifdef DEBUG
     141               0 :     , mInUpdate(false)
     142                 : #endif
     143                 :   {
     144               0 :     MOZ_COUNT_CTOR(nsMediaCache);
     145               0 :   }
     146               0 :   ~nsMediaCache() {
     147               0 :     NS_ASSERTION(mStreams.IsEmpty(), "Stream(s) still open!");
     148               0 :     Truncate();
     149               0 :     NS_ASSERTION(mIndex.Length() == 0, "Blocks leaked?");
     150               0 :     if (mFD) {
     151               0 :       PR_Close(mFD);
     152                 :     }
     153               0 :     MOZ_COUNT_DTOR(nsMediaCache);
     154               0 :   }
     155                 : 
     156                 :   // Main thread only. Creates the backing cache file. If this fails,
     157                 :   // then the cache is still in a semi-valid state; mFD will be null,
     158                 :   // so all I/O on the cache file will fail.
     159                 :   nsresult Init();
     160                 :   // Shut down the global cache if it's no longer needed. We shut down
     161                 :   // the cache as soon as there are no streams. This means that during
     162                 :   // normal operation we are likely to start up the cache and shut it down
     163                 :   // many times, but that's OK since starting it up is cheap and
     164                 :   // shutting it down cleans things up and releases disk space.
     165                 :   static void MaybeShutdown();
     166                 : 
     167                 :   // Brutally flush the cache contents. Main thread only.
     168                 :   static void Flush();
     169                 :   void FlushInternal();
     170                 : 
     171                 :   // Cache-file access methods. These are the lowest-level cache methods.
     172                 :   // mReentrantMonitor must be held; these can be called on any thread.
     173                 :   // This can return partial reads.
     174                 :   nsresult ReadCacheFile(PRInt64 aOffset, void* aData, PRInt32 aLength,
     175                 :                          PRInt32* aBytes);
     176                 :   // This will fail if all aLength bytes are not read
     177                 :   nsresult ReadCacheFileAllBytes(PRInt64 aOffset, void* aData, PRInt32 aLength);
     178                 :   // This will fail if all aLength bytes are not written
     179                 :   nsresult WriteCacheFile(PRInt64 aOffset, const void* aData, PRInt32 aLength);
     180                 : 
     181               0 :   PRInt64 AllocateResourceID()
     182                 :   {
     183               0 :     mReentrantMonitor.AssertCurrentThreadIn();
     184               0 :     return mNextResourceID++;
     185                 :   }
     186                 : 
     187                 :   // mReentrantMonitor must be held, called on main thread.
     188                 :   // These methods are used by the stream to set up and tear down streams,
     189                 :   // and to handle reads and writes.
     190                 :   // Add aStream to the list of streams.
     191                 :   void OpenStream(nsMediaCacheStream* aStream);
     192                 :   // Remove aStream from the list of streams.
     193                 :   void ReleaseStream(nsMediaCacheStream* aStream);
     194                 :   // Free all blocks belonging to aStream.
     195                 :   void ReleaseStreamBlocks(nsMediaCacheStream* aStream);
     196                 :   // Find a cache entry for this data, and write the data into it
     197                 :   void AllocateAndWriteBlock(nsMediaCacheStream* aStream, const void* aData,
     198                 :                              nsMediaCacheStream::ReadMode aMode);
     199                 : 
     200                 :   // mReentrantMonitor must be held; can be called on any thread
     201                 :   // Notify the cache that a seek has been requested. Some blocks may
     202                 :   // need to change their class between PLAYED_BLOCK and READAHEAD_BLOCK.
     203                 :   // This does not trigger channel seeks directly, the next Update()
     204                 :   // will do that if necessary. The caller will call QueueUpdate().
     205                 :   void NoteSeek(nsMediaCacheStream* aStream, PRInt64 aOldOffset);
     206                 :   // Notify the cache that a block has been read from. This is used
     207                 :   // to update last-use times. The block may not actually have a
     208                 :   // cache entry yet since Read can read data from a stream's
     209                 :   // in-memory mPartialBlockBuffer while the block is only partly full,
     210                 :   // and thus hasn't yet been committed to the cache. The caller will
     211                 :   // call QueueUpdate().
     212                 :   void NoteBlockUsage(nsMediaCacheStream* aStream, PRInt32 aBlockIndex,
     213                 :                       nsMediaCacheStream::ReadMode aMode, TimeStamp aNow);
     214                 :   // Mark aStream as having the block, adding it as an owner.
     215                 :   void AddBlockOwnerAsReadahead(PRInt32 aBlockIndex, nsMediaCacheStream* aStream,
     216                 :                                 PRInt32 aStreamBlockIndex);
     217                 : 
     218                 :   // This queues a call to Update() on the main thread.
     219                 :   void QueueUpdate();
     220                 : 
     221                 :   // Updates the cache state asynchronously on the main thread:
     222                 :   // -- try to trim the cache back to its desired size, if necessary
     223                 :   // -- suspend channels that are going to read data that's lower priority
     224                 :   // than anything currently cached
     225                 :   // -- resume channels that are going to read data that's higher priority
     226                 :   // than something currently cached
     227                 :   // -- seek channels that need to seek to a new location
     228                 :   void Update();
     229                 : 
     230                 : #ifdef DEBUG_VERIFY_CACHE
     231                 :   // Verify invariants, especially block list invariants
     232                 :   void Verify();
     233                 : #else
     234               0 :   void Verify() {}
     235                 : #endif
     236                 : 
     237               0 :   ReentrantMonitor& GetReentrantMonitor() { return mReentrantMonitor; }
     238                 : 
     239                 :   /**
     240                 :    * An iterator that makes it easy to iterate through all streams that
     241                 :    * have a given resource ID and are not closed.
     242                 :    */
     243                 :   class ResourceStreamIterator {
     244                 :   public:
     245               0 :     ResourceStreamIterator(PRInt64 aResourceID) :
     246               0 :       mResourceID(aResourceID), mNext(0) {}
     247               0 :     nsMediaCacheStream* Next()
     248                 :     {
     249               0 :       while (mNext < gMediaCache->mStreams.Length()) {
     250               0 :         nsMediaCacheStream* stream = gMediaCache->mStreams[mNext];
     251               0 :         ++mNext;
     252               0 :         if (stream->GetResourceID() == mResourceID && !stream->IsClosed())
     253               0 :           return stream;
     254                 :       }
     255               0 :       return nsnull;
     256                 :     }
     257                 :   private:
     258                 :     PRInt64  mResourceID;
     259                 :     PRUint32 mNext;
     260                 :   };
     261                 : 
     262                 : protected:
     263                 :   // Find a free or reusable block and return its index. If there are no
     264                 :   // free blocks and no reusable blocks, add a new block to the cache
     265                 :   // and return it. Can return -1 on OOM.
     266                 :   PRInt32 FindBlockForIncomingData(TimeStamp aNow, nsMediaCacheStream* aStream);
     267                 :   // Find a reusable block --- a free block, if there is one, otherwise
     268                 :   // the reusable block with the latest predicted-next-use, or -1 if
     269                 :   // there aren't any freeable blocks. Only block indices less than
     270                 :   // aMaxSearchBlockIndex are considered. If aForStream is non-null,
     271                 :   // then aForStream and aForStreamBlock indicate what media data will
     272                 :   // be placed; FindReusableBlock will favour returning free blocks
     273                 :   // near other blocks for that point in the stream.
     274                 :   PRInt32 FindReusableBlock(TimeStamp aNow,
     275                 :                             nsMediaCacheStream* aForStream,
     276                 :                             PRInt32 aForStreamBlock,
     277                 :                             PRInt32 aMaxSearchBlockIndex);
     278                 :   bool BlockIsReusable(PRInt32 aBlockIndex);
     279                 :   // Given a list of blocks sorted with the most reusable blocks at the
     280                 :   // end, find the last block whose stream is not pinned (if any)
     281                 :   // and whose cache entry index is less than aBlockIndexLimit
     282                 :   // and append it to aResult.
     283                 :   void AppendMostReusableBlock(BlockList* aBlockList,
     284                 :                                nsTArray<PRUint32>* aResult,
     285                 :                                PRInt32 aBlockIndexLimit);
     286                 : 
     287                 :   enum BlockClass {
     288                 :     // block belongs to mMetadataBlockList because data has been consumed
     289                 :     // from it in "metadata mode" --- in particular blocks read during
     290                 :     // Ogg seeks go into this class. These blocks may have played data
     291                 :     // in them too.
     292                 :     METADATA_BLOCK,
     293                 :     // block belongs to mPlayedBlockList because its offset is
     294                 :     // less than the stream's current reader position
     295                 :     PLAYED_BLOCK,
     296                 :     // block belongs to the stream's mReadaheadBlockList because its
     297                 :     // offset is greater than or equal to the stream's current
     298                 :     // reader position
     299                 :     READAHEAD_BLOCK
     300                 :   };
     301                 : 
     302               0 :   struct BlockOwner {
     303               0 :     BlockOwner() : mStream(nsnull), mClass(READAHEAD_BLOCK) {}
     304                 : 
     305                 :     // The stream that owns this block, or null if the block is free.
     306                 :     nsMediaCacheStream* mStream;
     307                 :     // The block index in the stream. Valid only if mStream is non-null.
     308                 :     PRUint32            mStreamBlock;
     309                 :     // Time at which this block was last used. Valid only if
     310                 :     // mClass is METADATA_BLOCK or PLAYED_BLOCK.
     311                 :     TimeStamp           mLastUseTime;
     312                 :     BlockClass          mClass;
     313                 :   };
     314                 : 
     315               0 :   struct Block {
     316                 :     // Free blocks have an empty mOwners array
     317                 :     nsTArray<BlockOwner> mOwners;
     318                 :   };
     319                 : 
     320                 :   // Get the BlockList that the block should belong to given its
     321                 :   // current owner
     322                 :   BlockList* GetListForBlock(BlockOwner* aBlock);
     323                 :   // Get the BlockOwner for the given block index and owning stream
     324                 :   // (returns null if the stream does not own the block)
     325                 :   BlockOwner* GetBlockOwner(PRInt32 aBlockIndex, nsMediaCacheStream* aStream);
     326                 :   // Returns true iff the block is free
     327               0 :   bool IsBlockFree(PRInt32 aBlockIndex)
     328               0 :   { return mIndex[aBlockIndex].mOwners.IsEmpty(); }
     329                 :   // Add the block to the free list and mark its streams as not having
     330                 :   // the block in cache
     331                 :   void FreeBlock(PRInt32 aBlock);
     332                 :   // Mark aStream as not having the block, removing it as an owner. If
     333                 :   // the block has no more owners it's added to the free list.
     334                 :   void RemoveBlockOwner(PRInt32 aBlockIndex, nsMediaCacheStream* aStream);
     335                 :   // Swap all metadata associated with the two blocks. The caller
     336                 :   // is responsible for swapping up any cache file state.
     337                 :   void SwapBlocks(PRInt32 aBlockIndex1, PRInt32 aBlockIndex2);
     338                 :   // Insert the block into the readahead block list for the stream
     339                 :   // at the right point in the list.
     340                 :   void InsertReadaheadBlock(BlockOwner* aBlockOwner, PRInt32 aBlockIndex);
     341                 : 
     342                 :   // Guess the duration until block aBlock will be next used
     343                 :   TimeDuration PredictNextUse(TimeStamp aNow, PRInt32 aBlock);
     344                 :   // Guess the duration until the next incoming data on aStream will be used
     345                 :   TimeDuration PredictNextUseForIncomingData(nsMediaCacheStream* aStream);
     346                 : 
     347                 :   // Truncate the file and index array if there are free blocks at the
     348                 :   // end
     349                 :   void Truncate();
     350                 : 
     351                 :   // This member is main-thread only. It's used to allocate unique
     352                 :   // resource IDs to streams.
     353                 :   PRInt64                       mNextResourceID;
     354                 :   // This member is main-thread only. It contains all the streams.
     355                 :   nsTArray<nsMediaCacheStream*> mStreams;
     356                 : 
     357                 :   // The monitor protects all the data members here. Also, off-main-thread
     358                 :   // readers that need to block will Wait() on this monitor. When new
     359                 :   // data becomes available in the cache, we NotifyAll() on this monitor.
     360                 :   ReentrantMonitor         mReentrantMonitor;
     361                 :   // The Blocks describing the cache entries.
     362                 :   nsTArray<Block> mIndex;
     363                 :   // The file descriptor of the cache file. The file will be deleted
     364                 :   // by the operating system when this is closed.
     365                 :   PRFileDesc*     mFD;
     366                 :   // The current file offset in the cache file.
     367                 :   PRInt64         mFDCurrentPos;
     368                 :   // The list of free blocks; they are not ordered.
     369                 :   BlockList       mFreeBlocks;
     370                 :   // True if an event to run Update() has been queued but not processed
     371                 :   bool            mUpdateQueued;
     372                 : #ifdef DEBUG
     373                 :   bool            mInUpdate;
     374                 : #endif
     375                 : };
     376                 : 
     377                 : NS_IMETHODIMP
     378               0 : nsMediaCacheFlusher::Observe(nsISupports *aSubject, char const *aTopic, PRUnichar const *aData)
     379                 : {
     380               0 :   if (strcmp(aTopic, NS_PRIVATE_BROWSING_SWITCH_TOPIC) == 0 &&
     381               0 :       NS_LITERAL_STRING(NS_PRIVATE_BROWSING_LEAVE).Equals(aData)) {
     382               0 :     nsMediaCache::Flush();
     383                 :   }
     384               0 :   return NS_OK;
     385                 : }
     386                 : 
     387               0 : void nsMediaCacheStream::BlockList::AddFirstBlock(PRInt32 aBlock)
     388                 : {
     389               0 :   NS_ASSERTION(!mEntries.GetEntry(aBlock), "Block already in list");
     390               0 :   Entry* entry = mEntries.PutEntry(aBlock);
     391                 : 
     392               0 :   if (mFirstBlock < 0) {
     393               0 :     entry->mNextBlock = entry->mPrevBlock = aBlock;
     394                 :   } else {
     395               0 :     entry->mNextBlock = mFirstBlock;
     396               0 :     entry->mPrevBlock = mEntries.GetEntry(mFirstBlock)->mPrevBlock;
     397               0 :     mEntries.GetEntry(entry->mNextBlock)->mPrevBlock = aBlock;
     398               0 :     mEntries.GetEntry(entry->mPrevBlock)->mNextBlock = aBlock;
     399                 :   }
     400               0 :   mFirstBlock = aBlock;
     401               0 :   ++mCount;
     402               0 : }
     403                 : 
     404               0 : void nsMediaCacheStream::BlockList::AddAfter(PRInt32 aBlock, PRInt32 aBefore)
     405                 : {
     406               0 :   NS_ASSERTION(!mEntries.GetEntry(aBlock), "Block already in list");
     407               0 :   Entry* entry = mEntries.PutEntry(aBlock);
     408                 : 
     409               0 :   Entry* addAfter = mEntries.GetEntry(aBefore);
     410               0 :   NS_ASSERTION(addAfter, "aBefore not in list");
     411                 : 
     412               0 :   entry->mNextBlock = addAfter->mNextBlock;
     413               0 :   entry->mPrevBlock = aBefore;
     414               0 :   mEntries.GetEntry(entry->mNextBlock)->mPrevBlock = aBlock;
     415               0 :   mEntries.GetEntry(entry->mPrevBlock)->mNextBlock = aBlock;
     416               0 :   ++mCount;
     417               0 : }
     418                 : 
     419               0 : void nsMediaCacheStream::BlockList::RemoveBlock(PRInt32 aBlock)
     420                 : {
     421               0 :   Entry* entry = mEntries.GetEntry(aBlock);
     422               0 :   NS_ASSERTION(entry, "Block not in list");
     423                 : 
     424               0 :   if (entry->mNextBlock == aBlock) {
     425               0 :     NS_ASSERTION(entry->mPrevBlock == aBlock, "Linked list inconsistency");
     426               0 :     NS_ASSERTION(mFirstBlock == aBlock, "Linked list inconsistency");
     427               0 :     mFirstBlock = -1;
     428                 :   } else {
     429               0 :     if (mFirstBlock == aBlock) {
     430               0 :       mFirstBlock = entry->mNextBlock;
     431                 :     }
     432               0 :     mEntries.GetEntry(entry->mNextBlock)->mPrevBlock = entry->mPrevBlock;
     433               0 :     mEntries.GetEntry(entry->mPrevBlock)->mNextBlock = entry->mNextBlock;
     434                 :   }
     435               0 :   mEntries.RemoveEntry(aBlock);
     436               0 :   --mCount;
     437               0 : }
     438                 : 
     439               0 : PRInt32 nsMediaCacheStream::BlockList::GetLastBlock() const
     440                 : {
     441               0 :   if (mFirstBlock < 0)
     442               0 :     return -1;
     443               0 :   return mEntries.GetEntry(mFirstBlock)->mPrevBlock;
     444                 : }
     445                 : 
     446               0 : PRInt32 nsMediaCacheStream::BlockList::GetNextBlock(PRInt32 aBlock) const
     447                 : {
     448               0 :   PRInt32 block = mEntries.GetEntry(aBlock)->mNextBlock;
     449               0 :   if (block == mFirstBlock)
     450               0 :     return -1;
     451               0 :   return block;
     452                 : }
     453                 : 
     454               0 : PRInt32 nsMediaCacheStream::BlockList::GetPrevBlock(PRInt32 aBlock) const
     455                 : {
     456               0 :   if (aBlock == mFirstBlock)
     457               0 :     return -1;
     458               0 :   return mEntries.GetEntry(aBlock)->mPrevBlock;
     459                 : }
     460                 : 
     461                 : #ifdef DEBUG
     462               0 : void nsMediaCacheStream::BlockList::Verify()
     463                 : {
     464               0 :   PRInt32 count = 0;
     465               0 :   if (mFirstBlock >= 0) {
     466               0 :     PRInt32 block = mFirstBlock;
     467               0 :     do {
     468               0 :       Entry* entry = mEntries.GetEntry(block);
     469               0 :       NS_ASSERTION(mEntries.GetEntry(entry->mNextBlock)->mPrevBlock == block,
     470                 :                    "Bad prev link");
     471               0 :       NS_ASSERTION(mEntries.GetEntry(entry->mPrevBlock)->mNextBlock == block,
     472                 :                    "Bad next link");
     473               0 :       block = entry->mNextBlock;
     474               0 :       ++count;
     475                 :     } while (block != mFirstBlock);
     476                 :   }
     477               0 :   NS_ASSERTION(count == mCount, "Bad count");
     478               0 : }
     479                 : #endif
     480                 : 
     481               0 : static void UpdateSwappedBlockIndex(PRInt32* aBlockIndex,
     482                 :     PRInt32 aBlock1Index, PRInt32 aBlock2Index)
     483                 : {
     484               0 :   PRInt32 index = *aBlockIndex;
     485               0 :   if (index == aBlock1Index) {
     486               0 :     *aBlockIndex = aBlock2Index;
     487               0 :   } else if (index == aBlock2Index) {
     488               0 :     *aBlockIndex = aBlock1Index;
     489                 :   }
     490               0 : }
     491                 : 
     492                 : void
     493               0 : nsMediaCacheStream::BlockList::NotifyBlockSwapped(PRInt32 aBlockIndex1,
     494                 :                                                   PRInt32 aBlockIndex2)
     495                 : {
     496               0 :   Entry* e1 = mEntries.GetEntry(aBlockIndex1);
     497               0 :   Entry* e2 = mEntries.GetEntry(aBlockIndex2);
     498               0 :   PRInt32 e1Prev = -1, e1Next = -1, e2Prev = -1, e2Next = -1;
     499                 : 
     500                 :   // Fix mFirstBlock
     501               0 :   UpdateSwappedBlockIndex(&mFirstBlock, aBlockIndex1, aBlockIndex2);
     502                 : 
     503                 :   // Fix mNextBlock/mPrevBlock links. First capture previous/next links
     504                 :   // so we don't get confused due to aliasing.
     505               0 :   if (e1) {
     506               0 :     e1Prev = e1->mPrevBlock;
     507               0 :     e1Next = e1->mNextBlock;
     508                 :   }
     509               0 :   if (e2) {
     510               0 :     e2Prev = e2->mPrevBlock;
     511               0 :     e2Next = e2->mNextBlock;
     512                 :   }
     513                 :   // Update the entries.
     514               0 :   if (e1) {
     515               0 :     mEntries.GetEntry(e1Prev)->mNextBlock = aBlockIndex2;
     516               0 :     mEntries.GetEntry(e1Next)->mPrevBlock = aBlockIndex2;
     517                 :   }
     518               0 :   if (e2) {
     519               0 :     mEntries.GetEntry(e2Prev)->mNextBlock = aBlockIndex1;
     520               0 :     mEntries.GetEntry(e2Next)->mPrevBlock = aBlockIndex1;
     521                 :   }
     522                 : 
     523                 :   // Fix hashtable keys. First remove stale entries.
     524               0 :   if (e1) {
     525               0 :     e1Prev = e1->mPrevBlock;
     526               0 :     e1Next = e1->mNextBlock;
     527               0 :     mEntries.RemoveEntry(aBlockIndex1);
     528                 :     // Refresh pointer after hashtable mutation.
     529               0 :     e2 = mEntries.GetEntry(aBlockIndex2);
     530                 :   }
     531               0 :   if (e2) {
     532               0 :     e2Prev = e2->mPrevBlock;
     533               0 :     e2Next = e2->mNextBlock;
     534               0 :     mEntries.RemoveEntry(aBlockIndex2);
     535                 :   }
     536                 :   // Put new entries back.
     537               0 :   if (e1) {
     538               0 :     e1 = mEntries.PutEntry(aBlockIndex2);
     539               0 :     e1->mNextBlock = e1Next;
     540               0 :     e1->mPrevBlock = e1Prev;
     541                 :   }
     542               0 :   if (e2) {
     543               0 :     e2 = mEntries.PutEntry(aBlockIndex1);
     544               0 :     e2->mNextBlock = e2Next;
     545               0 :     e2->mPrevBlock = e2Prev;
     546                 :   }
     547               0 : }
     548                 : 
     549                 : nsresult
     550               0 : nsMediaCache::Init()
     551                 : {
     552               0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     553               0 :   NS_ASSERTION(!mFD, "Cache file already open?");
     554                 : 
     555                 :   // In single process Gecko, store the media cache in the profile directory
     556                 :   // so that multiple users can use separate media caches concurrently.
     557                 :   // In multi-process Gecko, there is no profile dir, so just store it in the
     558                 :   // system temp directory instead.
     559                 :   nsresult rv;
     560               0 :   nsCOMPtr<nsIFile> tmp;
     561               0 :   const char* dir = (XRE_GetProcessType() == GeckoProcessType_Content) ?
     562               0 :     NS_OS_TEMP_DIR : NS_APP_USER_PROFILE_LOCAL_50_DIR;
     563               0 :   rv = NS_GetSpecialDirectory(dir, getter_AddRefs(tmp));
     564               0 :   NS_ENSURE_SUCCESS(rv,rv);
     565                 : 
     566               0 :   nsCOMPtr<nsILocalFile> tmpFile = do_QueryInterface(tmp);
     567               0 :   NS_ENSURE_TRUE(tmpFile != nsnull, NS_ERROR_FAILURE);
     568                 : 
     569                 :   // We put the media cache file in
     570                 :   // ${TempDir}/mozilla-media-cache/media_cache
     571               0 :   rv = tmpFile->AppendNative(nsDependentCString("mozilla-media-cache"));
     572               0 :   NS_ENSURE_SUCCESS(rv,rv);
     573                 : 
     574               0 :   rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
     575               0 :   if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
     576                 :     // Ensure the permissions are 0700. If not, we won't be able to create,
     577                 :     // read to and write from the media cache file in its subdirectory on
     578                 :     // non-Windows platforms.
     579                 :     PRUint32 perms;
     580               0 :     rv = tmpFile->GetPermissions(&perms);
     581               0 :     NS_ENSURE_SUCCESS(rv,rv);
     582               0 :     if (perms != 0700) {
     583               0 :       rv = tmpFile->SetPermissions(0700);
     584               0 :       NS_ENSURE_SUCCESS(rv,rv);
     585                 :     }
     586                 :   } else {
     587               0 :     NS_ENSURE_SUCCESS(rv,rv);
     588                 :   }
     589                 : 
     590               0 :   rv = tmpFile->AppendNative(nsDependentCString("media_cache"));
     591               0 :   NS_ENSURE_SUCCESS(rv,rv);
     592                 : 
     593               0 :   rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0700);
     594               0 :   NS_ENSURE_SUCCESS(rv,rv);
     595                 : 
     596               0 :   rv = tmpFile->OpenNSPRFileDesc(PR_RDWR | nsILocalFile::DELETE_ON_CLOSE,
     597               0 :                                  PR_IRWXU, &mFD);
     598               0 :   NS_ENSURE_SUCCESS(rv,rv);
     599                 : 
     600                 : #ifdef PR_LOGGING
     601               0 :   if (!gMediaCacheLog) {
     602               0 :     gMediaCacheLog = PR_NewLogModule("nsMediaCache");
     603                 :   }
     604                 : #endif
     605                 : 
     606               0 :   nsMediaCacheFlusher::Init();
     607                 : 
     608               0 :   return NS_OK;
     609                 : }
     610                 : 
     611                 : void
     612               0 : nsMediaCache::Flush()
     613                 : {
     614               0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     615                 : 
     616               0 :   if (!gMediaCache)
     617               0 :     return;
     618                 : 
     619               0 :   gMediaCache->FlushInternal();
     620                 : }
     621                 : 
     622                 : void
     623               0 : nsMediaCache::FlushInternal()
     624                 : {
     625               0 :   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     626                 : 
     627               0 :   for (PRUint32 blockIndex = 0; blockIndex < mIndex.Length(); ++blockIndex) {
     628               0 :     FreeBlock(blockIndex);
     629                 :   }
     630                 : 
     631                 :   // Truncate file, close it, and reopen
     632               0 :   Truncate();
     633               0 :   NS_ASSERTION(mIndex.Length() == 0, "Blocks leaked?");
     634               0 :   if (mFD) {
     635               0 :     PR_Close(mFD);
     636               0 :     mFD = nsnull;
     637                 :   }
     638               0 :   Init();
     639               0 : }
     640                 : 
     641                 : void
     642               0 : nsMediaCache::MaybeShutdown()
     643                 : {
     644               0 :   NS_ASSERTION(NS_IsMainThread(),
     645                 :                "nsMediaCache::MaybeShutdown called on non-main thread");
     646               0 :   if (!gMediaCache->mStreams.IsEmpty()) {
     647                 :     // Don't shut down yet, streams are still alive
     648               0 :     return;
     649                 :   }
     650                 : 
     651                 :   // Since we're on the main thread, no-one is going to add a new stream
     652                 :   // while we shut down.
     653                 :   // This function is static so we don't have to delete 'this'.
     654               0 :   delete gMediaCache;
     655               0 :   gMediaCache = nsnull;
     656               0 :   NS_IF_RELEASE(gMediaCacheFlusher);
     657                 : }
     658                 : 
     659                 : static void
     660               0 : InitMediaCache()
     661                 : {
     662               0 :   if (gMediaCache)
     663               0 :     return;
     664                 : 
     665               0 :   gMediaCache = new nsMediaCache();
     666               0 :   if (!gMediaCache)
     667               0 :     return;
     668                 : 
     669               0 :   nsresult rv = gMediaCache->Init();
     670               0 :   if (NS_FAILED(rv)) {
     671               0 :     delete gMediaCache;
     672               0 :     gMediaCache = nsnull;
     673                 :   }
     674                 : }
     675                 : 
     676                 : nsresult
     677               0 : nsMediaCache::ReadCacheFile(PRInt64 aOffset, void* aData, PRInt32 aLength,
     678                 :                             PRInt32* aBytes)
     679                 : {
     680               0 :   mReentrantMonitor.AssertCurrentThreadIn();
     681                 : 
     682               0 :   if (!mFD)
     683               0 :     return NS_ERROR_FAILURE;
     684                 : 
     685               0 :   if (mFDCurrentPos != aOffset) {
     686               0 :     PROffset64 offset = PR_Seek64(mFD, aOffset, PR_SEEK_SET);
     687               0 :     if (offset != aOffset)
     688               0 :       return NS_ERROR_FAILURE;
     689               0 :     mFDCurrentPos = aOffset;
     690                 :   }
     691               0 :   PRInt32 amount = PR_Read(mFD, aData, aLength);
     692               0 :   if (amount <= 0)
     693               0 :     return NS_ERROR_FAILURE;
     694               0 :   mFDCurrentPos += amount;
     695               0 :   *aBytes = amount;
     696               0 :   return NS_OK;
     697                 : }
     698                 : 
     699                 : nsresult
     700               0 : nsMediaCache::ReadCacheFileAllBytes(PRInt64 aOffset, void* aData, PRInt32 aLength)
     701                 : {
     702               0 :   mReentrantMonitor.AssertCurrentThreadIn();
     703                 : 
     704               0 :   PRInt64 offset = aOffset;
     705               0 :   PRInt32 count = aLength;
     706                 :   // Cast to char* so we can do byte-wise pointer arithmetic
     707               0 :   char* data = static_cast<char*>(aData);
     708               0 :   while (count > 0) {
     709                 :     PRInt32 bytes;
     710               0 :     nsresult rv = ReadCacheFile(offset, data, count, &bytes);
     711               0 :     if (NS_FAILED(rv))
     712               0 :       return rv;
     713               0 :     if (bytes == 0)
     714               0 :       return NS_ERROR_FAILURE;
     715               0 :     count -= bytes;
     716               0 :     data += bytes;
     717               0 :     offset += bytes;
     718                 :   }
     719               0 :   return NS_OK;
     720                 : }
     721                 : 
     722                 : nsresult
     723               0 : nsMediaCache::WriteCacheFile(PRInt64 aOffset, const void* aData, PRInt32 aLength)
     724                 : {
     725               0 :   mReentrantMonitor.AssertCurrentThreadIn();
     726                 : 
     727               0 :   if (!mFD)
     728               0 :     return NS_ERROR_FAILURE;
     729                 : 
     730               0 :   if (mFDCurrentPos != aOffset) {
     731               0 :     PROffset64 offset = PR_Seek64(mFD, aOffset, PR_SEEK_SET);
     732               0 :     if (offset != aOffset)
     733               0 :       return NS_ERROR_FAILURE;
     734               0 :     mFDCurrentPos = aOffset;
     735                 :   }
     736                 : 
     737               0 :   const char* data = static_cast<const char*>(aData);
     738               0 :   PRInt32 length = aLength;
     739               0 :   while (length > 0) {
     740               0 :     PRInt32 amount = PR_Write(mFD, data, length);
     741               0 :     if (amount <= 0)
     742               0 :       return NS_ERROR_FAILURE;
     743               0 :     mFDCurrentPos += amount;
     744               0 :     length -= amount;
     745               0 :     data += amount;
     746                 :   }
     747                 : 
     748               0 :   return NS_OK;
     749                 : }
     750                 : 
     751               0 : static PRInt32 GetMaxBlocks()
     752                 : {
     753                 :   // We look up the cache size every time. This means dynamic changes
     754                 :   // to the pref are applied.
     755                 :   // Cache size is in KB
     756               0 :   PRInt32 cacheSize = Preferences::GetInt("media.cache_size", 500*1024);
     757               0 :   PRInt64 maxBlocks = static_cast<PRInt64>(cacheSize)*1024/nsMediaCache::BLOCK_SIZE;
     758               0 :   maxBlocks = NS_MAX<PRInt64>(maxBlocks, 1);
     759               0 :   return PRInt32(NS_MIN<PRInt64>(maxBlocks, PR_INT32_MAX));
     760                 : }
     761                 : 
     762                 : PRInt32
     763               0 : nsMediaCache::FindBlockForIncomingData(TimeStamp aNow,
     764                 :                                        nsMediaCacheStream* aStream)
     765                 : {
     766               0 :   mReentrantMonitor.AssertCurrentThreadIn();
     767                 : 
     768                 :   PRInt32 blockIndex = FindReusableBlock(aNow, aStream,
     769               0 :       aStream->mChannelOffset/BLOCK_SIZE, PR_INT32_MAX);
     770                 : 
     771               0 :   if (blockIndex < 0 || !IsBlockFree(blockIndex)) {
     772                 :     // The block returned is already allocated.
     773                 :     // Don't reuse it if a) there's room to expand the cache or
     774                 :     // b) the data we're going to store in the free block is not higher
     775                 :     // priority than the data already stored in the free block.
     776                 :     // The latter can lead us to go over the cache limit a bit.
     777               0 :     if ((mIndex.Length() < PRUint32(GetMaxBlocks()) || blockIndex < 0 ||
     778               0 :          PredictNextUseForIncomingData(aStream) >= PredictNextUse(aNow, blockIndex))) {
     779               0 :       blockIndex = mIndex.Length();
     780               0 :       if (!mIndex.AppendElement())
     781               0 :         return -1;
     782               0 :       mFreeBlocks.AddFirstBlock(blockIndex);
     783               0 :       return blockIndex;
     784                 :     }
     785                 :   }
     786                 : 
     787               0 :   return blockIndex;
     788                 : }
     789                 : 
     790                 : bool
     791               0 : nsMediaCache::BlockIsReusable(PRInt32 aBlockIndex)
     792                 : {
     793               0 :   Block* block = &mIndex[aBlockIndex];
     794               0 :   for (PRUint32 i = 0; i < block->mOwners.Length(); ++i) {
     795               0 :     nsMediaCacheStream* stream = block->mOwners[i].mStream;
     796               0 :     if (stream->mPinCount > 0 ||
     797               0 :         stream->mStreamOffset/BLOCK_SIZE == block->mOwners[i].mStreamBlock) {
     798               0 :       return false;
     799                 :     }
     800                 :   }
     801               0 :   return true;
     802                 : }
     803                 : 
     804                 : void
     805               0 : nsMediaCache::AppendMostReusableBlock(BlockList* aBlockList,
     806                 :                                       nsTArray<PRUint32>* aResult,
     807                 :                                       PRInt32 aBlockIndexLimit)
     808                 : {
     809               0 :   mReentrantMonitor.AssertCurrentThreadIn();
     810                 : 
     811               0 :   PRInt32 blockIndex = aBlockList->GetLastBlock();
     812               0 :   if (blockIndex < 0)
     813               0 :     return;
     814               0 :   do {
     815                 :     // Don't consider blocks for pinned streams, or blocks that are
     816                 :     // beyond the specified limit, or a block that contains a stream's
     817                 :     // current read position (such a block contains both played data
     818                 :     // and readahead data)
     819               0 :     if (blockIndex < aBlockIndexLimit && BlockIsReusable(blockIndex)) {
     820               0 :       aResult->AppendElement(blockIndex);
     821               0 :       return;
     822                 :     }
     823               0 :     blockIndex = aBlockList->GetPrevBlock(blockIndex);
     824                 :   } while (blockIndex >= 0);
     825                 : }
     826                 : 
     827                 : PRInt32
     828               0 : nsMediaCache::FindReusableBlock(TimeStamp aNow,
     829                 :                                 nsMediaCacheStream* aForStream,
     830                 :                                 PRInt32 aForStreamBlock,
     831                 :                                 PRInt32 aMaxSearchBlockIndex)
     832                 : {
     833               0 :   mReentrantMonitor.AssertCurrentThreadIn();
     834                 : 
     835               0 :   PRUint32 length = NS_MIN(PRUint32(aMaxSearchBlockIndex), mIndex.Length());
     836                 : 
     837               0 :   if (aForStream && aForStreamBlock > 0 &&
     838               0 :       PRUint32(aForStreamBlock) <= aForStream->mBlocks.Length()) {
     839               0 :     PRInt32 prevCacheBlock = aForStream->mBlocks[aForStreamBlock - 1];
     840               0 :     if (prevCacheBlock >= 0) {
     841                 :       PRUint32 freeBlockScanEnd =
     842               0 :         NS_MIN(length, prevCacheBlock + FREE_BLOCK_SCAN_LIMIT);
     843               0 :       for (PRUint32 i = prevCacheBlock; i < freeBlockScanEnd; ++i) {
     844               0 :         if (IsBlockFree(i))
     845               0 :           return i;
     846                 :       }
     847                 :     }
     848                 :   }
     849                 : 
     850               0 :   if (!mFreeBlocks.IsEmpty()) {
     851               0 :     PRInt32 blockIndex = mFreeBlocks.GetFirstBlock();
     852               0 :     do {
     853               0 :       if (blockIndex < aMaxSearchBlockIndex)
     854               0 :         return blockIndex;
     855               0 :       blockIndex = mFreeBlocks.GetNextBlock(blockIndex);
     856                 :     } while (blockIndex >= 0);
     857                 :   }
     858                 : 
     859                 :   // Build a list of the blocks we should consider for the "latest
     860                 :   // predicted time of next use". We can exploit the fact that the block
     861                 :   // linked lists are ordered by increasing time of next use. This is
     862                 :   // actually the whole point of having the linked lists.
     863               0 :   nsAutoTArray<PRUint32,8> candidates;
     864               0 :   for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
     865               0 :     nsMediaCacheStream* stream = mStreams[i];
     866               0 :     if (stream->mPinCount > 0) {
     867                 :       // No point in even looking at this stream's blocks
     868               0 :       continue;
     869                 :     }
     870                 : 
     871               0 :     AppendMostReusableBlock(&stream->mMetadataBlocks, &candidates, length);
     872               0 :     AppendMostReusableBlock(&stream->mPlayedBlocks, &candidates, length);
     873                 : 
     874                 :     // Don't consider readahead blocks in non-seekable streams. If we
     875                 :     // remove the block we won't be able to seek back to read it later.
     876               0 :     if (stream->mIsSeekable) {
     877               0 :       AppendMostReusableBlock(&stream->mReadaheadBlocks, &candidates, length);
     878                 :     }
     879                 :   }
     880                 : 
     881               0 :   TimeDuration latestUse;
     882               0 :   PRInt32 latestUseBlock = -1;
     883               0 :   for (PRUint32 i = 0; i < candidates.Length(); ++i) {
     884               0 :     TimeDuration nextUse = PredictNextUse(aNow, candidates[i]);
     885               0 :     if (nextUse > latestUse) {
     886               0 :       latestUse = nextUse;
     887               0 :       latestUseBlock = candidates[i];
     888                 :     }
     889                 :   }
     890                 : 
     891               0 :   return latestUseBlock;
     892                 : }
     893                 : 
     894                 : nsMediaCache::BlockList*
     895               0 : nsMediaCache::GetListForBlock(BlockOwner* aBlock)
     896                 : {
     897               0 :   switch (aBlock->mClass) {
     898                 :   case METADATA_BLOCK:
     899               0 :     NS_ASSERTION(aBlock->mStream, "Metadata block has no stream?");
     900               0 :     return &aBlock->mStream->mMetadataBlocks;
     901                 :   case PLAYED_BLOCK:
     902               0 :     NS_ASSERTION(aBlock->mStream, "Metadata block has no stream?");
     903               0 :     return &aBlock->mStream->mPlayedBlocks;
     904                 :   case READAHEAD_BLOCK:
     905               0 :     NS_ASSERTION(aBlock->mStream, "Readahead block has no stream?");
     906               0 :     return &aBlock->mStream->mReadaheadBlocks;
     907                 :   default:
     908               0 :     NS_ERROR("Invalid block class");
     909               0 :     return nsnull;
     910                 :   }
     911                 : }
     912                 : 
     913                 : nsMediaCache::BlockOwner*
     914               0 : nsMediaCache::GetBlockOwner(PRInt32 aBlockIndex, nsMediaCacheStream* aStream)
     915                 : {
     916               0 :   Block* block = &mIndex[aBlockIndex];
     917               0 :   for (PRUint32 i = 0; i < block->mOwners.Length(); ++i) {
     918               0 :     if (block->mOwners[i].mStream == aStream)
     919               0 :       return &block->mOwners[i];
     920                 :   }
     921               0 :   return nsnull;
     922                 : }
     923                 : 
     924                 : void
     925               0 : nsMediaCache::SwapBlocks(PRInt32 aBlockIndex1, PRInt32 aBlockIndex2)
     926                 : {
     927               0 :   mReentrantMonitor.AssertCurrentThreadIn();
     928                 : 
     929               0 :   Block* block1 = &mIndex[aBlockIndex1];
     930               0 :   Block* block2 = &mIndex[aBlockIndex2];
     931                 : 
     932               0 :   block1->mOwners.SwapElements(block2->mOwners);
     933                 : 
     934                 :   // Now all references to block1 have to be replaced with block2 and
     935                 :   // vice versa.
     936                 :   // First update stream references to blocks via mBlocks.
     937               0 :   const Block* blocks[] = { block1, block2 };
     938               0 :   PRInt32 blockIndices[] = { aBlockIndex1, aBlockIndex2 };
     939               0 :   for (PRInt32 i = 0; i < 2; ++i) {
     940               0 :     for (PRUint32 j = 0; j < blocks[i]->mOwners.Length(); ++j) {
     941               0 :       const BlockOwner* b = &blocks[i]->mOwners[j];
     942               0 :       b->mStream->mBlocks[b->mStreamBlock] = blockIndices[i];
     943                 :     }
     944                 :   }
     945                 : 
     946                 :   // Now update references to blocks in block lists.
     947               0 :   mFreeBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
     948                 : 
     949               0 :   nsTHashtable<nsPtrHashKey<nsMediaCacheStream> > visitedStreams;
     950               0 :   visitedStreams.Init();
     951                 : 
     952               0 :   for (PRInt32 i = 0; i < 2; ++i) {
     953               0 :     for (PRUint32 j = 0; j < blocks[i]->mOwners.Length(); ++j) {
     954               0 :       nsMediaCacheStream* stream = blocks[i]->mOwners[j].mStream;
     955                 :       // Make sure that we don't update the same stream twice --- that
     956                 :       // would result in swapping the block references back again!
     957               0 :       if (visitedStreams.GetEntry(stream))
     958               0 :         continue;
     959               0 :       visitedStreams.PutEntry(stream);
     960               0 :       stream->mReadaheadBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
     961               0 :       stream->mPlayedBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
     962               0 :       stream->mMetadataBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
     963                 :     }
     964                 :   }
     965                 : 
     966               0 :   Verify();
     967               0 : }
     968                 : 
     969                 : void
     970               0 : nsMediaCache::RemoveBlockOwner(PRInt32 aBlockIndex, nsMediaCacheStream* aStream)
     971                 : {
     972               0 :   Block* block = &mIndex[aBlockIndex];
     973               0 :   for (PRUint32 i = 0; i < block->mOwners.Length(); ++i) {
     974               0 :     BlockOwner* bo = &block->mOwners[i];
     975               0 :     if (bo->mStream == aStream) {
     976               0 :       GetListForBlock(bo)->RemoveBlock(aBlockIndex);
     977               0 :       bo->mStream->mBlocks[bo->mStreamBlock] = -1;
     978               0 :       block->mOwners.RemoveElementAt(i);
     979               0 :       if (block->mOwners.IsEmpty()) {
     980               0 :         mFreeBlocks.AddFirstBlock(aBlockIndex);
     981                 :       }
     982               0 :       return;
     983                 :     }
     984                 :   }
     985                 : }
     986                 : 
     987                 : void
     988               0 : nsMediaCache::AddBlockOwnerAsReadahead(PRInt32 aBlockIndex,
     989                 :                                        nsMediaCacheStream* aStream,
     990                 :                                        PRInt32 aStreamBlockIndex)
     991                 : {
     992               0 :   Block* block = &mIndex[aBlockIndex];
     993               0 :   if (block->mOwners.IsEmpty()) {
     994               0 :     mFreeBlocks.RemoveBlock(aBlockIndex);
     995                 :   }
     996               0 :   BlockOwner* bo = block->mOwners.AppendElement();
     997               0 :   bo->mStream = aStream;
     998               0 :   bo->mStreamBlock = aStreamBlockIndex;
     999               0 :   aStream->mBlocks[aStreamBlockIndex] = aBlockIndex;
    1000               0 :   bo->mClass = READAHEAD_BLOCK;
    1001               0 :   InsertReadaheadBlock(bo, aBlockIndex);
    1002               0 : }
    1003                 : 
    1004                 : void
    1005               0 : nsMediaCache::FreeBlock(PRInt32 aBlock)
    1006                 : {
    1007               0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1008                 : 
    1009               0 :   Block* block = &mIndex[aBlock];
    1010               0 :   if (block->mOwners.IsEmpty()) {
    1011                 :     // already free
    1012               0 :     return;
    1013                 :   }
    1014                 : 
    1015               0 :   LOG(PR_LOG_DEBUG, ("Released block %d", aBlock));
    1016                 : 
    1017               0 :   for (PRUint32 i = 0; i < block->mOwners.Length(); ++i) {
    1018               0 :     BlockOwner* bo = &block->mOwners[i];
    1019               0 :     GetListForBlock(bo)->RemoveBlock(aBlock);
    1020               0 :     bo->mStream->mBlocks[bo->mStreamBlock] = -1;
    1021                 :   }
    1022               0 :   block->mOwners.Clear();
    1023               0 :   mFreeBlocks.AddFirstBlock(aBlock);
    1024               0 :   Verify();
    1025                 : }
    1026                 : 
    1027                 : TimeDuration
    1028               0 : nsMediaCache::PredictNextUse(TimeStamp aNow, PRInt32 aBlock)
    1029                 : {
    1030               0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1031               0 :   NS_ASSERTION(!IsBlockFree(aBlock), "aBlock is free");
    1032                 : 
    1033               0 :   Block* block = &mIndex[aBlock];
    1034                 :   // Blocks can be belong to multiple streams. The predicted next use
    1035                 :   // time is the earliest time predicted by any of the streams.
    1036               0 :   TimeDuration result;
    1037               0 :   for (PRUint32 i = 0; i < block->mOwners.Length(); ++i) {
    1038               0 :     BlockOwner* bo = &block->mOwners[i];
    1039               0 :     TimeDuration prediction;
    1040               0 :     switch (bo->mClass) {
    1041                 :     case METADATA_BLOCK:
    1042                 :       // This block should be managed in LRU mode. For metadata we predict
    1043                 :       // that the time until the next use is the time since the last use.
    1044               0 :       prediction = aNow - bo->mLastUseTime;
    1045               0 :       break;
    1046                 :     case PLAYED_BLOCK:
    1047                 :       // This block should be managed in LRU mode, and we should impose
    1048                 :       // a "replay delay" to reflect the likelihood of replay happening
    1049               0 :       NS_ASSERTION(static_cast<PRInt64>(bo->mStreamBlock)*BLOCK_SIZE <
    1050                 :                    bo->mStream->mStreamOffset,
    1051                 :                    "Played block after the current stream position?");
    1052               0 :       prediction = aNow - bo->mLastUseTime +
    1053               0 :         TimeDuration::FromSeconds(REPLAY_DELAY);
    1054               0 :       break;
    1055                 :     case READAHEAD_BLOCK: {
    1056                 :       PRInt64 bytesAhead =
    1057               0 :         static_cast<PRInt64>(bo->mStreamBlock)*BLOCK_SIZE - bo->mStream->mStreamOffset;
    1058               0 :       NS_ASSERTION(bytesAhead >= 0,
    1059                 :                    "Readahead block before the current stream position?");
    1060                 :       PRInt64 millisecondsAhead =
    1061               0 :         bytesAhead*1000/bo->mStream->mPlaybackBytesPerSecond;
    1062                 :       prediction = TimeDuration::FromMilliseconds(
    1063               0 :           NS_MIN<PRInt64>(millisecondsAhead, PR_INT32_MAX));
    1064               0 :       break;
    1065                 :     }
    1066                 :     default:
    1067               0 :       NS_ERROR("Invalid class for predicting next use");
    1068               0 :       return TimeDuration(0);
    1069                 :     }
    1070               0 :     if (i == 0 || prediction < result) {
    1071               0 :       result = prediction;
    1072                 :     }
    1073                 :   }
    1074               0 :   return result;
    1075                 : }
    1076                 : 
    1077                 : TimeDuration
    1078               0 : nsMediaCache::PredictNextUseForIncomingData(nsMediaCacheStream* aStream)
    1079                 : {
    1080               0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1081                 : 
    1082               0 :   PRInt64 bytesAhead = aStream->mChannelOffset - aStream->mStreamOffset;
    1083               0 :   if (bytesAhead <= -BLOCK_SIZE) {
    1084                 :     // Hmm, no idea when data behind us will be used. Guess 24 hours.
    1085               0 :     return TimeDuration::FromSeconds(24*60*60);
    1086                 :   }
    1087               0 :   if (bytesAhead <= 0)
    1088               0 :     return TimeDuration(0);
    1089               0 :   PRInt64 millisecondsAhead = bytesAhead*1000/aStream->mPlaybackBytesPerSecond;
    1090                 :   return TimeDuration::FromMilliseconds(
    1091               0 :       NS_MIN<PRInt64>(millisecondsAhead, PR_INT32_MAX));
    1092                 : }
    1093                 : 
    1094                 : enum StreamAction { NONE, SEEK, SEEK_AND_RESUME, RESUME, SUSPEND };
    1095                 : 
    1096                 : void
    1097               0 : nsMediaCache::Update()
    1098                 : {
    1099               0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1100                 : 
    1101                 :   // The action to use for each stream. We store these so we can make
    1102                 :   // decisions while holding the cache lock but implement those decisions
    1103                 :   // without holding the cache lock, since we need to call out to
    1104                 :   // stream, decoder and element code.
    1105               0 :   nsAutoTArray<StreamAction,10> actions;
    1106                 : 
    1107                 :   {
    1108               0 :     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
    1109               0 :     mUpdateQueued = false;
    1110                 : #ifdef DEBUG
    1111               0 :     mInUpdate = true;
    1112                 : #endif
    1113                 : 
    1114               0 :     PRInt32 maxBlocks = GetMaxBlocks();
    1115               0 :     TimeStamp now = TimeStamp::Now();
    1116                 : 
    1117               0 :     PRInt32 freeBlockCount = mFreeBlocks.GetCount();
    1118                 :     // Try to trim back the cache to its desired maximum size. The cache may
    1119                 :     // have overflowed simply due to data being received when we have
    1120                 :     // no blocks in the main part of the cache that are free or lower
    1121                 :     // priority than the new data. The cache can also be overflowing because
    1122                 :     // the media.cache_size preference was reduced.
    1123                 :     // First, figure out what the least valuable block in the cache overflow
    1124                 :     // is. We don't want to replace any blocks in the main part of the
    1125                 :     // cache whose expected time of next use is earlier or equal to that.
    1126                 :     // If we allow that, we can effectively end up discarding overflowing
    1127                 :     // blocks (by moving an overflowing block to the main part of the cache,
    1128                 :     // and then overwriting it with another overflowing block), and we try
    1129                 :     // to avoid that since it requires HTTP seeks.
    1130                 :     // We also use this loop to eliminate overflowing blocks from
    1131                 :     // freeBlockCount.
    1132               0 :     TimeDuration latestPredictedUseForOverflow = 0;
    1133               0 :     for (PRInt32 blockIndex = mIndex.Length() - 1; blockIndex >= maxBlocks;
    1134                 :          --blockIndex) {
    1135               0 :       if (IsBlockFree(blockIndex)) {
    1136                 :         // Don't count overflowing free blocks in our free block count
    1137               0 :         --freeBlockCount;
    1138               0 :         continue;
    1139                 :       }
    1140               0 :       TimeDuration predictedUse = PredictNextUse(now, blockIndex);
    1141               0 :       latestPredictedUseForOverflow = NS_MAX(latestPredictedUseForOverflow, predictedUse);
    1142                 :     }
    1143                 : 
    1144                 :     // Now try to move overflowing blocks to the main part of the cache.
    1145               0 :     for (PRInt32 blockIndex = mIndex.Length() - 1; blockIndex >= maxBlocks;
    1146                 :          --blockIndex) {
    1147               0 :       if (IsBlockFree(blockIndex))
    1148               0 :         continue;
    1149                 : 
    1150               0 :       Block* block = &mIndex[blockIndex];
    1151                 :       // Try to relocate the block close to other blocks for the first stream.
    1152                 :       // There is no point in trying to make it close to other blocks in
    1153                 :       // *all* the streams it might belong to.
    1154                 :       PRInt32 destinationBlockIndex =
    1155               0 :         FindReusableBlock(now, block->mOwners[0].mStream,
    1156               0 :                           block->mOwners[0].mStreamBlock, maxBlocks);
    1157               0 :       if (destinationBlockIndex < 0) {
    1158                 :         // Nowhere to place this overflow block. We won't be able to
    1159                 :         // place any more overflow blocks.
    1160               0 :         break;
    1161                 :       }
    1162                 : 
    1163               0 :       if (IsBlockFree(destinationBlockIndex) ||
    1164               0 :           PredictNextUse(now, destinationBlockIndex) > latestPredictedUseForOverflow) {
    1165                 :         // Reuse blocks in the main part of the cache that are less useful than
    1166                 :         // the least useful overflow blocks
    1167                 :         char buf[BLOCK_SIZE];
    1168               0 :         nsresult rv = ReadCacheFileAllBytes(blockIndex*BLOCK_SIZE, buf, sizeof(buf));
    1169               0 :         if (NS_SUCCEEDED(rv)) {
    1170               0 :           rv = WriteCacheFile(destinationBlockIndex*BLOCK_SIZE, buf, BLOCK_SIZE);
    1171               0 :           if (NS_SUCCEEDED(rv)) {
    1172                 :             // We successfully copied the file data.
    1173               0 :             LOG(PR_LOG_DEBUG, ("Swapping blocks %d and %d (trimming cache)",
    1174                 :                 blockIndex, destinationBlockIndex));
    1175                 :             // Swapping the block metadata here lets us maintain the
    1176                 :             // correct positions in the linked lists
    1177               0 :             SwapBlocks(blockIndex, destinationBlockIndex);
    1178                 :           } else {
    1179                 :             // If the write fails we may have corrupted the destination
    1180                 :             // block. Free it now.
    1181               0 :             LOG(PR_LOG_DEBUG, ("Released block %d (trimming cache)",
    1182                 :                 destinationBlockIndex));
    1183               0 :             FreeBlock(destinationBlockIndex);
    1184                 :           }
    1185                 :           // Free the overflowing block even if the copy failed.
    1186               0 :           LOG(PR_LOG_DEBUG, ("Released block %d (trimming cache)",
    1187                 :               blockIndex));
    1188               0 :           FreeBlock(blockIndex);
    1189                 :         }
    1190                 :       } else {
    1191               0 :         LOG(PR_LOG_DEBUG, ("Could not trim cache block %d (destination %d, predicted next use %f, latest predicted use for overflow %f",
    1192                 :                            blockIndex, destinationBlockIndex,
    1193                 :                            PredictNextUse(now, destinationBlockIndex).ToSeconds(),
    1194                 :                            latestPredictedUseForOverflow.ToSeconds()));
    1195                 :       }
    1196                 :     }
    1197                 :     // Try chopping back the array of cache entries and the cache file.
    1198               0 :     Truncate();
    1199                 : 
    1200                 :     // Count the blocks allocated for readahead of non-seekable streams
    1201                 :     // (these blocks can't be freed but we don't want them to monopolize the
    1202                 :     // cache)
    1203               0 :     PRInt32 nonSeekableReadaheadBlockCount = 0;
    1204               0 :     for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
    1205               0 :       nsMediaCacheStream* stream = mStreams[i];
    1206               0 :       if (!stream->mIsSeekable) {
    1207               0 :         nonSeekableReadaheadBlockCount += stream->mReadaheadBlocks.GetCount();
    1208                 :       }
    1209                 :     }
    1210                 : 
    1211                 :     // If freeBlockCount is zero, then compute the latest of
    1212                 :     // the predicted next-uses for all blocks
    1213               0 :     TimeDuration latestNextUse;
    1214               0 :     if (freeBlockCount == 0) {
    1215               0 :       PRInt32 reusableBlock = FindReusableBlock(now, nsnull, 0, maxBlocks);
    1216               0 :       if (reusableBlock >= 0) {
    1217               0 :         latestNextUse = PredictNextUse(now, reusableBlock);
    1218                 :       }
    1219                 :     }
    1220                 : 
    1221               0 :     for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
    1222               0 :       actions.AppendElement(NONE);
    1223                 : 
    1224               0 :       nsMediaCacheStream* stream = mStreams[i];
    1225               0 :       if (stream->mClosed)
    1226               0 :         continue;
    1227                 : 
    1228                 :       // Figure out where we should be reading from. It's the first
    1229                 :       // uncached byte after the current mStreamOffset.
    1230               0 :       PRInt64 dataOffset = stream->GetCachedDataEndInternal(stream->mStreamOffset);
    1231                 : 
    1232                 :       // Compute where we'd actually seek to to read at readOffset
    1233               0 :       PRInt64 desiredOffset = dataOffset;
    1234               0 :       if (stream->mIsSeekable) {
    1235               0 :         if (desiredOffset > stream->mChannelOffset &&
    1236                 :             desiredOffset <= stream->mChannelOffset + SEEK_VS_READ_THRESHOLD) {
    1237                 :           // Assume it's more efficient to just keep reading up to the
    1238                 :           // desired position instead of trying to seek
    1239               0 :           desiredOffset = stream->mChannelOffset;
    1240                 :         }
    1241                 :       } else {
    1242                 :         // We can't seek directly to the desired offset...
    1243               0 :         if (stream->mChannelOffset > desiredOffset) {
    1244                 :           // Reading forward won't get us anywhere, we need to go backwards.
    1245                 :           // Seek back to 0 (the client will reopen the stream) and then
    1246                 :           // read forward.
    1247               0 :           NS_WARNING("Can't seek backwards, so seeking to 0");
    1248               0 :           desiredOffset = 0;
    1249                 :           // Flush cached blocks out, since if this is a live stream
    1250                 :           // the cached data may be completely different next time we
    1251                 :           // read it. We have to assume that live streams don't
    1252                 :           // advertise themselves as being seekable...
    1253               0 :           ReleaseStreamBlocks(stream);
    1254                 :         } else {
    1255                 :           // otherwise reading forward is looking good, so just stay where we
    1256                 :           // are and don't trigger a channel seek!
    1257               0 :           desiredOffset = stream->mChannelOffset;
    1258                 :         }
    1259                 :       }
    1260                 : 
    1261                 :       // Figure out if we should be reading data now or not. It's amazing
    1262                 :       // how complex this is, but each decision is simple enough.
    1263                 :       bool enableReading;
    1264               0 :       if (stream->mStreamLength >= 0 && dataOffset >= stream->mStreamLength) {
    1265                 :         // We want data at the end of the stream, where there's nothing to
    1266                 :         // read. We don't want to try to read if we're suspended, because that
    1267                 :         // might create a new channel and seek unnecessarily (and incorrectly,
    1268                 :         // since HTTP doesn't allow seeking to the actual EOF), and we don't want
    1269                 :         // to suspend if we're not suspended and already reading at the end of
    1270                 :         // the stream, since there just might be more data than the server
    1271                 :         // advertised with Content-Length, and we may as well keep reading.
    1272                 :         // But we don't want to seek to the end of the stream if we're not
    1273                 :         // already there.
    1274               0 :         LOG(PR_LOG_DEBUG, ("Stream %p at end of stream", stream));
    1275               0 :         enableReading = !stream->mCacheSuspended &&
    1276               0 :           stream->mStreamLength == stream->mChannelOffset;
    1277               0 :       } else if (desiredOffset < stream->mStreamOffset) {
    1278                 :         // We're reading to try to catch up to where the current stream
    1279                 :         // reader wants to be. Better not stop.
    1280               0 :         LOG(PR_LOG_DEBUG, ("Stream %p catching up", stream));
    1281               0 :         enableReading = true;
    1282               0 :       } else if (desiredOffset < stream->mStreamOffset + BLOCK_SIZE) {
    1283                 :         // The stream reader is waiting for us, or nearly so. Better feed it.
    1284               0 :         LOG(PR_LOG_DEBUG, ("Stream %p feeding reader", stream));
    1285               0 :         enableReading = true;
    1286               0 :       } else if (!stream->mIsSeekable &&
    1287                 :                  nonSeekableReadaheadBlockCount >= maxBlocks*NONSEEKABLE_READAHEAD_MAX) {
    1288                 :         // This stream is not seekable and there are already too many blocks
    1289                 :         // being cached for readahead for nonseekable streams (which we can't
    1290                 :         // free). So stop reading ahead now.
    1291               0 :         LOG(PR_LOG_DEBUG, ("Stream %p throttling non-seekable readahead", stream));
    1292               0 :         enableReading = false;
    1293               0 :       } else if (mIndex.Length() > PRUint32(maxBlocks)) {
    1294                 :         // We're in the process of bringing the cache size back to the
    1295                 :         // desired limit, so don't bring in more data yet
    1296               0 :         LOG(PR_LOG_DEBUG, ("Stream %p throttling to reduce cache size", stream));
    1297               0 :         enableReading = false;
    1298               0 :       } else if (freeBlockCount > 0 || mIndex.Length() < PRUint32(maxBlocks)) {
    1299                 :         // Free blocks in the cache, so keep reading
    1300               0 :         LOG(PR_LOG_DEBUG, ("Stream %p reading since there are free blocks", stream));
    1301               0 :         enableReading = true;
    1302               0 :       } else if (latestNextUse <= TimeDuration(0)) {
    1303                 :         // No reusable blocks, so can't read anything
    1304               0 :         LOG(PR_LOG_DEBUG, ("Stream %p throttling due to no reusable blocks", stream));
    1305               0 :         enableReading = false;
    1306                 :       } else {
    1307                 :         // Read ahead if the data we expect to read is more valuable than
    1308                 :         // the least valuable block in the main part of the cache
    1309               0 :         TimeDuration predictedNewDataUse = PredictNextUseForIncomingData(stream);
    1310               0 :         LOG(PR_LOG_DEBUG, ("Stream %p predict next data in %f, current worst block is %f",
    1311                 :             stream, predictedNewDataUse.ToSeconds(), latestNextUse.ToSeconds()));
    1312               0 :         enableReading = predictedNewDataUse < latestNextUse;
    1313                 :       }
    1314                 : 
    1315               0 :       if (enableReading) {
    1316               0 :         for (PRUint32 j = 0; j < i; ++j) {
    1317               0 :           nsMediaCacheStream* other = mStreams[j];
    1318               0 :           if (other->mResourceID == stream->mResourceID &&
    1319               0 :               !other->mClient->IsSuspended() &&
    1320                 :               other->mChannelOffset/BLOCK_SIZE == desiredOffset/BLOCK_SIZE) {
    1321                 :             // This block is already going to be read by the other stream.
    1322                 :             // So don't try to read it from this stream as well.
    1323               0 :             enableReading = false;
    1324               0 :             LOG(PR_LOG_DEBUG, ("Stream %p waiting on same block (%lld) from stream %p",
    1325                 :                                stream, desiredOffset/BLOCK_SIZE, other));
    1326               0 :             break;
    1327                 :           }
    1328                 :         }
    1329                 :       }
    1330                 : 
    1331               0 :       if (stream->mChannelOffset != desiredOffset && enableReading) {
    1332                 :         // We need to seek now.
    1333               0 :         NS_ASSERTION(stream->mIsSeekable || desiredOffset == 0,
    1334                 :                      "Trying to seek in a non-seekable stream!");
    1335                 :         // Round seek offset down to the start of the block. This is essential
    1336                 :         // because we don't want to think we have part of a block already
    1337                 :         // in mPartialBlockBuffer.
    1338               0 :         stream->mChannelOffset = (desiredOffset/BLOCK_SIZE)*BLOCK_SIZE;
    1339               0 :         actions[i] = stream->mCacheSuspended ? SEEK_AND_RESUME : SEEK;
    1340               0 :       } else if (enableReading && stream->mCacheSuspended) {
    1341               0 :         actions[i] = RESUME;
    1342               0 :       } else if (!enableReading && !stream->mCacheSuspended) {
    1343               0 :         actions[i] = SUSPEND;
    1344                 :       }
    1345                 :     }
    1346                 : #ifdef DEBUG
    1347               0 :     mInUpdate = false;
    1348                 : #endif
    1349                 :   }
    1350                 : 
    1351                 :   // Update the channel state without holding our cache lock. While we're
    1352                 :   // doing this, decoder threads may be running and seeking, reading or changing
    1353                 :   // other cache state. That's OK, they'll trigger new Update events and we'll
    1354                 :   // get back here and revise our decisions. The important thing here is that
    1355                 :   // performing these actions only depends on mChannelOffset and
    1356                 :   // the action, which can only be written by the main thread (i.e., this
    1357                 :   // thread), so we don't have races here.
    1358                 : 
    1359                 :   // First, update the mCacheSuspended/mCacheEnded flags so that they're all correct
    1360                 :   // when we fire our CacheClient commands below. Those commands can rely on these flags
    1361                 :   // being set correctly for all streams.
    1362               0 :   for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
    1363               0 :     nsMediaCacheStream* stream = mStreams[i];
    1364               0 :     switch (actions[i]) {
    1365                 :     case SEEK:
    1366                 :         case SEEK_AND_RESUME:
    1367               0 :       stream->mCacheSuspended = false;
    1368               0 :       stream->mChannelEnded = false;
    1369               0 :       break;
    1370                 :     case RESUME:
    1371               0 :       stream->mCacheSuspended = false;
    1372               0 :       break;
    1373                 :     case SUSPEND:
    1374               0 :       stream->mCacheSuspended = true;
    1375               0 :       break;
    1376                 :     default:
    1377               0 :       break;
    1378                 :     }
    1379               0 :     stream->mHasHadUpdate = true;
    1380                 :   }
    1381                 : 
    1382               0 :   for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
    1383               0 :     nsMediaCacheStream* stream = mStreams[i];
    1384                 :     nsresult rv;
    1385               0 :     switch (actions[i]) {
    1386                 :     case SEEK:
    1387                 :         case SEEK_AND_RESUME:
    1388               0 :       LOG(PR_LOG_DEBUG, ("Stream %p CacheSeek to %lld (resume=%d)", stream,
    1389                 :            (long long)stream->mChannelOffset, actions[i] == SEEK_AND_RESUME));
    1390                 :       rv = stream->mClient->CacheClientSeek(stream->mChannelOffset,
    1391               0 :                                             actions[i] == SEEK_AND_RESUME);
    1392               0 :       break;
    1393                 :     case RESUME:
    1394               0 :       LOG(PR_LOG_DEBUG, ("Stream %p Resumed", stream));
    1395               0 :       rv = stream->mClient->CacheClientResume();
    1396               0 :       break;
    1397                 :     case SUSPEND:
    1398               0 :       LOG(PR_LOG_DEBUG, ("Stream %p Suspended", stream));
    1399               0 :       rv = stream->mClient->CacheClientSuspend();
    1400               0 :       break;
    1401                 :     default:
    1402               0 :       rv = NS_OK;
    1403               0 :       break;
    1404                 :     }
    1405                 : 
    1406               0 :     if (NS_FAILED(rv)) {
    1407                 :       // Close the streams that failed due to error. This will cause all
    1408                 :       // client Read and Seek operations on those streams to fail. Blocked
    1409                 :       // Reads will also be woken up.
    1410               0 :       ReentrantMonitorAutoEnter mon(mReentrantMonitor);
    1411               0 :       stream->CloseInternal(mon);
    1412                 :     }
    1413                 :   }
    1414               0 : }
    1415                 : 
    1416                 : class UpdateEvent : public nsRunnable
    1417               0 : {
    1418                 : public:
    1419               0 :   NS_IMETHOD Run()
    1420                 :   {
    1421               0 :     if (gMediaCache) {
    1422               0 :       gMediaCache->Update();
    1423                 :     }
    1424               0 :     return NS_OK;
    1425                 :   }
    1426                 : };
    1427                 : 
    1428                 : void
    1429               0 : nsMediaCache::QueueUpdate()
    1430                 : {
    1431               0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1432                 : 
    1433                 :   // Queuing an update while we're in an update raises a high risk of
    1434                 :   // triggering endless events
    1435               0 :   NS_ASSERTION(!mInUpdate,
    1436                 :                "Queuing an update while we're in an update");
    1437               0 :   if (mUpdateQueued)
    1438               0 :     return;
    1439               0 :   mUpdateQueued = true;
    1440               0 :   nsCOMPtr<nsIRunnable> event = new UpdateEvent();
    1441               0 :   NS_DispatchToMainThread(event);
    1442                 : }
    1443                 : 
    1444                 : #ifdef DEBUG_VERIFY_CACHE
    1445                 : void
    1446                 : nsMediaCache::Verify()
    1447                 : {
    1448                 :   mReentrantMonitor.AssertCurrentThreadIn();
    1449                 : 
    1450                 :   mFreeBlocks.Verify();
    1451                 :   for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
    1452                 :     nsMediaCacheStream* stream = mStreams[i];
    1453                 :     stream->mReadaheadBlocks.Verify();
    1454                 :     stream->mPlayedBlocks.Verify();
    1455                 :     stream->mMetadataBlocks.Verify();
    1456                 : 
    1457                 :     // Verify that the readahead blocks are listed in stream block order
    1458                 :     PRInt32 block = stream->mReadaheadBlocks.GetFirstBlock();
    1459                 :     PRInt32 lastStreamBlock = -1;
    1460                 :     while (block >= 0) {
    1461                 :       PRUint32 j = 0;
    1462                 :       while (mIndex[block].mOwners[j].mStream != stream) {
    1463                 :         ++j;
    1464                 :       }
    1465                 :       PRInt32 nextStreamBlock =
    1466                 :         PRInt32(mIndex[block].mOwners[j].mStreamBlock);
    1467                 :       NS_ASSERTION(lastStreamBlock < nextStreamBlock,
    1468                 :                    "Blocks not increasing in readahead stream");
    1469                 :       lastStreamBlock = nextStreamBlock;
    1470                 :       block = stream->mReadaheadBlocks.GetNextBlock(block);
    1471                 :     }
    1472                 :   }
    1473                 : }
    1474                 : #endif
    1475                 : 
    1476                 : void
    1477               0 : nsMediaCache::InsertReadaheadBlock(BlockOwner* aBlockOwner,
    1478                 :                                    PRInt32 aBlockIndex)
    1479                 : {
    1480               0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1481                 : 
    1482                 :   // Find the last block whose stream block is before aBlockIndex's
    1483                 :   // stream block, and insert after it
    1484               0 :   nsMediaCacheStream* stream = aBlockOwner->mStream;
    1485               0 :   PRInt32 readaheadIndex = stream->mReadaheadBlocks.GetLastBlock();
    1486               0 :   while (readaheadIndex >= 0) {
    1487               0 :     BlockOwner* bo = GetBlockOwner(readaheadIndex, stream);
    1488               0 :     NS_ASSERTION(bo, "stream must own its blocks");
    1489               0 :     if (bo->mStreamBlock < aBlockOwner->mStreamBlock) {
    1490               0 :       stream->mReadaheadBlocks.AddAfter(aBlockIndex, readaheadIndex);
    1491               0 :       return;
    1492                 :     }
    1493               0 :     NS_ASSERTION(bo->mStreamBlock > aBlockOwner->mStreamBlock,
    1494                 :                  "Duplicated blocks??");
    1495               0 :     readaheadIndex = stream->mReadaheadBlocks.GetPrevBlock(readaheadIndex);
    1496                 :   }
    1497                 : 
    1498               0 :   stream->mReadaheadBlocks.AddFirstBlock(aBlockIndex);
    1499               0 :   Verify();
    1500                 : }
    1501                 : 
    1502                 : void
    1503               0 : nsMediaCache::AllocateAndWriteBlock(nsMediaCacheStream* aStream, const void* aData,
    1504                 :                                     nsMediaCacheStream::ReadMode aMode)
    1505                 : {
    1506               0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1507                 : 
    1508               0 :   PRInt32 streamBlockIndex = aStream->mChannelOffset/BLOCK_SIZE;
    1509                 : 
    1510                 :   // Remove all cached copies of this block
    1511               0 :   ResourceStreamIterator iter(aStream->mResourceID);
    1512               0 :   while (nsMediaCacheStream* stream = iter.Next()) {
    1513               0 :     while (streamBlockIndex >= PRInt32(stream->mBlocks.Length())) {
    1514               0 :       stream->mBlocks.AppendElement(-1);
    1515                 :     }
    1516               0 :     if (stream->mBlocks[streamBlockIndex] >= 0) {
    1517                 :       // We no longer want to own this block
    1518               0 :       PRInt32 globalBlockIndex = stream->mBlocks[streamBlockIndex];
    1519               0 :       LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
    1520                 :           globalBlockIndex, stream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE));
    1521               0 :       RemoveBlockOwner(globalBlockIndex, stream);
    1522                 :     }
    1523                 :   }
    1524                 : 
    1525                 :   // Extend the mBlocks array as necessary
    1526                 : 
    1527               0 :   TimeStamp now = TimeStamp::Now();
    1528               0 :   PRInt32 blockIndex = FindBlockForIncomingData(now, aStream);
    1529               0 :   if (blockIndex >= 0) {
    1530               0 :     FreeBlock(blockIndex);
    1531                 : 
    1532               0 :     Block* block = &mIndex[blockIndex];
    1533               0 :     LOG(PR_LOG_DEBUG, ("Allocated block %d to stream %p block %d(%lld)",
    1534                 :         blockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE));
    1535                 : 
    1536               0 :     mFreeBlocks.RemoveBlock(blockIndex);
    1537                 : 
    1538                 :     // Tell each stream using this resource about the new block.
    1539               0 :     ResourceStreamIterator iter(aStream->mResourceID);
    1540               0 :     while (nsMediaCacheStream* stream = iter.Next()) {
    1541               0 :       BlockOwner* bo = block->mOwners.AppendElement();
    1542               0 :       if (!bo)
    1543               0 :         return;
    1544                 : 
    1545               0 :       bo->mStream = stream;
    1546               0 :       bo->mStreamBlock = streamBlockIndex;
    1547               0 :       bo->mLastUseTime = now;
    1548               0 :       stream->mBlocks[streamBlockIndex] = blockIndex;
    1549               0 :       if (streamBlockIndex*BLOCK_SIZE < stream->mStreamOffset) {
    1550                 :         bo->mClass = aMode == nsMediaCacheStream::MODE_PLAYBACK
    1551               0 :           ? PLAYED_BLOCK : METADATA_BLOCK;
    1552                 :         // This must be the most-recently-used block, since we
    1553                 :         // marked it as used now (which may be slightly bogus, but we'll
    1554                 :         // treat it as used for simplicity).
    1555               0 :         GetListForBlock(bo)->AddFirstBlock(blockIndex);
    1556               0 :         Verify();
    1557                 :       } else {
    1558                 :         // This may not be the latest readahead block, although it usually
    1559                 :         // will be. We may have to scan for the right place to insert
    1560                 :         // the block in the list.
    1561               0 :         bo->mClass = READAHEAD_BLOCK;
    1562               0 :         InsertReadaheadBlock(bo, blockIndex);
    1563                 :       }
    1564                 :     }
    1565                 : 
    1566               0 :     nsresult rv = WriteCacheFile(blockIndex*BLOCK_SIZE, aData, BLOCK_SIZE);
    1567               0 :     if (NS_FAILED(rv)) {
    1568               0 :       LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
    1569                 :           blockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE));
    1570               0 :       FreeBlock(blockIndex);
    1571                 :     }
    1572                 :   }
    1573                 : 
    1574                 :   // Queue an Update since the cache state has changed (for example
    1575                 :   // we might want to stop loading because the cache is full)
    1576               0 :   QueueUpdate();
    1577                 : }
    1578                 : 
    1579                 : void
    1580               0 : nsMediaCache::OpenStream(nsMediaCacheStream* aStream)
    1581                 : {
    1582               0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1583                 : 
    1584               0 :   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
    1585               0 :   LOG(PR_LOG_DEBUG, ("Stream %p opened", aStream));
    1586               0 :   mStreams.AppendElement(aStream);
    1587               0 :   aStream->mResourceID = AllocateResourceID();
    1588                 : 
    1589                 :   // Queue an update since a new stream has been opened.
    1590               0 :   gMediaCache->QueueUpdate();
    1591               0 : }
    1592                 : 
    1593                 : void
    1594               0 : nsMediaCache::ReleaseStream(nsMediaCacheStream* aStream)
    1595                 : {
    1596               0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1597                 : 
    1598               0 :   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
    1599               0 :   LOG(PR_LOG_DEBUG, ("Stream %p closed", aStream));
    1600               0 :   mStreams.RemoveElement(aStream);
    1601               0 : }
    1602                 : 
    1603                 : void
    1604               0 : nsMediaCache::ReleaseStreamBlocks(nsMediaCacheStream* aStream)
    1605                 : {
    1606               0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1607                 : 
    1608                 :   // XXX scanning the entire stream doesn't seem great, if not much of it
    1609                 :   // is cached, but the only easy alternative is to scan the entire cache
    1610                 :   // which isn't better
    1611               0 :   PRUint32 length = aStream->mBlocks.Length();
    1612               0 :   for (PRUint32 i = 0; i < length; ++i) {
    1613               0 :     PRInt32 blockIndex = aStream->mBlocks[i];
    1614               0 :     if (blockIndex >= 0) {
    1615               0 :       LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
    1616                 :           blockIndex, aStream, i, (long long)i*BLOCK_SIZE));
    1617               0 :       RemoveBlockOwner(blockIndex, aStream);
    1618                 :     }
    1619                 :   }
    1620               0 : }
    1621                 : 
    1622                 : void
    1623               0 : nsMediaCache::Truncate()
    1624                 : {
    1625                 :   PRUint32 end;
    1626               0 :   for (end = mIndex.Length(); end > 0; --end) {
    1627               0 :     if (!IsBlockFree(end - 1))
    1628               0 :       break;
    1629               0 :     mFreeBlocks.RemoveBlock(end - 1);
    1630                 :   }
    1631                 : 
    1632               0 :   if (end < mIndex.Length()) {
    1633               0 :     mIndex.TruncateLength(end);
    1634                 :     // XXX We could truncate the cache file here, but we don't seem
    1635                 :     // to have a cross-platform API for doing that. At least when all
    1636                 :     // streams are closed we shut down the cache, which erases the
    1637                 :     // file at that point.
    1638                 :   }
    1639               0 : }
    1640                 : 
    1641                 : void
    1642               0 : nsMediaCache::NoteBlockUsage(nsMediaCacheStream* aStream, PRInt32 aBlockIndex,
    1643                 :                              nsMediaCacheStream::ReadMode aMode,
    1644                 :                              TimeStamp aNow)
    1645                 : {
    1646               0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1647                 : 
    1648               0 :   if (aBlockIndex < 0) {
    1649                 :     // this block is not in the cache yet
    1650               0 :     return;
    1651                 :   }
    1652                 : 
    1653               0 :   BlockOwner* bo = GetBlockOwner(aBlockIndex, aStream);
    1654               0 :   if (!bo) {
    1655                 :     // this block is not in the cache yet
    1656               0 :     return;
    1657                 :   }
    1658                 : 
    1659                 :   // The following check has to be <= because the stream offset has
    1660                 :   // not yet been updated for the data read from this block
    1661               0 :   NS_ASSERTION(bo->mStreamBlock*BLOCK_SIZE <= bo->mStream->mStreamOffset,
    1662                 :                "Using a block that's behind the read position?");
    1663                 : 
    1664               0 :   GetListForBlock(bo)->RemoveBlock(aBlockIndex);
    1665                 :   bo->mClass =
    1666                 :     (aMode == nsMediaCacheStream::MODE_METADATA || bo->mClass == METADATA_BLOCK)
    1667               0 :     ? METADATA_BLOCK : PLAYED_BLOCK;
    1668                 :   // Since this is just being used now, it can definitely be at the front
    1669                 :   // of mMetadataBlocks or mPlayedBlocks
    1670               0 :   GetListForBlock(bo)->AddFirstBlock(aBlockIndex);
    1671               0 :   bo->mLastUseTime = aNow;
    1672               0 :   Verify();
    1673                 : }
    1674                 : 
    1675                 : void
    1676               0 : nsMediaCache::NoteSeek(nsMediaCacheStream* aStream, PRInt64 aOldOffset)
    1677                 : {
    1678               0 :   mReentrantMonitor.AssertCurrentThreadIn();
    1679                 : 
    1680               0 :   if (aOldOffset < aStream->mStreamOffset) {
    1681                 :     // We seeked forward. Convert blocks from readahead to played.
    1682                 :     // Any readahead block that intersects the seeked-over range must
    1683                 :     // be converted.
    1684               0 :     PRInt32 blockIndex = aOldOffset/BLOCK_SIZE;
    1685                 :     PRInt32 endIndex =
    1686                 :       NS_MIN<PRInt64>((aStream->mStreamOffset + BLOCK_SIZE - 1)/BLOCK_SIZE,
    1687               0 :              aStream->mBlocks.Length());
    1688               0 :     TimeStamp now = TimeStamp::Now();
    1689               0 :     while (blockIndex < endIndex) {
    1690               0 :       PRInt32 cacheBlockIndex = aStream->mBlocks[blockIndex];
    1691               0 :       if (cacheBlockIndex >= 0) {
    1692                 :         // Marking the block used may not be exactly what we want but
    1693                 :         // it's simple
    1694                 :         NoteBlockUsage(aStream, cacheBlockIndex, nsMediaCacheStream::MODE_PLAYBACK,
    1695               0 :                        now);
    1696                 :       }
    1697               0 :       ++blockIndex;
    1698                 :     }
    1699                 :   } else {
    1700                 :     // We seeked backward. Convert from played to readahead.
    1701                 :     // Any played block that is entirely after the start of the seeked-over
    1702                 :     // range must be converted.
    1703                 :     PRInt32 blockIndex =
    1704               0 :       (aStream->mStreamOffset + BLOCK_SIZE - 1)/BLOCK_SIZE;
    1705                 :     PRInt32 endIndex =
    1706                 :       NS_MIN<PRInt64>((aOldOffset + BLOCK_SIZE - 1)/BLOCK_SIZE,
    1707               0 :              aStream->mBlocks.Length());
    1708               0 :     while (blockIndex < endIndex) {
    1709               0 :       PRInt32 cacheBlockIndex = aStream->mBlocks[endIndex - 1];
    1710               0 :       if (cacheBlockIndex >= 0) {
    1711               0 :         BlockOwner* bo = GetBlockOwner(cacheBlockIndex, aStream);
    1712               0 :         NS_ASSERTION(bo, "Stream doesn't own its blocks?");
    1713               0 :         if (bo->mClass == PLAYED_BLOCK) {
    1714               0 :           aStream->mPlayedBlocks.RemoveBlock(cacheBlockIndex);
    1715               0 :           bo->mClass = READAHEAD_BLOCK;
    1716                 :           // Adding this as the first block is sure to be OK since
    1717                 :           // this must currently be the earliest readahead block
    1718                 :           // (that's why we're proceeding backwards from the end of
    1719                 :           // the seeked range to the start)
    1720               0 :           aStream->mReadaheadBlocks.AddFirstBlock(cacheBlockIndex);
    1721               0 :           Verify();
    1722                 :         }
    1723                 :       }
    1724               0 :       --endIndex;
    1725                 :     }
    1726                 :   }
    1727               0 : }
    1728                 : 
    1729                 : void
    1730               0 : nsMediaCacheStream::NotifyDataLength(PRInt64 aLength)
    1731                 : {
    1732               0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1733                 : 
    1734               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    1735               0 :   mStreamLength = aLength;
    1736               0 : }
    1737                 : 
    1738                 : void
    1739               0 : nsMediaCacheStream::NotifyDataStarted(PRInt64 aOffset)
    1740                 : {
    1741               0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1742                 : 
    1743               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    1744               0 :   NS_WARN_IF_FALSE(aOffset == mChannelOffset,
    1745                 :                    "Server is giving us unexpected offset");
    1746               0 :   mChannelOffset = aOffset;
    1747               0 :   if (mStreamLength >= 0) {
    1748                 :     // If we started reading at a certain offset, then for sure
    1749                 :     // the stream is at least that long.
    1750               0 :     mStreamLength = NS_MAX(mStreamLength, mChannelOffset);
    1751                 :   }
    1752               0 : }
    1753                 : 
    1754                 : void
    1755               0 : nsMediaCacheStream::UpdatePrincipal(nsIPrincipal* aPrincipal)
    1756                 : {
    1757               0 :   if (!mPrincipal) {
    1758               0 :     NS_ASSERTION(!mUsingNullPrincipal, "Are we using a null principal or not?");
    1759               0 :     if (mUsingNullPrincipal) {
    1760                 :       // Don't let mPrincipal be set to anything
    1761               0 :       return;
    1762                 :     }
    1763               0 :     mPrincipal = aPrincipal;
    1764               0 :     return;
    1765                 :   }
    1766                 : 
    1767               0 :   if (mPrincipal == aPrincipal) {
    1768                 :     // Common case
    1769               0 :     NS_ASSERTION(!mUsingNullPrincipal, "We can't receive data from a null principal");
    1770               0 :     return;
    1771                 :   }
    1772               0 :   if (mUsingNullPrincipal) {
    1773                 :     // We've already fallen back to a null principal, so nothing more
    1774                 :     // to do.
    1775               0 :     return;
    1776                 :   }
    1777                 : 
    1778                 :   bool equal;
    1779               0 :   nsresult rv = mPrincipal->Equals(aPrincipal, &equal);
    1780               0 :   if (NS_SUCCEEDED(rv) && equal)
    1781               0 :     return;
    1782                 : 
    1783                 :   // Principals are not equal, so set mPrincipal to a null principal.
    1784               0 :   mPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1");
    1785               0 :   mUsingNullPrincipal = true;
    1786                 : }
    1787                 : 
    1788                 : void
    1789               0 : nsMediaCacheStream::NotifyDataReceived(PRInt64 aSize, const char* aData,
    1790                 :     nsIPrincipal* aPrincipal)
    1791                 : {
    1792               0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1793                 : 
    1794               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    1795               0 :   PRInt64 size = aSize;
    1796               0 :   const char* data = aData;
    1797                 : 
    1798               0 :   LOG(PR_LOG_DEBUG, ("Stream %p DataReceived at %lld count=%lld",
    1799                 :       this, (long long)mChannelOffset, (long long)aSize));
    1800                 : 
    1801                 :   // We process the data one block (or part of a block) at a time
    1802               0 :   while (size > 0) {
    1803               0 :     PRUint32 blockIndex = mChannelOffset/BLOCK_SIZE;
    1804               0 :     PRInt32 blockOffset = PRInt32(mChannelOffset - blockIndex*BLOCK_SIZE);
    1805               0 :     PRInt32 chunkSize = NS_MIN<PRInt64>(BLOCK_SIZE - blockOffset, size);
    1806                 : 
    1807                 :     // This gets set to something non-null if we have a whole block
    1808                 :     // of data to write to the cache
    1809               0 :     const char* blockDataToStore = nsnull;
    1810               0 :     ReadMode mode = MODE_PLAYBACK;
    1811               0 :     if (blockOffset == 0 && chunkSize == BLOCK_SIZE) {
    1812                 :       // We received a whole block, so avoid a useless copy through
    1813                 :       // mPartialBlockBuffer
    1814               0 :       blockDataToStore = data;
    1815                 :     } else {
    1816               0 :       if (blockOffset == 0) {
    1817                 :         // We've just started filling this buffer so now is a good time
    1818                 :         // to clear this flag.
    1819               0 :         mMetadataInPartialBlockBuffer = false;
    1820                 :       }
    1821               0 :       memcpy(reinterpret_cast<char*>(mPartialBlockBuffer) + blockOffset,
    1822               0 :              data, chunkSize);
    1823                 : 
    1824               0 :       if (blockOffset + chunkSize == BLOCK_SIZE) {
    1825                 :         // We completed a block, so lets write it out.
    1826               0 :         blockDataToStore = reinterpret_cast<char*>(mPartialBlockBuffer);
    1827               0 :         if (mMetadataInPartialBlockBuffer) {
    1828               0 :           mode = MODE_METADATA;
    1829                 :         }
    1830                 :       }
    1831                 :     }
    1832                 : 
    1833               0 :     if (blockDataToStore) {
    1834               0 :       gMediaCache->AllocateAndWriteBlock(this, blockDataToStore, mode);
    1835                 :     }
    1836                 : 
    1837               0 :     mChannelOffset += chunkSize;
    1838               0 :     size -= chunkSize;
    1839               0 :     data += chunkSize;
    1840                 :   }
    1841                 : 
    1842               0 :   nsMediaCache::ResourceStreamIterator iter(mResourceID);
    1843               0 :   while (nsMediaCacheStream* stream = iter.Next()) {
    1844               0 :     if (stream->mStreamLength >= 0) {
    1845                 :       // The stream is at least as long as what we've read
    1846               0 :       stream->mStreamLength = NS_MAX(stream->mStreamLength, mChannelOffset);
    1847                 :     }
    1848               0 :     stream->UpdatePrincipal(aPrincipal);
    1849               0 :     stream->mClient->CacheClientNotifyDataReceived();
    1850                 :   }
    1851                 : 
    1852                 :   // Notify in case there's a waiting reader
    1853                 :   // XXX it would be fairly easy to optimize things a lot more to
    1854                 :   // avoid waking up reader threads unnecessarily
    1855               0 :   mon.NotifyAll();
    1856               0 : }
    1857                 : 
    1858                 : void
    1859               0 : nsMediaCacheStream::NotifyDataEnded(nsresult aStatus)
    1860                 : {
    1861               0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1862                 : 
    1863               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    1864                 : 
    1865               0 :   if (NS_FAILED(aStatus)) {
    1866                 :     // Disconnect from other streams sharing our resource, since they
    1867                 :     // should continue trying to load. Our load might have been deliberately
    1868                 :     // canceled and that shouldn't affect other streams.
    1869               0 :     mResourceID = gMediaCache->AllocateResourceID();
    1870                 :   }
    1871                 : 
    1872               0 :   PRInt32 blockOffset = PRInt32(mChannelOffset%BLOCK_SIZE);
    1873               0 :   if (blockOffset > 0) {
    1874                 :     // Write back the partial block
    1875               0 :     memset(reinterpret_cast<char*>(mPartialBlockBuffer) + blockOffset, 0,
    1876               0 :            BLOCK_SIZE - blockOffset);
    1877                 :     gMediaCache->AllocateAndWriteBlock(this, mPartialBlockBuffer,
    1878               0 :         mMetadataInPartialBlockBuffer ? MODE_METADATA : MODE_PLAYBACK);
    1879                 :     // Wake up readers who may be waiting for this data
    1880               0 :     mon.NotifyAll();
    1881                 :   }
    1882                 : 
    1883               0 :   if (!mDidNotifyDataEnded) {
    1884               0 :     nsMediaCache::ResourceStreamIterator iter(mResourceID);
    1885               0 :     while (nsMediaCacheStream* stream = iter.Next()) {
    1886               0 :       if (NS_SUCCEEDED(aStatus)) {
    1887                 :         // We read the whole stream, so remember the true length
    1888               0 :         stream->mStreamLength = mChannelOffset;
    1889                 :       }
    1890               0 :       NS_ASSERTION(!stream->mDidNotifyDataEnded, "Stream already ended!");
    1891               0 :       stream->mDidNotifyDataEnded = true;
    1892               0 :       stream->mNotifyDataEndedStatus = aStatus;
    1893               0 :       stream->mClient->CacheClientNotifyDataEnded(aStatus);
    1894                 :     }
    1895                 :   }
    1896                 : 
    1897               0 :   mChannelEnded = true;
    1898               0 :   gMediaCache->QueueUpdate();
    1899               0 : }
    1900                 : 
    1901               0 : nsMediaCacheStream::~nsMediaCacheStream()
    1902                 : {
    1903               0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1904               0 :   NS_ASSERTION(!mPinCount, "Unbalanced Pin");
    1905                 : 
    1906               0 :   if (gMediaCache) {
    1907               0 :     NS_ASSERTION(mClosed, "Stream was not closed");
    1908               0 :     gMediaCache->ReleaseStream(this);
    1909               0 :     nsMediaCache::MaybeShutdown();
    1910                 :   }
    1911               0 : }
    1912                 : 
    1913                 : void
    1914               0 : nsMediaCacheStream::SetSeekable(bool aIsSeekable)
    1915                 : {
    1916               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    1917               0 :   NS_ASSERTION(mIsSeekable || aIsSeekable ||
    1918                 :                mChannelOffset == 0, "channel offset must be zero when we become non-seekable");
    1919               0 :   mIsSeekable = aIsSeekable;
    1920                 :   // Queue an Update since we may change our strategy for dealing
    1921                 :   // with this stream
    1922               0 :   gMediaCache->QueueUpdate();
    1923               0 : }
    1924                 : 
    1925                 : bool
    1926               0 : nsMediaCacheStream::IsSeekable()
    1927                 : {
    1928               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    1929               0 :   return mIsSeekable;
    1930                 : }
    1931                 : 
    1932                 : bool
    1933               0 : nsMediaCacheStream::AreAllStreamsForResourceSuspended(MediaResource** aActiveStream)
    1934                 : {
    1935               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    1936               0 :   nsMediaCache::ResourceStreamIterator iter(mResourceID);
    1937               0 :   while (nsMediaCacheStream* stream = iter.Next()) {
    1938               0 :     if (!stream->mCacheSuspended && !stream->mChannelEnded && !stream->mClosed) {
    1939               0 :       if (aActiveStream) {
    1940               0 :         *aActiveStream = stream->mClient;
    1941                 :       }
    1942               0 :       return false;
    1943                 :     }
    1944                 :   }
    1945               0 :   if (aActiveStream) {
    1946               0 :     *aActiveStream = nsnull;
    1947                 :   }
    1948               0 :   return true;
    1949                 : }
    1950                 : 
    1951                 : void
    1952               0 : nsMediaCacheStream::Close()
    1953                 : {
    1954               0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1955                 : 
    1956               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    1957               0 :   CloseInternal(mon);
    1958                 :   // Queue an Update since we may have created more free space. Don't do
    1959                 :   // it from CloseInternal since that gets called by Update() itself
    1960                 :   // sometimes, and we try to not to queue updates from Update().
    1961               0 :   gMediaCache->QueueUpdate();
    1962               0 : }
    1963                 : 
    1964                 : void
    1965               0 : nsMediaCacheStream::EnsureCacheUpdate()
    1966                 : {
    1967               0 :   if (mHasHadUpdate)
    1968               0 :     return;
    1969               0 :   gMediaCache->Update();
    1970                 : }
    1971                 : 
    1972                 : void
    1973               0 : nsMediaCacheStream::CloseInternal(ReentrantMonitorAutoEnter& aReentrantMonitor)
    1974                 : {
    1975               0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1976                 : 
    1977               0 :   if (mClosed)
    1978               0 :     return;
    1979               0 :   mClosed = true;
    1980               0 :   gMediaCache->ReleaseStreamBlocks(this);
    1981                 :   // Wake up any blocked readers
    1982               0 :   aReentrantMonitor.NotifyAll();
    1983                 : }
    1984                 : 
    1985                 : void
    1986               0 : nsMediaCacheStream::Pin()
    1987                 : {
    1988               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    1989               0 :   ++mPinCount;
    1990                 :   // Queue an Update since we may no longer want to read more into the
    1991                 :   // cache, if this stream's block have become non-evictable
    1992               0 :   gMediaCache->QueueUpdate();
    1993               0 : }
    1994                 : 
    1995                 : void
    1996               0 : nsMediaCacheStream::Unpin()
    1997                 : {
    1998               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    1999               0 :   NS_ASSERTION(mPinCount > 0, "Unbalanced Unpin");
    2000               0 :   --mPinCount;
    2001                 :   // Queue an Update since we may be able to read more into the
    2002                 :   // cache, if this stream's block have become evictable
    2003               0 :   gMediaCache->QueueUpdate();
    2004               0 : }
    2005                 : 
    2006                 : PRInt64
    2007               0 : nsMediaCacheStream::GetLength()
    2008                 : {
    2009               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    2010               0 :   return mStreamLength;
    2011                 : }
    2012                 : 
    2013                 : PRInt64
    2014               0 : nsMediaCacheStream::GetNextCachedData(PRInt64 aOffset)
    2015                 : {
    2016               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    2017               0 :   return GetNextCachedDataInternal(aOffset);
    2018                 : }
    2019                 : 
    2020                 : PRInt64
    2021               0 : nsMediaCacheStream::GetCachedDataEnd(PRInt64 aOffset)
    2022                 : {
    2023               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    2024               0 :   return GetCachedDataEndInternal(aOffset);
    2025                 : }
    2026                 : 
    2027                 : bool
    2028               0 : nsMediaCacheStream::IsDataCachedToEndOfStream(PRInt64 aOffset)
    2029                 : {
    2030               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    2031               0 :   if (mStreamLength < 0)
    2032               0 :     return false;
    2033               0 :   return GetCachedDataEndInternal(aOffset) >= mStreamLength;
    2034                 : }
    2035                 : 
    2036                 : PRInt64
    2037               0 : nsMediaCacheStream::GetCachedDataEndInternal(PRInt64 aOffset)
    2038                 : {
    2039               0 :   gMediaCache->GetReentrantMonitor().AssertCurrentThreadIn();
    2040               0 :   PRUint32 startBlockIndex = aOffset/BLOCK_SIZE;
    2041               0 :   PRUint32 blockIndex = startBlockIndex;
    2042               0 :   while (blockIndex < mBlocks.Length() && mBlocks[blockIndex] != -1) {
    2043               0 :     ++blockIndex;
    2044                 :   }
    2045               0 :   PRInt64 result = blockIndex*BLOCK_SIZE;
    2046               0 :   if (blockIndex == mChannelOffset/BLOCK_SIZE) {
    2047                 :     // The block containing mChannelOffset may be partially read but not
    2048                 :     // yet committed to the main cache
    2049               0 :     result = mChannelOffset;
    2050                 :   }
    2051               0 :   if (mStreamLength >= 0) {
    2052                 :     // The last block in the cache may only be partially valid, so limit
    2053                 :     // the cached range to the stream length
    2054               0 :     result = NS_MIN(result, mStreamLength);
    2055                 :   }
    2056               0 :   return NS_MAX(result, aOffset);
    2057                 : }
    2058                 : 
    2059                 : PRInt64
    2060               0 : nsMediaCacheStream::GetNextCachedDataInternal(PRInt64 aOffset)
    2061                 : {
    2062               0 :   gMediaCache->GetReentrantMonitor().AssertCurrentThreadIn();
    2063               0 :   if (aOffset == mStreamLength)
    2064               0 :     return -1;
    2065                 : 
    2066               0 :   PRUint32 startBlockIndex = aOffset/BLOCK_SIZE;
    2067               0 :   PRUint32 channelBlockIndex = mChannelOffset/BLOCK_SIZE;
    2068                 : 
    2069               0 :   if (startBlockIndex == channelBlockIndex &&
    2070                 :       aOffset < mChannelOffset) {
    2071                 :     // The block containing mChannelOffset is partially read, but not
    2072                 :     // yet committed to the main cache. aOffset lies in the partially
    2073                 :     // read portion, thus it is effectively cached.
    2074               0 :     return aOffset;
    2075                 :   }
    2076                 : 
    2077               0 :   if (startBlockIndex >= mBlocks.Length())
    2078               0 :     return -1;
    2079                 : 
    2080                 :   // Is the current block cached?
    2081               0 :   if (mBlocks[startBlockIndex] != -1)
    2082               0 :     return aOffset;
    2083                 : 
    2084                 :   // Count the number of uncached blocks
    2085               0 :   bool hasPartialBlock = (mChannelOffset % BLOCK_SIZE) != 0;
    2086               0 :   PRUint32 blockIndex = startBlockIndex + 1;
    2087               0 :   while (true) {
    2088               0 :     if ((hasPartialBlock && blockIndex == channelBlockIndex) ||
    2089               0 :         (blockIndex < mBlocks.Length() && mBlocks[blockIndex] != -1)) {
    2090                 :       // We at the incoming channel block, which has has data in it,
    2091                 :       // or are we at a cached block. Return index of block start.
    2092               0 :       return blockIndex * BLOCK_SIZE;
    2093                 :     }
    2094                 : 
    2095                 :     // No more cached blocks?
    2096               0 :     if (blockIndex >= mBlocks.Length())
    2097               0 :       return -1;
    2098                 : 
    2099               0 :     ++blockIndex;
    2100                 :   }
    2101                 : 
    2102                 :   NS_NOTREACHED("Should return in loop");
    2103                 :   return -1;
    2104                 : }
    2105                 : 
    2106                 : void
    2107               0 : nsMediaCacheStream::SetReadMode(ReadMode aMode)
    2108                 : {
    2109               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    2110               0 :   if (aMode == mCurrentMode)
    2111                 :     return;
    2112               0 :   mCurrentMode = aMode;
    2113               0 :   gMediaCache->QueueUpdate();
    2114                 : }
    2115                 : 
    2116                 : void
    2117               0 : nsMediaCacheStream::SetPlaybackRate(PRUint32 aBytesPerSecond)
    2118                 : {
    2119               0 :   NS_ASSERTION(aBytesPerSecond > 0, "Zero playback rate not allowed");
    2120               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    2121               0 :   if (aBytesPerSecond == mPlaybackBytesPerSecond)
    2122                 :     return;
    2123               0 :   mPlaybackBytesPerSecond = aBytesPerSecond;
    2124               0 :   gMediaCache->QueueUpdate();
    2125                 : }
    2126                 : 
    2127                 : nsresult
    2128               0 : nsMediaCacheStream::Seek(PRInt32 aWhence, PRInt64 aOffset)
    2129                 : {
    2130               0 :   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
    2131                 : 
    2132               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    2133               0 :   if (mClosed)
    2134               0 :     return NS_ERROR_FAILURE;
    2135                 : 
    2136               0 :   PRInt64 oldOffset = mStreamOffset;
    2137               0 :   switch (aWhence) {
    2138                 :   case PR_SEEK_END:
    2139               0 :     if (mStreamLength < 0)
    2140               0 :       return NS_ERROR_FAILURE;
    2141               0 :     mStreamOffset = mStreamLength + aOffset;
    2142               0 :     break;
    2143                 :   case PR_SEEK_CUR:
    2144               0 :     mStreamOffset += aOffset;
    2145               0 :     break;
    2146                 :   case PR_SEEK_SET:
    2147               0 :     mStreamOffset = aOffset;
    2148               0 :     break;
    2149                 :   default:
    2150               0 :     NS_ERROR("Unknown whence");
    2151               0 :     return NS_ERROR_FAILURE;
    2152                 :   }
    2153                 : 
    2154               0 :   LOG(PR_LOG_DEBUG, ("Stream %p Seek to %lld", this, (long long)mStreamOffset));
    2155               0 :   gMediaCache->NoteSeek(this, oldOffset);
    2156                 : 
    2157               0 :   gMediaCache->QueueUpdate();
    2158               0 :   return NS_OK;
    2159                 : }
    2160                 : 
    2161                 : PRInt64
    2162               0 : nsMediaCacheStream::Tell()
    2163                 : {
    2164               0 :   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
    2165                 : 
    2166               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    2167               0 :   return mStreamOffset;
    2168                 : }
    2169                 : 
    2170                 : nsresult
    2171               0 : nsMediaCacheStream::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
    2172                 : {
    2173               0 :   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
    2174                 : 
    2175               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    2176               0 :   if (mClosed)
    2177               0 :     return NS_ERROR_FAILURE;
    2178                 : 
    2179               0 :   PRUint32 count = 0;
    2180                 :   // Read one block (or part of a block) at a time
    2181               0 :   while (count < aCount) {
    2182               0 :     PRUint32 streamBlock = PRUint32(mStreamOffset/BLOCK_SIZE);
    2183                 :     PRUint32 offsetInStreamBlock =
    2184               0 :       PRUint32(mStreamOffset - streamBlock*BLOCK_SIZE);
    2185               0 :     PRInt64 size = NS_MIN(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
    2186                 : 
    2187               0 :     if (mStreamLength >= 0) {
    2188                 :       // Don't try to read beyond the end of the stream
    2189               0 :       PRInt64 bytesRemaining = mStreamLength - mStreamOffset;
    2190               0 :       if (bytesRemaining <= 0) {
    2191                 :         // Get out of here and return NS_OK
    2192               0 :         break;
    2193                 :       }
    2194               0 :       size = NS_MIN(size, bytesRemaining);
    2195                 :       // Clamp size until 64-bit file size issues (bug 500784) are fixed.
    2196               0 :       size = NS_MIN(size, PRInt64(PR_INT32_MAX));
    2197                 :     }
    2198                 : 
    2199                 :     PRInt32 bytes;
    2200               0 :     PRInt32 cacheBlock = streamBlock < mBlocks.Length() ? mBlocks[streamBlock] : -1;
    2201               0 :     if (cacheBlock < 0) {
    2202                 :       // We don't have a complete cached block here.
    2203                 : 
    2204               0 :       if (count > 0) {
    2205                 :         // Some data has been read, so return what we've got instead of
    2206                 :         // blocking or trying to find a stream with a partial block.
    2207               0 :         break;
    2208                 :       }
    2209                 : 
    2210                 :       // See if the data is available in the partial cache block of any
    2211                 :       // stream reading this resource. We need to do this in case there is
    2212                 :       // another stream with this resource that has all the data to the end of
    2213                 :       // the stream but the data doesn't end on a block boundary.
    2214               0 :       nsMediaCacheStream* streamWithPartialBlock = nsnull;
    2215               0 :       nsMediaCache::ResourceStreamIterator iter(mResourceID);
    2216               0 :       while (nsMediaCacheStream* stream = iter.Next()) {
    2217               0 :         if (PRUint32(stream->mChannelOffset/BLOCK_SIZE) == streamBlock &&
    2218                 :             mStreamOffset < stream->mChannelOffset) {
    2219               0 :           streamWithPartialBlock = stream;
    2220               0 :           break;
    2221                 :         }
    2222                 :       }
    2223               0 :       if (streamWithPartialBlock) {
    2224                 :         // We can just use the data in mPartialBlockBuffer. In fact we should
    2225                 :         // use it rather than waiting for the block to fill and land in
    2226                 :         // the cache.
    2227               0 :         bytes = NS_MIN<PRInt64>(size, streamWithPartialBlock->mChannelOffset - mStreamOffset);
    2228                 :         memcpy(aBuffer,
    2229               0 :           reinterpret_cast<char*>(streamWithPartialBlock->mPartialBlockBuffer) + offsetInStreamBlock, bytes);
    2230               0 :         if (mCurrentMode == MODE_METADATA) {
    2231               0 :           streamWithPartialBlock->mMetadataInPartialBlockBuffer = true;
    2232                 :         }
    2233               0 :         mStreamOffset += bytes;
    2234               0 :         count = bytes;
    2235               0 :         break;
    2236                 :       }
    2237                 : 
    2238                 :       // No data has been read yet, so block
    2239               0 :       mon.Wait();
    2240               0 :       if (mClosed) {
    2241                 :         // We may have successfully read some data, but let's just throw
    2242                 :         // that out.
    2243               0 :         return NS_ERROR_FAILURE;
    2244                 :       }
    2245               0 :       continue;
    2246                 :     }
    2247                 : 
    2248               0 :     gMediaCache->NoteBlockUsage(this, cacheBlock, mCurrentMode, TimeStamp::Now());
    2249                 : 
    2250               0 :     PRInt64 offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock;
    2251               0 :     NS_ABORT_IF_FALSE(size >= 0 && size <= PR_INT32_MAX, "Size out of range.");
    2252               0 :     nsresult rv = gMediaCache->ReadCacheFile(offset, aBuffer + count, PRInt32(size), &bytes);
    2253               0 :     if (NS_FAILED(rv)) {
    2254               0 :       if (count == 0)
    2255               0 :         return rv;
    2256                 :       // If we did successfully read some data, may as well return it
    2257               0 :       break;
    2258                 :     }
    2259               0 :     mStreamOffset += bytes;
    2260               0 :     count += bytes;
    2261                 :   }
    2262                 : 
    2263               0 :   if (count > 0) {
    2264                 :     // Some data was read, so queue an update since block priorities may
    2265                 :     // have changed
    2266               0 :     gMediaCache->QueueUpdate();
    2267                 :   }
    2268               0 :   LOG(PR_LOG_DEBUG,
    2269                 :       ("Stream %p Read at %lld count=%d", this, (long long)(mStreamOffset-count), count));
    2270               0 :   *aBytes = count;
    2271               0 :   return NS_OK;
    2272                 : }
    2273                 : 
    2274                 : nsresult
    2275               0 : nsMediaCacheStream::ReadFromCache(char* aBuffer,
    2276                 :                                   PRInt64 aOffset,
    2277                 :                                   PRInt64 aCount)
    2278                 : {
    2279               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    2280               0 :   if (mClosed)
    2281               0 :     return NS_ERROR_FAILURE;
    2282                 : 
    2283                 :   // Read one block (or part of a block) at a time
    2284               0 :   PRUint32 count = 0;
    2285               0 :   PRInt64 streamOffset = aOffset;
    2286               0 :   while (count < aCount) {
    2287               0 :     PRUint32 streamBlock = PRUint32(streamOffset/BLOCK_SIZE);
    2288                 :     PRUint32 offsetInStreamBlock =
    2289               0 :       PRUint32(streamOffset - streamBlock*BLOCK_SIZE);
    2290               0 :     PRInt64 size = NS_MIN<PRInt64>(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
    2291                 : 
    2292               0 :     if (mStreamLength >= 0) {
    2293                 :       // Don't try to read beyond the end of the stream
    2294               0 :       PRInt64 bytesRemaining = mStreamLength - streamOffset;
    2295               0 :       if (bytesRemaining <= 0) {
    2296               0 :         return NS_ERROR_FAILURE;
    2297                 :       }
    2298               0 :       size = NS_MIN(size, bytesRemaining);
    2299                 :       // Clamp size until 64-bit file size issues (bug 500784) are fixed.
    2300               0 :       size = NS_MIN(size, PRInt64(PR_INT32_MAX));
    2301                 :     }
    2302                 : 
    2303                 :     PRInt32 bytes;
    2304               0 :     PRUint32 channelBlock = PRUint32(mChannelOffset/BLOCK_SIZE);
    2305               0 :     PRInt32 cacheBlock = streamBlock < mBlocks.Length() ? mBlocks[streamBlock] : -1;
    2306               0 :     if (channelBlock == streamBlock && streamOffset < mChannelOffset) {
    2307                 :       // We can just use the data in mPartialBlockBuffer. In fact we should
    2308                 :       // use it rather than waiting for the block to fill and land in
    2309                 :       // the cache.
    2310               0 :       bytes = NS_MIN<PRInt64>(size, mChannelOffset - streamOffset);
    2311               0 :       memcpy(aBuffer + count,
    2312               0 :         reinterpret_cast<char*>(mPartialBlockBuffer) + offsetInStreamBlock, bytes);
    2313                 :     } else {
    2314               0 :       if (cacheBlock < 0) {
    2315                 :         // We expect all blocks to be cached! Fail!
    2316               0 :         return NS_ERROR_FAILURE;
    2317                 :       }
    2318               0 :       PRInt64 offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock;
    2319               0 :       NS_ABORT_IF_FALSE(size >= 0 && size <= PR_INT32_MAX, "Size out of range.");
    2320               0 :       nsresult rv = gMediaCache->ReadCacheFile(offset, aBuffer + count, PRInt32(size), &bytes);
    2321               0 :       if (NS_FAILED(rv)) {
    2322               0 :         return rv;
    2323                 :       }
    2324                 :     }
    2325               0 :     streamOffset += bytes;
    2326               0 :     count += bytes;
    2327                 :   }
    2328                 : 
    2329               0 :   return NS_OK;
    2330                 : }
    2331                 : 
    2332                 : nsresult
    2333               0 : nsMediaCacheStream::Init()
    2334                 : {
    2335               0 :   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    2336                 : 
    2337               0 :   if (mInitialized)
    2338               0 :     return NS_OK;
    2339                 : 
    2340               0 :   InitMediaCache();
    2341               0 :   if (!gMediaCache)
    2342               0 :     return NS_ERROR_FAILURE;
    2343               0 :   gMediaCache->OpenStream(this);
    2344               0 :   mInitialized = true;
    2345               0 :   return NS_OK;
    2346                 : }
    2347                 : 
    2348                 : nsresult
    2349               0 : nsMediaCacheStream::InitAsClone(nsMediaCacheStream* aOriginal)
    2350                 : {
    2351               0 :   if (!aOriginal->IsAvailableForSharing())
    2352               0 :     return NS_ERROR_FAILURE;
    2353                 : 
    2354               0 :   if (mInitialized)
    2355               0 :     return NS_OK;
    2356                 : 
    2357               0 :   nsresult rv = Init();
    2358               0 :   if (NS_FAILED(rv))
    2359               0 :     return rv;
    2360               0 :   mResourceID = aOriginal->mResourceID;
    2361                 : 
    2362                 :   // Grab cache blocks from aOriginal as readahead blocks for our stream
    2363               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    2364                 : 
    2365               0 :   mPrincipal = aOriginal->mPrincipal;
    2366               0 :   mStreamLength = aOriginal->mStreamLength;
    2367               0 :   mIsSeekable = aOriginal->mIsSeekable;
    2368                 : 
    2369                 :   // Cloned streams are initially suspended, since there is no channel open
    2370                 :   // initially for a clone.
    2371               0 :   mCacheSuspended = true;
    2372               0 :   mChannelEnded = true;
    2373                 : 
    2374               0 :   if (aOriginal->mDidNotifyDataEnded) {
    2375               0 :     mNotifyDataEndedStatus = aOriginal->mNotifyDataEndedStatus;
    2376               0 :     mDidNotifyDataEnded = true;
    2377               0 :     mClient->CacheClientNotifyDataEnded(mNotifyDataEndedStatus);
    2378                 :   }
    2379                 : 
    2380               0 :   for (PRUint32 i = 0; i < aOriginal->mBlocks.Length(); ++i) {
    2381               0 :     PRInt32 cacheBlockIndex = aOriginal->mBlocks[i];
    2382               0 :     if (cacheBlockIndex < 0)
    2383               0 :       continue;
    2384                 : 
    2385               0 :     while (i >= mBlocks.Length()) {
    2386               0 :       mBlocks.AppendElement(-1);
    2387                 :     }
    2388                 :     // Every block is a readahead block for the clone because the clone's initial
    2389                 :     // stream offset is zero
    2390               0 :     gMediaCache->AddBlockOwnerAsReadahead(cacheBlockIndex, this, i);
    2391                 :   }
    2392                 : 
    2393               0 :   return NS_OK;
    2394                 : }
    2395                 : 
    2396               0 : nsresult nsMediaCacheStream::GetCachedRanges(nsTArray<MediaByteRange>& aRanges)
    2397                 : {
    2398                 :   // Take the monitor, so that the cached data ranges can't grow while we're
    2399                 :   // trying to loop over them.
    2400               0 :   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
    2401                 : 
    2402                 :   // We must be pinned while running this, otherwise the cached data ranges may
    2403                 :   // shrink while we're trying to loop over them.
    2404               0 :   NS_ASSERTION(mPinCount > 0, "Must be pinned");
    2405                 : 
    2406               0 :   PRInt64 startOffset = GetNextCachedData(0);
    2407               0 :   while (startOffset >= 0) {
    2408               0 :     PRInt64 endOffset = GetCachedDataEnd(startOffset);
    2409               0 :     NS_ASSERTION(startOffset < endOffset, "Buffered range must end after its start");
    2410                 :     // Bytes [startOffset..endOffset] are cached.
    2411               0 :     aRanges.AppendElement(MediaByteRange(startOffset, endOffset));
    2412               0 :     startOffset = GetNextCachedData(endOffset);
    2413               0 :     NS_ASSERTION(startOffset == -1 || startOffset > endOffset,
    2414                 :       "Must have advanced to start of next range, or hit end of stream");
    2415                 :   }
    2416               0 :   return NS_OK;
    2417                 : }

Generated by: LCOV version 1.7