blob: 5102718e8667c51a391f9e43d5be3cfc1b9a5381 [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;
import android.net.Uri;
import android.os.AsyncTask;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileLock;
import java.util.Scanner;
/**
* Provides support for storing lists of documents identified by Uri.
*
* <li>Access to this object *must* be synchronized externally.
* <li>All calls to this class are I/O intensive and must be wrapped in an AsyncTask.
*/
public final class ClipStorage {
private static final String TAG = "ClipStorage";
private static final byte[] LINE_SEPARATOR = System.lineSeparator().getBytes();
public static final long NO_SELECTION_TAG = -1;
private final File mOutDir;
/**
* @param outDir see {@link #prepareStorage(File)}.
*/
public ClipStorage(File outDir) {
assert(outDir.isDirectory());
mOutDir = outDir;
}
/**
* Creates a clip tag.
*
* NOTE: this tag doesn't guarantee perfect uniqueness, but should work well unless user creates
* clips more than hundreds of times per second.
*/
public long createTag() {
return System.currentTimeMillis();
}
/**
* Returns a writer. Callers must close the writer when finished.
*/
public Writer createWriter(long tag) throws IOException {
File file = toTagFile(tag);
return new Writer(file);
}
@VisibleForTesting
public Reader createReader(long tag) throws IOException {
File file = toTagFile(tag);
return new Reader(file);
}
@VisibleForTesting
public void delete(long tag) throws IOException {
toTagFile(tag).delete();
}
private File toTagFile(long tag) {
return new File(mOutDir, String.valueOf(tag));
}
/**
* Provides initialization of the clip data storage directory.
*/
static File prepareStorage(File cacheDir) {
File clipDir = getClipDir(cacheDir);
clipDir.mkdir();
assert(clipDir.isDirectory());
return clipDir;
}
public static boolean hasDocList(long tag) {
return tag != NO_SELECTION_TAG;
}
private static File getClipDir(File cacheDir) {
return new File(cacheDir, "clippings");
}
static final class Reader implements Iterable<Uri>, Closeable {
private final Scanner mScanner;
private final FileLock mLock;
private Reader(File file) throws IOException {
FileInputStream inStream = new FileInputStream(file);
// Lock the file here so it won't pass this line until the corresponding writer is done
// writing.
mLock = inStream.getChannel().lock(0L, Long.MAX_VALUE, true);
mScanner = new Scanner(inStream);
}
@Override
public Iterator iterator() {
return new Iterator(mScanner);
}
@Override
public void close() throws IOException {
if (mLock != null) {
mLock.release();
}
if (mScanner != null) {
mScanner.close();
}
}
}
private static final class Iterator implements java.util.Iterator {
private final Scanner mScanner;
private Iterator(Scanner scanner) {
mScanner = scanner;
}
@Override
public boolean hasNext() {
return mScanner.hasNextLine();
}
@Override
public Uri next() {
String line = mScanner.nextLine();
return Uri.parse(line);
}
}
private static final class Writer implements Closeable {
private final FileOutputStream mOut;
private final FileLock mLock;
private Writer(File file) throws IOException {
mOut = new FileOutputStream(file);
// Lock the file here so copy tasks would wait until everything is flushed to disk
// before start to run.
mLock = mOut.getChannel().lock();
}
public void write(Uri uri) throws IOException {
mOut.write(uri.toString().getBytes());
mOut.write(LINE_SEPARATOR);
}
@Override
public void close() throws IOException {
if (mLock != null) {
mLock.release();
}
if (mOut != null) {
mOut.close();
}
}
}
/**
* An {@link AsyncTask} that persists doc uris in {@link ClipStorage}.
*/
static final class PersistTask extends AsyncTask<Void, Void, Void> {
private final ClipStorage mClipStorage;
private final Iterable<Uri> mUris;
private final long mTag;
PersistTask(ClipStorage clipStorage, Iterable<Uri> uris, long tag) {
mClipStorage = clipStorage;
mUris = uris;
mTag = tag;
}
@Override
protected Void doInBackground(Void... params) {
try (ClipStorage.Writer writer = mClipStorage.createWriter(mTag)) {
for (Uri uri: mUris) {
assert(uri != null);
writer.write(uri);
}
} catch (IOException e) {
Log.e(TAG, "Caught exception trying to write jumbo clip to disk.", e);
}
return null;
}
}
}