1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2024-12-23 16:40:32 +00:00

Move Stored(File|Directory)Helper into NewPipe

This commit is contained in:
wb9688 2020-06-14 19:37:45 +02:00 committed by Stypox
parent 0f75024e03
commit 1164ea52f9
No known key found for this signature in database
GPG Key ID: 4BDF1B40A49FDD23
18 changed files with 271 additions and 186 deletions

View File

@ -49,6 +49,8 @@ import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.streams.io.StoredDirectoryHelper;
import org.schabi.newpipe.streams.io.StoredFileHelper;
import org.schabi.newpipe.util.FilePickerActivityHelper; import org.schabi.newpipe.util.FilePickerActivityHelper;
import org.schabi.newpipe.util.FilenameUtils; import org.schabi.newpipe.util.FilenameUtils;
import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.ListHelper;
@ -68,8 +70,6 @@ import icepick.Icepick;
import icepick.State; import icepick.State;
import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import us.shandian.giga.get.MissionRecoveryInfo; import us.shandian.giga.get.MissionRecoveryInfo;
import us.shandian.giga.io.StoredDirectoryHelper;
import us.shandian.giga.io.StoredFileHelper;
import us.shandian.giga.postprocessing.Postprocessing; import us.shandian.giga.postprocessing.Postprocessing;
import us.shandian.giga.service.DownloadManager; import us.shandian.giga.service.DownloadManager;
import us.shandian.giga.service.DownloadManagerService; import us.shandian.giga.service.DownloadManagerService;

View File

@ -57,11 +57,11 @@ import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
import org.schabi.newpipe.streams.io.StoredFileHelper
import org.schabi.newpipe.util.FilePickerActivityHelper import org.schabi.newpipe.util.FilePickerActivityHelper
import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.OnClickGesture import org.schabi.newpipe.util.OnClickGesture
import org.schabi.newpipe.util.ShareUtils import org.schabi.newpipe.util.ShareUtils
import us.shandian.giga.io.StoredFileHelper
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale

View File

@ -34,7 +34,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import icepick.State; import icepick.State;
import us.shandian.giga.io.StoredFileHelper; import org.schabi.newpipe.streams.io.StoredFileHelper;
import static org.schabi.newpipe.extractor.subscription.SubscriptionExtractor.ContentSource.CHANNEL_URL; import static org.schabi.newpipe.extractor.subscription.SubscriptionExtractor.ContentSource.CHANNEL_URL;
import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.CHANNEL_URL_MODE; import static org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.CHANNEL_URL_MODE;

View File

@ -32,6 +32,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity; import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.extractor.subscription.SubscriptionItem; import org.schabi.newpipe.extractor.subscription.SubscriptionItem;
import org.schabi.newpipe.streams.io.SharpOutputStream; import org.schabi.newpipe.streams.io.SharpOutputStream;
import org.schabi.newpipe.streams.io.StoredFileHelper;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@ -41,7 +42,6 @@ import java.util.List;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.functions.Function; import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
import us.shandian.giga.io.StoredFileHelper;
import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.MainActivity.DEBUG;

View File

@ -38,6 +38,7 @@ import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.subscription.SubscriptionItem; import org.schabi.newpipe.extractor.subscription.SubscriptionItem;
import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.streams.io.SharpInputStream; import org.schabi.newpipe.streams.io.SharpInputStream;
import org.schabi.newpipe.streams.io.StoredFileHelper;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
@ -52,10 +53,9 @@ import io.reactivex.rxjava3.core.Notification;
import io.reactivex.rxjava3.functions.Consumer; import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.functions.Function; import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
import us.shandian.giga.io.StoredFileHelper;
import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.MainActivity.DEBUG;
import static us.shandian.giga.io.StoredFileHelper.DEFAULT_MIME; import static org.schabi.newpipe.streams.io.StoredFileHelper.DEFAULT_MIME;
public class SubscriptionsImportService extends BaseImportExportService { public class SubscriptionsImportService extends BaseImportExportService {
public static final int CHANNEL_URL_MODE = 0; public static final int CHANNEL_URL_MODE = 0;

View File

@ -36,8 +36,8 @@ import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
import us.shandian.giga.io.StoredDirectoryHelper; import org.schabi.newpipe.streams.io.StoredDirectoryHelper;
import us.shandian.giga.io.StoredFileHelper; import org.schabi.newpipe.streams.io.StoredFileHelper;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;

View File

@ -26,7 +26,7 @@ import java.net.URI;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import us.shandian.giga.io.StoredDirectoryHelper; import org.schabi.newpipe.streams.io.StoredDirectoryHelper;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;

View File

@ -1,6 +1,5 @@
package us.shandian.giga.io; package org.schabi.newpipe.streams.io;
import android.annotation.TargetApi;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -24,10 +23,11 @@ import java.util.Collections;
import static android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME; import static android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME;
import static android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID; import static android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
public class StoredDirectoryHelper { public class StoredDirectoryHelper {
public final static int PERMISSION_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION; public static final int PERMISSION_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
private File ioTree; private File ioTree;
private DocumentFile docTree; private DocumentFile docTree;
@ -36,7 +36,8 @@ public class StoredDirectoryHelper {
private final String tag; private final String tag;
public StoredDirectoryHelper(@NonNull Context context, @NonNull Uri path, String tag) throws IOException { public StoredDirectoryHelper(@NonNull final Context context, @NonNull final Uri path,
final String tag) throws IOException {
this.tag = tag; this.tag = tag;
if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(path.getScheme())) { if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(path.getScheme())) {
@ -48,61 +49,59 @@ public class StoredDirectoryHelper {
try { try {
this.context.getContentResolver().takePersistableUriPermission(path, PERMISSION_FLAGS); this.context.getContentResolver().takePersistableUriPermission(path, PERMISSION_FLAGS);
} catch (Exception e) { } catch (final Exception e) {
throw new IOException(e); throw new IOException(e);
} }
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
throw new IOException("Storage Access Framework with Directory API is not available"); throw new IOException("Storage Access Framework with Directory API is not available");
}
this.docTree = DocumentFile.fromTreeUri(context, path); this.docTree = DocumentFile.fromTreeUri(context, path);
if (this.docTree == null) if (this.docTree == null) {
throw new IOException("Failed to create the tree from Uri"); throw new IOException("Failed to create the tree from Uri");
} }
@TargetApi(Build.VERSION_CODES.KITKAT)
public StoredDirectoryHelper(@NonNull URI location, String tag) {
ioTree = new File(location);
this.tag = tag;
} }
public StoredFileHelper createFile(String filename, String mime) { public StoredFileHelper createFile(final String filename, final String mime) {
return createFile(filename, mime, false); return createFile(filename, mime, false);
} }
public StoredFileHelper createUniqueFile(String name, String mime) { public StoredFileHelper createUniqueFile(final String name, final String mime) {
ArrayList<String> matches = new ArrayList<>(); final ArrayList<String> matches = new ArrayList<>();
String[] filename = splitFilename(name); final String[] filename = splitFilename(name);
String lcFilename = filename[0].toLowerCase(); final String lcFilename = filename[0].toLowerCase();
if (docTree == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { if (docTree == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
for (File file : ioTree.listFiles()) for (final File file : ioTree.listFiles()) {
addIfStartWith(matches, lcFilename, file.getName()); addIfStartWith(matches, lcFilename, file.getName());
}
} else { } else {
// warning: SAF file listing is very slow // warning: SAF file listing is very slow
Uri docTreeChildren = DocumentsContract.buildChildDocumentsUriUsingTree( final Uri docTreeChildren = DocumentsContract.buildChildDocumentsUriUsingTree(
docTree.getUri(), DocumentsContract.getDocumentId(docTree.getUri()) docTree.getUri(), DocumentsContract.getDocumentId(docTree.getUri()));
);
String[] projection = {COLUMN_DISPLAY_NAME}; final String[] projection = new String[]{COLUMN_DISPLAY_NAME};
String selection = "(LOWER(" + COLUMN_DISPLAY_NAME + ") LIKE ?%"; final String selection = "(LOWER(" + COLUMN_DISPLAY_NAME + ") LIKE ?%";
ContentResolver cr = context.getContentResolver(); final ContentResolver cr = context.getContentResolver();
try (Cursor cursor = cr.query(docTreeChildren, projection, selection, new String[]{lcFilename}, null)) { try (Cursor cursor = cr.query(docTreeChildren, projection, selection,
new String[]{lcFilename}, null)) {
if (cursor != null) { if (cursor != null) {
while (cursor.moveToNext()) while (cursor.moveToNext()) {
addIfStartWith(matches, lcFilename, cursor.getString(0)); addIfStartWith(matches, lcFilename, cursor.getString(0));
} }
} }
} }
}
if (matches.size() < 1) { if (matches.size() < 1) {
return createFile(name, mime, true); return createFile(name, mime, true);
} else { } else {
// check if the filename is in use // check if the filename is in use
String lcName = name.toLowerCase(); String lcName = name.toLowerCase();
for (String testName : matches) { for (final String testName : matches) {
if (testName.equals(lcName)) { if (testName.equals(lcName)) {
lcName = null; lcName = null;
break; break;
@ -110,28 +109,34 @@ public class StoredDirectoryHelper {
} }
// check if not in use // check if not in use
if (lcName != null) return createFile(name, mime, true); if (lcName != null) {
return createFile(name, mime, true);
}
} }
Collections.sort(matches, String::compareTo); Collections.sort(matches, String::compareTo);
for (int i = 1; i < 1000; i++) { for (int i = 1; i < 1000; i++) {
if (Collections.binarySearch(matches, makeFileName(lcFilename, i, filename[1])) < 0) if (Collections.binarySearch(matches, makeFileName(lcFilename, i, filename[1])) < 0) {
return createFile(makeFileName(filename[0], i, filename[1]), mime, true); return createFile(makeFileName(filename[0], i, filename[1]), mime, true);
} }
return createFile(String.valueOf(System.currentTimeMillis()).concat(filename[1]), mime, false);
} }
private StoredFileHelper createFile(String filename, String mime, boolean safe) { return createFile(String.valueOf(System.currentTimeMillis()).concat(filename[1]), mime,
StoredFileHelper storage; false);
}
private StoredFileHelper createFile(final String filename, final String mime,
final boolean safe) {
final StoredFileHelper storage;
try { try {
if (docTree == null) if (docTree == null) {
storage = new StoredFileHelper(ioTree, filename, mime); storage = new StoredFileHelper(ioTree, filename, mime);
else } else {
storage = new StoredFileHelper(context, docTree, filename, mime, safe); storage = new StoredFileHelper(context, docTree, filename, mime, safe);
} catch (IOException e) { }
} catch (final IOException e) {
return null; return null;
} }
@ -149,7 +154,7 @@ public class StoredDirectoryHelper {
} }
/** /**
* Indicates whatever if is possible access using the {@code java.io} API * Indicates whether it's using the {@code java.io} API.
* *
* @return {@code true} for Java I/O API, otherwise, {@code false} for Storage Access Framework * @return {@code true} for Java I/O API, otherwise, {@code false} for Storage Access Framework
*/ */
@ -172,7 +177,9 @@ public class StoredDirectoryHelper {
return ioTree.exists() || ioTree.mkdirs(); return ioTree.exists() || ioTree.mkdirs();
} }
if (docTree.exists()) return true; if (docTree.exists()) {
return true;
}
try { try {
DocumentFile parent; DocumentFile parent;
@ -180,14 +187,18 @@ public class StoredDirectoryHelper {
while (true) { while (true) {
parent = docTree.getParentFile(); parent = docTree.getParentFile();
if (parent == null || child == null) break; if (parent == null || child == null) {
if (parent.exists()) return true; break;
}
if (parent.exists()) {
return true;
}
parent.createDirectory(child); parent.createDirectory(child);
child = parent.getName(); // for the next iteration child = parent.getName(); // for the next iteration
} }
} catch (Exception e) { } catch (final Exception ignored) {
// no more parent directories or unsupported by the storage provider // no more parent directories or unsupported by the storage provider
} }
@ -198,13 +209,13 @@ public class StoredDirectoryHelper {
return tag; return tag;
} }
public Uri findFile(String filename) { public Uri findFile(final String filename) {
if (docTree == null) { if (docTree == null) {
File res = new File(ioTree, filename); final File res = new File(ioTree, filename);
return res.exists() ? Uri.fromFile(res) : null; return res.exists() ? Uri.fromFile(res) : null;
} }
DocumentFile res = findFileSAFHelper(context, docTree, filename); final DocumentFile res = findFileSAFHelper(context, docTree, filename);
return res == null ? null : res.getUri(); return res == null ? null : res.getUri();
} }
@ -218,72 +229,82 @@ public class StoredDirectoryHelper {
return (docTree == null ? Uri.fromFile(ioTree) : docTree.getUri()).toString(); return (docTree == null ? Uri.fromFile(ioTree) : docTree.getUri()).toString();
} }
//////////////////// ////////////////////
// Utils // Utils
/////////////////// ///////////////////
private static void addIfStartWith(ArrayList<String> list, @NonNull String base, String str) { private static void addIfStartWith(final ArrayList<String> list, @NonNull final String base,
if (str == null || str.isEmpty()) return; final String str) {
str = str.toLowerCase(); if (isNullOrEmpty(str)) {
if (str.startsWith(base)) list.add(str); return;
}
final String lowerStr = str.toLowerCase();
if (lowerStr.startsWith(base)) {
list.add(lowerStr);
}
} }
private static String[] splitFilename(@NonNull String filename) { private static String[] splitFilename(@NonNull final String filename) {
int dotIndex = filename.lastIndexOf('.'); final int dotIndex = filename.lastIndexOf('.');
if (dotIndex < 0 || (dotIndex == filename.length() - 1)) if (dotIndex < 0 || (dotIndex == filename.length() - 1)) {
return new String[]{filename, ""}; return new String[]{filename, ""};
}
return new String[]{filename.substring(0, dotIndex), filename.substring(dotIndex)}; return new String[]{filename.substring(0, dotIndex), filename.substring(dotIndex)};
} }
private static String makeFileName(String name, int idx, String ext) { private static String makeFileName(final String name, final int idx, final String ext) {
return name.concat(" (").concat(String.valueOf(idx)).concat(")").concat(ext); return name.concat(" (").concat(String.valueOf(idx)).concat(")").concat(ext);
} }
/** /**
* Fast (but not enough) file/directory finder under the storage access framework * Fast (but not enough) file/directory finder under the storage access framework.
* *
* @param context The context * @param context The context
* @param tree Directory where search * @param tree Directory where search
* @param filename Target filename * @param filename Target filename
* @return A {@link DocumentFile} contain the reference, otherwise, null * @return A {@link DocumentFile} contain the reference, otherwise, null
*/ */
static DocumentFile findFileSAFHelper(@Nullable Context context, DocumentFile tree, String filename) { static DocumentFile findFileSAFHelper(@Nullable final Context context, final DocumentFile tree,
final String filename) {
if (context == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { if (context == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return tree.findFile(filename); // warning: this is very slow return tree.findFile(filename); // warning: this is very slow
} }
if (!tree.canRead()) return null;// missing read permission if (!tree.canRead()) {
return null; // missing read permission
}
final int name = 0; final int name = 0;
final int documentId = 1; final int documentId = 1;
// LOWER() SQL function is not supported // LOWER() SQL function is not supported
String selection = COLUMN_DISPLAY_NAME + " = ?"; final String selection = COLUMN_DISPLAY_NAME + " = ?";
//String selection = COLUMN_DISPLAY_NAME + " LIKE ?%"; //final String selection = COLUMN_DISPLAY_NAME + " LIKE ?%";
Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree( final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(tree.getUri(),
tree.getUri(), DocumentsContract.getDocumentId(tree.getUri()) DocumentsContract.getDocumentId(tree.getUri()));
); final String[] projection = {COLUMN_DISPLAY_NAME, COLUMN_DOCUMENT_ID};
String[] projection = {COLUMN_DISPLAY_NAME, COLUMN_DOCUMENT_ID}; final ContentResolver contentResolver = context.getContentResolver();
ContentResolver contentResolver = context.getContentResolver();
filename = filename.toLowerCase(); final String lowerFilename = filename.toLowerCase();
try (Cursor cursor = contentResolver.query(childrenUri, projection, selection, new String[]{filename}, null)) { try (Cursor cursor = contentResolver.query(childrenUri, projection, selection,
if (cursor == null) return null; new String[]{lowerFilename}, null)) {
if (cursor == null) {
return null;
}
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
if (cursor.isNull(name) || !cursor.getString(name).toLowerCase().startsWith(filename)) if (cursor.isNull(name)
|| !cursor.getString(name).toLowerCase().startsWith(lowerFilename)) {
continue; continue;
}
return DocumentFile.fromSingleUri( return DocumentFile.fromSingleUri(context,
context, DocumentsContract.buildDocumentUriUsingTree( DocumentsContract.buildDocumentUriUsingTree(tree.getUri(),
tree.getUri(), cursor.getString(documentId) cursor.getString(documentId)));
)
);
} }
} }

View File

@ -1,4 +1,4 @@
package us.shandian.giga.io; package org.schabi.newpipe.streams.io;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.ContentResolver; import android.content.ContentResolver;
@ -16,7 +16,6 @@ import androidx.documentfile.provider.DocumentFile;
import com.nononsenseapps.filepicker.Utils; import com.nononsenseapps.filepicker.Utils;
import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.streams.io.SharpStream;
import org.schabi.newpipe.util.FilePickerActivityHelper; import org.schabi.newpipe.util.FilePickerActivityHelper;
import java.io.File; import java.io.File;
@ -24,6 +23,9 @@ import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.net.URI; import java.net.URI;
import us.shandian.giga.io.FileStream;
import us.shandian.giga.io.FileStreamSAF;
public class StoredFileHelper implements Serializable { public class StoredFileHelper implements Serializable {
private static final long serialVersionUID = 0L; private static final long serialVersionUID = 0L;
public static final String DEFAULT_MIME = "application/octet-stream"; public static final String DEFAULT_MIME = "application/octet-stream";
@ -54,27 +56,34 @@ public class StoredFileHelper implements Serializable {
this.srcType = mime; this.srcType = mime;
} }
public StoredFileHelper(@Nullable Uri parent, String filename, String mime, String tag) { public StoredFileHelper(@Nullable final Uri parent, final String filename, final String mime,
final String tag) {
this.source = null; // this instance will be "invalid" see invalidate()/isInvalid() methods this.source = null; // this instance will be "invalid" see invalidate()/isInvalid() methods
this.srcName = filename; this.srcName = filename;
this.srcType = mime == null ? DEFAULT_MIME : mime; this.srcType = mime == null ? DEFAULT_MIME : mime;
if (parent != null) this.sourceTree = parent.toString(); if (parent != null) {
this.sourceTree = parent.toString();
}
this.tag = tag; this.tag = tag;
} }
@TargetApi(Build.VERSION_CODES.LOLLIPOP) @TargetApi(Build.VERSION_CODES.LOLLIPOP)
StoredFileHelper(@Nullable Context context, DocumentFile tree, String filename, String mime, boolean safe) throws IOException { StoredFileHelper(@Nullable final Context context, final DocumentFile tree,
final String filename, final String mime, final boolean safe)
throws IOException {
this.docTree = tree; this.docTree = tree;
this.context = context; this.context = context;
DocumentFile res; final DocumentFile res;
if (safe) { if (safe) {
// no conflicts (the filename is not in use) // no conflicts (the filename is not in use)
res = this.docTree.createFile(mime, filename); res = this.docTree.createFile(mime, filename);
if (res == null) throw new IOException("Cannot create the file"); if (res == null) {
throw new IOException("Cannot create the file");
}
} else { } else {
res = createSAF(context, mime, filename); res = createSAF(context, mime, filename);
} }
@ -88,16 +97,20 @@ public class StoredFileHelper implements Serializable {
this.srcType = this.docFile.getType(); this.srcType = this.docFile.getType();
} }
StoredFileHelper(File location, String filename, String mime) throws IOException { StoredFileHelper(final File location, final String filename, final String mime)
throws IOException {
this.ioFile = new File(location, filename); this.ioFile = new File(location, filename);
if (this.ioFile.exists()) { if (this.ioFile.exists()) {
if (!this.ioFile.isFile() && !this.ioFile.delete()) if (!this.ioFile.isFile() && !this.ioFile.delete()) {
throw new IOException("The filename is already in use by non-file entity and cannot overwrite it"); throw new IOException("The filename is already in use by non-file entity "
+ "and cannot overwrite it");
}
} else { } else {
if (!this.ioFile.createNewFile()) if (!this.ioFile.createNewFile()) {
throw new IOException("Cannot create the file"); throw new IOException("Cannot create the file");
} }
}
this.source = Uri.fromFile(this.ioFile).toString(); this.source = Uri.fromFile(this.ioFile).toString();
this.sourceTree = Uri.fromFile(location).toString(); this.sourceTree = Uri.fromFile(location).toString();
@ -107,16 +120,20 @@ public class StoredFileHelper implements Serializable {
} }
@TargetApi(Build.VERSION_CODES.KITKAT) @TargetApi(Build.VERSION_CODES.KITKAT)
public StoredFileHelper(Context context, @Nullable Uri parent, @NonNull Uri path, String tag) throws IOException { public StoredFileHelper(final Context context, @Nullable final Uri parent,
@NonNull final Uri path, final String tag) throws IOException {
this.tag = tag; this.tag = tag;
this.source = path.toString(); this.source = path.toString();
if (path.getScheme() == null || path.getScheme().equalsIgnoreCase(ContentResolver.SCHEME_FILE)) { if (path.getScheme() == null
|| path.getScheme().equalsIgnoreCase(ContentResolver.SCHEME_FILE)) {
this.ioFile = new File(URI.create(this.source)); this.ioFile = new File(URI.create(this.source));
} else { } else {
DocumentFile file = DocumentFile.fromSingleUri(context, path); final DocumentFile file = DocumentFile.fromSingleUri(context, path);
if (file == null) throw new RuntimeException("SAF not available"); if (file == null) {
throw new RuntimeException("SAF not available");
}
this.context = context; this.context = context;
@ -130,8 +147,9 @@ public class StoredFileHelper implements Serializable {
} }
if (parent != null) { if (parent != null) {
if (!ContentResolver.SCHEME_FILE.equals(parent.getScheme())) if (!ContentResolver.SCHEME_FILE.equals(parent.getScheme())) {
this.docTree = DocumentFile.fromTreeUri(context, parent); this.docTree = DocumentFile.fromTreeUri(context, parent);
}
this.sourceTree = parent.toString(); this.sourceTree = parent.toString();
} }
@ -141,37 +159,45 @@ public class StoredFileHelper implements Serializable {
} }
public static StoredFileHelper deserialize(@NonNull StoredFileHelper storage, Context context) throws IOException { public static StoredFileHelper deserialize(@NonNull final StoredFileHelper storage,
Uri treeUri = storage.sourceTree == null ? null : Uri.parse(storage.sourceTree); final Context context) throws IOException {
final Uri treeUri = storage.sourceTree == null ? null : Uri.parse(storage.sourceTree);
if (storage.isInvalid()) if (storage.isInvalid()) {
return new StoredFileHelper(treeUri, storage.srcName, storage.srcType, storage.tag); return new StoredFileHelper(treeUri, storage.srcName, storage.srcType, storage.tag);
}
StoredFileHelper instance = new StoredFileHelper(context, treeUri, Uri.parse(storage.source), storage.tag); final StoredFileHelper instance = new StoredFileHelper(context, treeUri,
Uri.parse(storage.source), storage.tag);
// under SAF, if the target document is deleted, conserve the filename and mime // under SAF, if the target document is deleted, conserve the filename and mime
if (instance.srcName == null) instance.srcName = storage.srcName; if (instance.srcName == null) {
if (instance.srcType == null) instance.srcType = storage.srcType; instance.srcName = storage.srcName;
}
if (instance.srcType == null) {
instance.srcType = storage.srcType;
}
return instance; return instance;
} }
public SharpStream getStream() throws IOException { public SharpStream getStream() throws IOException {
invalid(); assertValid();
if (docFile == null) if (docFile == null) {
return new FileStream(ioFile); return new FileStream(ioFile);
else } else {
return new FileStreamSAF(context.getContentResolver(), docFile.getUri()); return new FileStreamSAF(context.getContentResolver(), docFile.getUri());
} }
}
/** /**
* Indicates whatever if is possible access using the {@code java.io} API * Indicates whether it's using the {@code java.io} API.
* *
* @return {@code true} for Java I/O API, otherwise, {@code false} for Storage Access Framework * @return {@code true} for Java I/O API, otherwise, {@code false} for Storage Access Framework
*/ */
public boolean isDirect() { public boolean isDirect() {
invalid(); assertValid();
return docFile == null; return docFile == null;
} }
@ -181,19 +207,19 @@ public class StoredFileHelper implements Serializable {
} }
public Uri getUri() { public Uri getUri() {
invalid(); assertValid();
return docFile == null ? Uri.fromFile(ioFile) : docFile.getUri(); return docFile == null ? Uri.fromFile(ioFile) : docFile.getUri();
} }
public Uri getParentUri() { public Uri getParentUri() {
invalid(); assertValid();
return sourceTree == null ? null : Uri.parse(sourceTree); return sourceTree == null ? null : Uri.parse(sourceTree);
} }
public void truncate() throws IOException { public void truncate() throws IOException {
invalid(); assertValid();
try (SharpStream fs = getStream()) { try (SharpStream fs = getStream()) {
fs.setLength(0); fs.setLength(0);
@ -201,16 +227,20 @@ public class StoredFileHelper implements Serializable {
} }
public boolean delete() { public boolean delete() {
if (source == null) return true; if (source == null) {
if (docFile == null) return ioFile.delete(); return true;
}
if (docFile == null) {
return ioFile.delete();
}
final boolean res = docFile.delete();
boolean res = docFile.delete();
try { try {
int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION; final int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
context.getContentResolver().releasePersistableUriPermission(docFile.getUri(), flags); context.getContentResolver().releasePersistableUriPermission(docFile.getUri(), flags);
} catch (Exception ex) { } catch (final Exception ex) {
// nothing to do // nothing to do
} }
@ -218,31 +248,35 @@ public class StoredFileHelper implements Serializable {
} }
public long length() { public long length() {
invalid(); assertValid();
return docFile == null ? ioFile.length() : docFile.length(); return docFile == null ? ioFile.length() : docFile.length();
} }
public boolean canWrite() { public boolean canWrite() {
if (source == null) return false; if (source == null) {
return false;
}
return docFile == null ? ioFile.canWrite() : docFile.canWrite(); return docFile == null ? ioFile.canWrite() : docFile.canWrite();
} }
public String getName() { public String getName() {
if (source == null) if (source == null) {
return srcName; return srcName;
else if (docFile == null) } else if (docFile == null) {
return ioFile.getName(); return ioFile.getName();
}
String name = docFile.getName(); final String name = docFile.getName();
return name == null ? srcName : name; return name == null ? srcName : name;
} }
public String getType() { public String getType() {
if (source == null || docFile == null) if (source == null || docFile == null) {
return srcType; return srcType;
}
String type = docFile.getType(); final String type = docFile.getType();
return type == null ? srcType : type; return type == null ? srcType : type;
} }
@ -251,34 +285,41 @@ public class StoredFileHelper implements Serializable {
} }
public boolean existsAsFile() { public boolean existsAsFile() {
if (source == null) return false; if (source == null) {
return false;
}
// WARNING: DocumentFile.exists() and DocumentFile.isFile() methods are slow // WARNING: DocumentFile.exists() and DocumentFile.isFile() methods are slow
boolean exists = docFile == null ? ioFile.exists() : docFile.exists(); final boolean exists = docFile == null ? ioFile.exists() : docFile.exists();
boolean isFile = docFile == null ? ioFile.isFile() : docFile.isFile();// ¿docFile.isVirtual() means is no-physical? // ¿docFile.isVirtual() means is no-physical?
final boolean isFile = docFile == null ? ioFile.isFile() : docFile.isFile();
return exists && isFile; return exists && isFile;
} }
public boolean create() { public boolean create() {
invalid(); assertValid();
boolean result; final boolean result;
if (docFile == null) { if (docFile == null) {
try { try {
result = ioFile.createNewFile(); result = ioFile.createNewFile();
} catch (IOException e) { } catch (final IOException e) {
return false; return false;
} }
} else if (docTree == null) { } else if (docTree == null) {
result = false; result = false;
} else { } else {
if (!docTree.canRead() || !docTree.canWrite()) return false; if (!docTree.canRead() || !docTree.canWrite()) {
return false;
}
try { try {
docFile = createSAF(context, srcType, srcName); docFile = createSAF(context, srcType, srcName);
if (docFile.getName() == null) return false; if (docFile.getName() == null) {
return false;
}
result = true; result = true;
} catch (IOException e) { } catch (final IOException e) {
return false; return false;
} }
} }
@ -293,7 +334,9 @@ public class StoredFileHelper implements Serializable {
} }
public void invalidate() { public void invalidate() {
if (source == null) return; if (source == null) {
return;
}
srcName = getName(); srcName = getName();
srcType = getType(); srcType = getType();
@ -306,81 +349,102 @@ public class StoredFileHelper implements Serializable {
context = null; context = null;
} }
public boolean equals(StoredFileHelper storage) { public boolean equals(final StoredFileHelper storage) {
if (this == storage) return true; if (this == storage) {
return true;
}
// note: do not compare tags, files can have the same parent folder // note: do not compare tags, files can have the same parent folder
//if (stringMismatch(this.tag, storage.tag)) return false; //if (stringMismatch(this.tag, storage.tag)) return false;
if (stringMismatch(getLowerCase(this.sourceTree), getLowerCase(this.sourceTree))) if (stringMismatch(getLowerCase(this.sourceTree), getLowerCase(this.sourceTree))) {
return false; return false;
if (this.isInvalid() || storage.isInvalid()) {
if (this.srcName == null || storage.srcName == null || this.srcType == null || storage.srcType == null) return false;
return this.srcName.equalsIgnoreCase(storage.srcName) && this.srcType.equalsIgnoreCase(storage.srcType);
} }
if (this.isDirect() != storage.isDirect()) return false; if (this.isInvalid() || storage.isInvalid()) {
if (this.srcName == null || storage.srcName == null || this.srcType == null
|| storage.srcType == null) {
return false;
}
if (this.isDirect()) return this.srcName.equalsIgnoreCase(storage.srcName)
&& this.srcType.equalsIgnoreCase(storage.srcType);
}
if (this.isDirect() != storage.isDirect()) {
return false;
}
if (this.isDirect()) {
return this.ioFile.getPath().equalsIgnoreCase(storage.ioFile.getPath()); return this.ioFile.getPath().equalsIgnoreCase(storage.ioFile.getPath());
}
return DocumentsContract.getDocumentId( return DocumentsContract.getDocumentId(this.docFile.getUri())
this.docFile.getUri() .equalsIgnoreCase(DocumentsContract.getDocumentId(storage.docFile.getUri()));
).equalsIgnoreCase(DocumentsContract.getDocumentId(
storage.docFile.getUri()
));
} }
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
if (source == null) if (source == null) {
return "[Invalid state] name=" + srcName + " type=" + srcType + " tag=" + tag; return "[Invalid state] name=" + srcName + " type=" + srcType + " tag=" + tag;
else } else {
return "sourceFile=" + source + " treeSource=" + (sourceTree == null ? "" : sourceTree) + " tag=" + tag; return "sourceFile=" + source + " treeSource=" + (sourceTree == null ? "" : sourceTree)
+ " tag=" + tag;
}
} }
private void invalid() { private void assertValid() {
if (source == null) if (source == null) {
throw new IllegalStateException("In invalid state"); throw new IllegalStateException("In invalid state");
} }
}
private void takePermissionSAF() throws IOException { private void takePermissionSAF() throws IOException {
try { try {
context.getContentResolver().takePersistableUriPermission(docFile.getUri(), StoredDirectoryHelper.PERMISSION_FLAGS); context.getContentResolver().takePersistableUriPermission(docFile.getUri(),
} catch (Exception e) { StoredDirectoryHelper.PERMISSION_FLAGS);
if (docFile.getName() == null) throw new IOException(e); } catch (final Exception e) {
if (docFile.getName() == null) {
throw new IOException(e);
}
} }
} }
@NonNull @NonNull
private DocumentFile createSAF(@Nullable Context context, String mime, String filename) private DocumentFile createSAF(@Nullable final Context ctx, final String mime,
throws IOException { final String filename) throws IOException {
DocumentFile res = StoredDirectoryHelper.findFileSAFHelper(context, docTree, filename); DocumentFile res = StoredDirectoryHelper.findFileSAFHelper(ctx, docTree, filename);
if (res != null && res.exists() && res.isDirectory()) { if (res != null && res.exists() && res.isDirectory()) {
if (!res.delete()) if (!res.delete()) {
throw new IOException("Directory with the same name found but cannot delete"); throw new IOException("Directory with the same name found but cannot delete");
}
res = null; res = null;
} }
if (res == null) { if (res == null) {
res = this.docTree.createFile(srcType == null ? DEFAULT_MIME : mime, filename); res = this.docTree.createFile(srcType == null ? DEFAULT_MIME : mime, filename);
if (res == null) throw new IOException("Cannot create the file"); if (res == null) {
throw new IOException("Cannot create the file");
}
} }
return res; return res;
} }
private String getLowerCase(String str) { private String getLowerCase(final String str) {
return str == null ? null : str.toLowerCase(); return str == null ? null : str.toLowerCase();
} }
private boolean stringMismatch(String str1, String str2) { private boolean stringMismatch(final String str1, final String str2) {
if (str1 == null && str2 == null) return false; if (str1 == null && str2 == null) {
if ((str1 == null) != (str2 == null)) return true; return false;
}
if ((str1 == null) != (str2 == null)) {
return true;
}
return !str1.equals(str2); return !str1.equals(str2);
} }

View File

@ -11,7 +11,7 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import us.shandian.giga.io.StoredFileHelper; import org.schabi.newpipe.streams.io.StoredFileHelper;
/** /**
* Created by Christian Schabesberger on 28.01.18. * Created by Christian Schabesberger on 28.01.18.

View File

@ -26,7 +26,7 @@ import java.util.Objects;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import us.shandian.giga.io.StoredFileHelper; import org.schabi.newpipe.streams.io.StoredFileHelper;
import us.shandian.giga.postprocessing.Postprocessing; import us.shandian.giga.postprocessing.Postprocessing;
import us.shandian.giga.service.DownloadManagerService; import us.shandian.giga.service.DownloadManagerService;
import us.shandian.giga.util.Utility; import us.shandian.giga.util.Utility;

View File

@ -5,7 +5,7 @@ import androidx.annotation.NonNull;
import java.io.Serializable; import java.io.Serializable;
import java.util.Calendar; import java.util.Calendar;
import us.shandian.giga.io.StoredFileHelper; import org.schabi.newpipe.streams.io.StoredFileHelper;
public abstract class Mission implements Serializable { public abstract class Mission implements Serializable {
private static final long serialVersionUID = 1L;// last bump: 27 march 2019 private static final long serialVersionUID = 1L;// last bump: 27 march 2019
@ -25,6 +25,10 @@ public abstract class Mission implements Serializable {
*/ */
public long timestamp; public long timestamp;
public long getTimestamp() {
return timestamp;
}
/** /**
* pre-defined content type * pre-defined content type
*/ */
@ -35,10 +39,6 @@ public abstract class Mission implements Serializable {
*/ */
public StoredFileHelper storage; public StoredFileHelper storage;
public long getTimestamp() {
return timestamp;
}
/** /**
* Delete the downloaded file * Delete the downloaded file
* *
@ -57,7 +57,7 @@ public abstract class Mission implements Serializable {
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
Calendar calendar = Calendar.getInstance(); final Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timestamp); calendar.setTimeInMillis(timestamp);
return "[" + calendar.getTime().toString() + "] " + (storage.isInvalid() ? storage.getName() : storage.getUri()); return "[" + calendar.getTime().toString() + "] " + (storage.isInvalid() ? storage.getName() : storage.getUri());
} }

View File

@ -17,7 +17,7 @@ import java.util.Objects;
import us.shandian.giga.get.DownloadMission; import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.get.FinishedMission; import us.shandian.giga.get.FinishedMission;
import us.shandian.giga.get.Mission; import us.shandian.giga.get.Mission;
import us.shandian.giga.io.StoredFileHelper; import org.schabi.newpipe.streams.io.StoredFileHelper;
/** /**
* SQLite helper to store finished {@link us.shandian.giga.get.FinishedMission}'s * SQLite helper to store finished {@link us.shandian.giga.get.FinishedMission}'s

View File

@ -19,8 +19,8 @@ import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.get.FinishedMission; import us.shandian.giga.get.FinishedMission;
import us.shandian.giga.get.Mission; import us.shandian.giga.get.Mission;
import us.shandian.giga.get.sqlite.FinishedMissionStore; import us.shandian.giga.get.sqlite.FinishedMissionStore;
import us.shandian.giga.io.StoredDirectoryHelper; import org.schabi.newpipe.streams.io.StoredDirectoryHelper;
import us.shandian.giga.io.StoredFileHelper; import org.schabi.newpipe.streams.io.StoredFileHelper;
import us.shandian.giga.util.Utility; import us.shandian.giga.util.Utility;
import static org.schabi.newpipe.BuildConfig.DEBUG; import static org.schabi.newpipe.BuildConfig.DEBUG;

View File

@ -47,8 +47,8 @@ import java.util.ArrayList;
import us.shandian.giga.get.DownloadMission; import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.get.MissionRecoveryInfo; import us.shandian.giga.get.MissionRecoveryInfo;
import us.shandian.giga.io.StoredDirectoryHelper; import org.schabi.newpipe.streams.io.StoredDirectoryHelper;
import us.shandian.giga.io.StoredFileHelper; import org.schabi.newpipe.streams.io.StoredFileHelper;
import us.shandian.giga.postprocessing.Postprocessing; import us.shandian.giga.postprocessing.Postprocessing;
import us.shandian.giga.service.DownloadManager.NetworkState; import us.shandian.giga.service.DownloadManager.NetworkState;

View File

@ -61,7 +61,7 @@ import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.get.FinishedMission; import us.shandian.giga.get.FinishedMission;
import us.shandian.giga.get.Mission; import us.shandian.giga.get.Mission;
import us.shandian.giga.get.MissionRecoveryInfo; import us.shandian.giga.get.MissionRecoveryInfo;
import us.shandian.giga.io.StoredFileHelper; import org.schabi.newpipe.streams.io.StoredFileHelper;
import us.shandian.giga.service.DownloadManager; import us.shandian.giga.service.DownloadManager;
import us.shandian.giga.service.DownloadManagerService; import us.shandian.giga.service.DownloadManagerService;
import us.shandian.giga.ui.common.Deleter; import us.shandian.giga.ui.common.Deleter;

View File

@ -36,7 +36,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import us.shandian.giga.get.DownloadMission; import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.io.StoredFileHelper; import org.schabi.newpipe.streams.io.StoredFileHelper;
import us.shandian.giga.service.DownloadManager; import us.shandian.giga.service.DownloadManager;
import us.shandian.giga.service.DownloadManagerService; import us.shandian.giga.service.DownloadManagerService;
import us.shandian.giga.service.DownloadManagerService.DownloadManagerBinder; import us.shandian.giga.service.DownloadManagerService.DownloadManagerBinder;

View File

@ -29,7 +29,7 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Locale; import java.util.Locale;
import us.shandian.giga.io.StoredFileHelper; import org.schabi.newpipe.streams.io.StoredFileHelper;
public class Utility { public class Utility {