Implement Storage Access Framework

* re-work finished mission database
* re-work DownloadMission and bump it Serializable version
* keep the classic Java IO API
* SAF Tree API support on Android Lollipop or higher
* add wrapper for SAF stream opening
* implement Closeable in SharpStream to replace the dispose() method

* do required changes for this API:
** remove any file creation logic from DownloadInitializer
** make PostProcessing Serializable and reduce the number of iterations
** update all strings.xml files
** storage helpers: StoredDirectoryHelper & StoredFileHelper
** best effort to handle any kind of SAF errors/exceptions
This commit is contained in:
kapodamy 2019-04-05 14:45:39 -03:00
parent 9e34fee58c
commit f6b32823ba
62 changed files with 2439 additions and 1180 deletions

View file

@ -0,0 +1,140 @@
package us.shandian.giga.io;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.support.annotation.NonNull;
import android.util.Log;
import org.schabi.newpipe.streams.io.SharpStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
public class FileStreamSAF extends SharpStream {
private final FileInputStream in;
private final FileOutputStream out;
private final FileChannel channel;
private final ParcelFileDescriptor file;
private boolean disposed;
public FileStreamSAF(@NonNull ContentResolver contentResolver, Uri fileUri) throws IOException {
// Notes:
// the file must exists first
// ¡read-write mode must allow seek!
// It is not guaranteed to work with files in the cloud (virtual files), tested in local storage devices
file = contentResolver.openFileDescriptor(fileUri, "rw");
if (file == null) {
throw new IOException("Cannot get the ParcelFileDescriptor for " + fileUri.toString());
}
in = new FileInputStream(file.getFileDescriptor());
out = new FileOutputStream(file.getFileDescriptor());
channel = out.getChannel();// or use in.getChannel()
}
@Override
public int read() throws IOException {
return in.read();
}
@Override
public int read(byte[] buffer) throws IOException {
return in.read(buffer);
}
@Override
public int read(byte[] buffer, int offset, int count) throws IOException {
return in.read(buffer, offset, count);
}
@Override
public long skip(long amount) throws IOException {
return in.skip(amount);// ¿or use channel.position(channel.position() + amount)?
}
@Override
public long available() {
try {
return in.available();
} catch (IOException e) {
return 0;// ¡but not -1!
}
}
@Override
public void rewind() throws IOException {
seek(0);
}
@Override
public void close() {
try {
disposed = true;
file.close();
in.close();
out.close();
channel.close();
} catch (IOException e) {
Log.e("FileStreamSAF", "close() error", e);
}
}
@Override
public boolean isClosed() {
return disposed;
}
@Override
public boolean canRewind() {
return true;
}
@Override
public boolean canRead() {
return true;
}
@Override
public boolean canWrite() {
return true;
}
public boolean canSetLength() {
return true;
}
public boolean canSeek() {
return true;
}
@Override
public void write(byte value) throws IOException {
out.write(value);
}
@Override
public void write(byte[] buffer) throws IOException {
out.write(buffer);
}
@Override
public void write(byte[] buffer, int offset, int count) throws IOException {
out.write(buffer, offset, count);
}
public void setLength(long length) throws IOException {
channel.truncate(length);
}
public void seek(long offset) throws IOException {
channel.position(offset);
}
}