Home | History | Annotate | Download | only in archives
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.documentsui.archives;
     18 
     19 import com.android.internal.annotations.GuardedBy;
     20 
     21 import android.content.Context;
     22 import android.net.Uri;
     23 import android.util.Log;
     24 
     25 import java.io.File;
     26 import java.io.FileNotFoundException;
     27 import java.io.IOException;
     28 import java.util.concurrent.ExecutorService;
     29 import java.util.concurrent.Executors;
     30 import java.util.concurrent.locks.Lock;
     31 
     32 /**
     33  * Loads an instance of Archive lazily.
     34  */
     35 public class Loader {
     36     private static final String TAG = "Loader";
     37 
     38     public static final int STATUS_OPENING = 0;
     39     public static final int STATUS_OPENED = 1;
     40     public static final int STATUS_FAILED = 2;
     41     public static final int STATUS_CLOSING = 3;
     42     public static final int STATUS_CLOSED = 4;
     43 
     44     private final Context mContext;
     45     private final Uri mArchiveUri;
     46     private final int mAccessMode;
     47     private final Uri mNotificationUri;
     48     private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
     49     private final Object mLock = new Object();
     50     @GuardedBy("mLock")
     51     private int mStatus = STATUS_OPENING;
     52     @GuardedBy("mLock")
     53     private int mRefCount = 0;
     54     private Archive mArchive = null;
     55 
     56     Loader(Context context, Uri archiveUri, int accessMode, Uri notificationUri) {
     57         this.mContext = context;
     58         this.mArchiveUri = archiveUri;
     59         this.mAccessMode = accessMode;
     60         this.mNotificationUri = notificationUri;
     61 
     62         // Start loading the archive immediately in the background.
     63         mExecutor.submit(this::get);
     64     }
     65 
     66     synchronized Archive get() {
     67         synchronized (mLock) {
     68             if (mStatus == STATUS_OPENED) {
     69                 return mArchive;
     70             }
     71         }
     72 
     73         synchronized (mLock) {
     74             if (mStatus != STATUS_OPENING) {
     75                 throw new IllegalStateException(
     76                         "Trying to perform an operation on an archive which is invalidated.");
     77             }
     78         }
     79 
     80         try {
     81             if (ReadableArchive.supportsAccessMode(mAccessMode)) {
     82                 mArchive = ReadableArchive.createForParcelFileDescriptor(
     83                         mContext,
     84                         mContext.getContentResolver().openFileDescriptor(
     85                                 mArchiveUri, "r", null /* signal */),
     86                         mArchiveUri, mAccessMode, mNotificationUri);
     87             } else if (WriteableArchive.supportsAccessMode(mAccessMode)) {
     88                 mArchive = WriteableArchive.createForParcelFileDescriptor(
     89                         mContext,
     90                         mContext.getContentResolver().openFileDescriptor(
     91                                 mArchiveUri, "w", null /* signal */),
     92                         mArchiveUri, mAccessMode, mNotificationUri);
     93             } else {
     94                 throw new IllegalStateException("Access mode not supported.");
     95             }
     96             synchronized (mLock) {
     97                 if (mRefCount == 0) {
     98                     mArchive.close();
     99                     mStatus = STATUS_CLOSED;
    100                 } else {
    101                     mStatus = STATUS_OPENED;
    102                 }
    103             }
    104         } catch (IOException | RuntimeException e) {
    105             Log.e(TAG, "Failed to open the archive.", e);
    106             synchronized (mLock) {
    107                 mStatus = STATUS_FAILED;
    108             }
    109             throw new IllegalStateException("Failed to open the archive.", e);
    110         } finally {
    111             synchronized (mLock) {
    112                 // Only notify when there might be someone listening.
    113                 if (mRefCount > 0) {
    114                     // Notify observers that the root directory is loaded (or failed)
    115                     // so clients reload it.
    116                     mContext.getContentResolver().notifyChange(
    117                             ArchivesProvider.buildUriForArchive(mArchiveUri, mAccessMode),
    118                             null /* observer */, false /* syncToNetwork */);
    119                 }
    120             }
    121         }
    122 
    123         return mArchive;
    124     }
    125 
    126     int getStatus() {
    127         synchronized (mLock) {
    128             return mStatus;
    129         }
    130     }
    131 
    132     void acquire() {
    133         synchronized (mLock) {
    134             mRefCount++;
    135         }
    136     }
    137 
    138     void release() {
    139         synchronized (mLock) {
    140             mRefCount--;
    141             if (mRefCount == 0) {
    142                 assert(mStatus == STATUS_OPENING
    143                         || mStatus == STATUS_OPENED
    144                         || mStatus == STATUS_FAILED);
    145 
    146                 switch (mStatus) {
    147                     case STATUS_OPENED:
    148                         try {
    149                             mArchive.close();
    150                             mStatus = STATUS_CLOSED;
    151                         } catch (IOException e) {
    152                             Log.e(TAG, "Failed to close the archive on release.", e);
    153                         }
    154                         break;
    155                     case STATUS_FAILED:
    156                         mStatus = STATUS_CLOSED;
    157                         break;
    158                     case STATUS_OPENING:
    159                         mStatus = STATUS_CLOSING;
    160                         // ::get() will close the archive once opened.
    161                         break;
    162                 }
    163             }
    164         }
    165     }
    166 }
    167