blob: 5536e37de22c5908d2377bc13f29c94be59e8650 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.documentsui.archives;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.GuardedBy;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.compressors.CompressorException;
/**
* Loads an instance of Archive lazily.
*/
public class Loader {
private static final String TAG = "Loader";
public static final int STATUS_OPENING = 0;
public static final int STATUS_OPENED = 1;
public static final int STATUS_FAILED = 2;
public static final int STATUS_CLOSING = 3;
public static final int STATUS_CLOSED = 4;
private final Context mContext;
private final Uri mArchiveUri;
private final int mAccessMode;
private final Uri mNotificationUri;
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
private final Object mLock = new Object();
@GuardedBy("mLock")
private int mStatus = STATUS_OPENING;
@GuardedBy("mLock")
private int mRefCount = 0;
private Archive mArchive = null;
Loader(Context context, Uri archiveUri, int accessMode, Uri notificationUri) {
this.mContext = context;
this.mArchiveUri = archiveUri;
this.mAccessMode = accessMode;
this.mNotificationUri = notificationUri;
// Start loading the archive immediately in the background.
mExecutor.submit(this::get);
}
synchronized Archive get() {
synchronized (mLock) {
if (mStatus == STATUS_OPENED) {
return mArchive;
}
}
synchronized (mLock) {
if (mStatus != STATUS_OPENING) {
throw new IllegalStateException(
"Trying to perform an operation on an archive which is invalidated.");
}
}
try {
if (ReadableArchive.supportsAccessMode(mAccessMode)) {
final ContentResolver contentResolver = mContext.getContentResolver();
final String archiveMimeType = contentResolver.getType(mArchiveUri);
mArchive = ReadableArchive.createForParcelFileDescriptor(
mContext,
contentResolver.openFileDescriptor(
mArchiveUri, "r", null /* signal */),
mArchiveUri, archiveMimeType, mAccessMode, mNotificationUri);
} else if (WriteableArchive.supportsAccessMode(mAccessMode)) {
mArchive = WriteableArchive.createForParcelFileDescriptor(
mContext,
mContext.getContentResolver().openFileDescriptor(
mArchiveUri, "w", null /* signal */),
mArchiveUri, mAccessMode, mNotificationUri);
} else {
throw new IllegalStateException("Access mode not supported.");
}
synchronized (mLock) {
if (mRefCount == 0) {
mArchive.close();
mStatus = STATUS_CLOSED;
} else {
mStatus = STATUS_OPENED;
}
}
} catch (IOException | RuntimeException | ArchiveException | CompressorException e) {
Log.e(TAG, "Failed to open the archive.", e);
synchronized (mLock) {
mStatus = STATUS_FAILED;
}
throw new IllegalStateException("Failed to open the archive.", e);
} finally {
synchronized (mLock) {
// Only notify when there might be someone listening.
if (mRefCount > 0) {
// Notify observers that the root directory is loaded (or failed)
// so clients reload it.
mContext.getContentResolver().notifyChange(
ArchivesProvider.buildUriForArchive(mArchiveUri, mAccessMode),
null /* observer */, false /* syncToNetwork */);
}
}
}
return mArchive;
}
int getStatus() {
synchronized (mLock) {
return mStatus;
}
}
void acquire() {
synchronized (mLock) {
mRefCount++;
}
}
void release() {
synchronized (mLock) {
mRefCount--;
if (mRefCount == 0) {
assert(mStatus == STATUS_OPENING
|| mStatus == STATUS_OPENED
|| mStatus == STATUS_FAILED);
switch (mStatus) {
case STATUS_OPENED:
try {
mArchive.close();
mStatus = STATUS_CLOSED;
} catch (IOException e) {
Log.e(TAG, "Failed to close the archive on release.", e);
}
break;
case STATUS_FAILED:
mStatus = STATUS_CLOSED;
break;
case STATUS_OPENING:
mStatus = STATUS_CLOSING;
// ::get() will close the archive once opened.
break;
}
}
}
}
}