mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-14 18:12:09 +08:00
feat(file-system): append, appendText & createFile (#10285)
This commit is contained in:
@ -1,6 +1,9 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.nativescript.widgets">
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true">
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
package org.nativescript.widgets;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
@ -599,19 +601,87 @@ public class Async {
|
||||
|
||||
public static class File {
|
||||
|
||||
static void updateValue(Context context, Uri uri) {
|
||||
ContentValues values = new ContentValues();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
context.getContentResolver().update(uri, values, null);
|
||||
} else {
|
||||
context.getContentResolver().update(uri, values, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void append(final String path, final byte[] content, final CompleteCallback callback, final Object context) {
|
||||
final android.os.Handler mHandler = new android.os.Handler(Looper.myLooper());
|
||||
threadPoolExecutor().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final AppendTask task = new AppendTask(callback, context);
|
||||
final boolean result = task.doInBackground(path, content);
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
task.onPostExecute(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void appendBuffer(final String path, final ByteBuffer content, final CompleteCallback callback, final Object context) {
|
||||
final android.os.Handler mHandler = new android.os.Handler(Looper.myLooper());
|
||||
threadPoolExecutor().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final AppendBufferTask task = new AppendBufferTask(callback, context);
|
||||
final boolean result = task.doInBackground(path, content);
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
task.onPostExecute(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void appendText(final String path, final String content, final String encoding, final CompleteCallback callback, final Object context) {
|
||||
final android.os.Handler mHandler = new android.os.Handler(Looper.myLooper());
|
||||
threadPoolExecutor().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final AppendTextTask task = new AppendTextTask(callback, context);
|
||||
final boolean result = task.doInBackground(path, content, encoding);
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
task.onPostExecute(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean copySync(final String src, final String dest, final Context context) throws Exception {
|
||||
InputStream is;
|
||||
OutputStream os;
|
||||
|
||||
boolean requiresUpdate = false;
|
||||
if(src.startsWith("content://")){
|
||||
is = context.getContentResolver().openInputStream(Uri.parse(src));
|
||||
}else is = new FileInputStream(new java.io.File(src));
|
||||
|
||||
if(dest.startsWith("content://")){
|
||||
os = context.getContentResolver().openOutputStream(Uri.parse(dest));
|
||||
requiresUpdate = true;
|
||||
}else os = new FileOutputStream(new java.io.File(dest));
|
||||
|
||||
return copySync(is, os, context);
|
||||
boolean ret = copySync(is, os, context);
|
||||
|
||||
if(ret && requiresUpdate){
|
||||
updateValue(context, Uri.parse(dest));
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
public static boolean copySync(final InputStream src, final OutputStream dest, final Object context) throws Exception {
|
||||
@ -630,15 +700,33 @@ public class Async {
|
||||
InputStream is;
|
||||
OutputStream os;
|
||||
|
||||
boolean requiresUpdate = false;
|
||||
|
||||
if(src.startsWith("content://")){
|
||||
is = context.getContentResolver().openInputStream(Uri.parse(src));
|
||||
}else is = new FileInputStream(new java.io.File(src));
|
||||
|
||||
if(dest.startsWith("content://")){
|
||||
requiresUpdate = true;
|
||||
os = context.getContentResolver().openOutputStream(Uri.parse(dest));
|
||||
}else os = new FileOutputStream(new java.io.File(dest));
|
||||
|
||||
copy(is, os, callback, context);
|
||||
boolean finalRequiresUpdate = requiresUpdate;
|
||||
copy(is, os, new CompleteCallback() {
|
||||
@Override
|
||||
public void onComplete(Object result, Object tag) {
|
||||
if(finalRequiresUpdate){
|
||||
updateValue(context, Uri.parse(dest));
|
||||
}
|
||||
callback.onComplete(result, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error, Object tag) {
|
||||
callback.onError(error, tag);
|
||||
}
|
||||
}, context);
|
||||
|
||||
}catch (Exception exception){
|
||||
callback.onError(exception.getMessage(), context);
|
||||
}
|
||||
@ -666,11 +754,12 @@ public class Async {
|
||||
return written;
|
||||
}
|
||||
|
||||
|
||||
public static void copy(final InputStream src, final OutputStream dest, final CompleteCallback callback, final Object context) {
|
||||
final android.os.Handler mHandler = new android.os.Handler(Looper.myLooper());
|
||||
threadPoolExecutor().execute((Runnable) () -> {
|
||||
|
||||
boolean done = false;
|
||||
Exception error = null;
|
||||
try (InputStream is = src; OutputStream os = dest){
|
||||
ReadableByteChannel isc = java.nio.channels.Channels.newChannel(is);
|
||||
WritableByteChannel osc = java.nio.channels.Channels.newChannel(os);
|
||||
@ -679,10 +768,19 @@ public class Async {
|
||||
|
||||
int written = fastChannelCopy(isc, osc);
|
||||
|
||||
mHandler.post(() -> callback.onComplete(size == written, context));
|
||||
done = size == written;
|
||||
|
||||
} catch (Exception e) {
|
||||
mHandler.post(() -> callback.onError(e.getMessage(), context));
|
||||
error = e;
|
||||
|
||||
}finally {
|
||||
if (error != null){
|
||||
Exception finalError = error;
|
||||
mHandler.post(() -> callback.onError(finalError.getMessage(), context));
|
||||
}else {
|
||||
boolean finalDone = done;
|
||||
mHandler.post(() -> callback.onComplete(finalDone, context));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1086,5 +1184,146 @@ public class Async {
|
||||
}
|
||||
}
|
||||
|
||||
static class AppendTask {
|
||||
private final CompleteCallback callback;
|
||||
private final Object context;
|
||||
|
||||
public AppendTask(CompleteCallback callback, Object context) {
|
||||
this.callback = callback;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
protected boolean doInBackground(Object... params) {
|
||||
java.io.File javaFile = new java.io.File((String) params[0]);
|
||||
FileOutputStream stream = null;
|
||||
byte[] content = (byte[]) params[1];
|
||||
|
||||
try {
|
||||
stream = new FileOutputStream(javaFile, true);
|
||||
stream.write(content, 0, content.length);
|
||||
|
||||
return true;
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(TAG, "Failed to append file, FileNotFoundException: " + e.getMessage());
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to write file, IOException: " + e.getMessage());
|
||||
return false;
|
||||
} finally {
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to close stream, IOException: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onPostExecute(final boolean result) {
|
||||
if (result) {
|
||||
this.callback.onComplete(null, this.context);
|
||||
} else {
|
||||
this.callback.onError("AppendTask returns no result.", this.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class AppendBufferTask {
|
||||
private final CompleteCallback callback;
|
||||
private final Object context;
|
||||
|
||||
public AppendBufferTask(CompleteCallback callback, Object context) {
|
||||
this.callback = callback;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
protected boolean doInBackground(Object... params) {
|
||||
java.io.File javaFile = new java.io.File((String) params[0]);
|
||||
FileOutputStream stream = null;
|
||||
ByteBuffer content = (ByteBuffer) params[1];
|
||||
|
||||
try {
|
||||
stream = new FileOutputStream(javaFile, true);
|
||||
FileChannel channel = stream.getChannel();
|
||||
content.rewind();
|
||||
channel.write(content);
|
||||
content.rewind();
|
||||
return true;
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(TAG, "Failed to append to file, FileNotFoundException: " + e.getMessage());
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to append file, IOException: " + e.getMessage());
|
||||
return false;
|
||||
} finally {
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to close stream, IOException: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onPostExecute(final boolean result) {
|
||||
if (result) {
|
||||
this.callback.onComplete(null, this.context);
|
||||
} else {
|
||||
this.callback.onError("AppendTask returns no result.", this.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class AppendTextTask {
|
||||
private final CompleteCallback callback;
|
||||
private final Object context;
|
||||
|
||||
public AppendTextTask(CompleteCallback callback, Object context) {
|
||||
this.callback = callback;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
protected boolean doInBackground(String... params) {
|
||||
java.io.File javaFile = new java.io.File(params[0]);
|
||||
FileOutputStream stream = null;
|
||||
try {
|
||||
stream = new FileOutputStream(javaFile, true);
|
||||
|
||||
OutputStreamWriter writer = new OutputStreamWriter(stream, params[2]);
|
||||
writer.write(params[1]);
|
||||
writer.close();
|
||||
|
||||
return true;
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(TAG, "Failed to append file, FileNotFoundException: " + e.getMessage());
|
||||
return false;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.e(TAG, "Failed to append file, UnsupportedEncodingException: " + e.getMessage());
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to append file, IOException: " + e.getMessage());
|
||||
return false;
|
||||
} finally {
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to close stream, IOException: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onPostExecute(final boolean result) {
|
||||
if (result) {
|
||||
this.callback.onComplete(null, this.context);
|
||||
} else {
|
||||
this.callback.onError("AppendTextTask returns no result.", this.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,8 @@
|
||||
D004031022F781A50089EAD8 /* NSString+Async.m in Sources */ = {isa = PBXBuildFile; fileRef = D004030E22F781A50089EAD8 /* NSString+Async.m */; };
|
||||
D004031322FA27D60089EAD8 /* NSData+Async.h in Headers */ = {isa = PBXBuildFile; fileRef = D004031122FA27D60089EAD8 /* NSData+Async.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D004031422FA27D60089EAD8 /* NSData+Async.m in Sources */ = {isa = PBXBuildFile; fileRef = D004031222FA27D60089EAD8 /* NSData+Async.m */; };
|
||||
F11DE2162A02F39A00B70DC5 /* NSFileHandle+Async.m in Sources */ = {isa = PBXBuildFile; fileRef = F11DE2152A02F39A00B70DC5 /* NSFileHandle+Async.m */; };
|
||||
F11DE2182A02F3B500B70DC5 /* NSFileHandle+Async.h in Headers */ = {isa = PBXBuildFile; fileRef = F11DE2172A02F3B500B70DC5 /* NSFileHandle+Async.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
F915D3551EC9EF5E00071914 /* TNSProcess.m in Sources */ = {isa = PBXBuildFile; fileRef = F915D3531EC9EF5E00071914 /* TNSProcess.m */; };
|
||||
F915D3561EC9EF5E00071914 /* TNSProcess.h in Headers */ = {isa = PBXBuildFile; fileRef = F915D3541EC9EF5E00071914 /* TNSProcess.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
F98F5CB31CD0EFEA00978308 /* TNSWidgets.h in Headers */ = {isa = PBXBuildFile; fileRef = F98F5CB21CD0EFEA00978308 /* TNSWidgets.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@ -51,6 +53,8 @@
|
||||
D004030E22F781A50089EAD8 /* NSString+Async.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+Async.m"; sourceTree = "<group>"; };
|
||||
D004031122FA27D60089EAD8 /* NSData+Async.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSData+Async.h"; sourceTree = "<group>"; };
|
||||
D004031222FA27D60089EAD8 /* NSData+Async.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSData+Async.m"; sourceTree = "<group>"; };
|
||||
F11DE2152A02F39A00B70DC5 /* NSFileHandle+Async.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSFileHandle+Async.m"; sourceTree = "<group>"; };
|
||||
F11DE2172A02F3B500B70DC5 /* NSFileHandle+Async.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSFileHandle+Async.h"; sourceTree = "<group>"; };
|
||||
F915D3531EC9EF5E00071914 /* TNSProcess.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TNSProcess.m; path = ../TNSProcess.m; sourceTree = "<group>"; };
|
||||
F915D3541EC9EF5E00071914 /* TNSProcess.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TNSProcess.h; path = ../TNSProcess.h; sourceTree = "<group>"; };
|
||||
F98F5CAF1CD0EFEA00978308 /* TNSWidgets.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TNSWidgets.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@ -121,6 +125,8 @@
|
||||
B8E76F5D212C3134009CFCE2 /* UIView+PassThroughParent.m */,
|
||||
D004031122FA27D60089EAD8 /* NSData+Async.h */,
|
||||
D004031222FA27D60089EAD8 /* NSData+Async.m */,
|
||||
F11DE2172A02F3B500B70DC5 /* NSFileHandle+Async.h */,
|
||||
F11DE2152A02F39A00B70DC5 /* NSFileHandle+Async.m */,
|
||||
);
|
||||
path = TNSWidgets;
|
||||
sourceTree = "<group>";
|
||||
@ -147,6 +153,7 @@
|
||||
D004031322FA27D60089EAD8 /* NSData+Async.h in Headers */,
|
||||
F98F5CCB1CD0F09E00978308 /* UIImage+TNSBlocks.h in Headers */,
|
||||
B8E76F52212C2DA2009CFCE2 /* NSObject+Swizzling.h in Headers */,
|
||||
F11DE2182A02F3B500B70DC5 /* NSFileHandle+Async.h in Headers */,
|
||||
B8E76F5A212C2F4E009CFCE2 /* UIView+PropertyBag.h in Headers */,
|
||||
D004030F22F781A50089EAD8 /* NSString+Async.h in Headers */,
|
||||
8B7321CF1D097ECD00884AC6 /* TNSLabel.h in Headers */,
|
||||
@ -252,6 +259,7 @@
|
||||
files = (
|
||||
8B7321D01D097ECD00884AC6 /* TNSLabel.m in Sources */,
|
||||
F915D3551EC9EF5E00071914 /* TNSProcess.m in Sources */,
|
||||
F11DE2162A02F39A00B70DC5 /* NSFileHandle+Async.m in Sources */,
|
||||
F98F5CCC1CD0F09E00978308 /* UIImage+TNSBlocks.m in Sources */,
|
||||
B8E76F53212C2DA2009CFCE2 /* NSObject+Swizzling.m in Sources */,
|
||||
D004031022F781A50089EAD8 /* NSString+Async.m in Sources */,
|
||||
|
@ -0,0 +1,22 @@
|
||||
//
|
||||
// NSFileHandle+Async.h
|
||||
// TNSWidgets
|
||||
//
|
||||
// Created by Osei Fortune on 03/05/2023.
|
||||
// Copyright © 2023 Telerik A D. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSFileHandle (Async)
|
||||
|
||||
- (void)appendData:(nonnull NSData*) data
|
||||
completion:(void (^) (NSError*))callback;
|
||||
|
||||
+ (void)fileHandleWith:(NSString *)path data:(NSData *)data completion:(void (^)(NSFileHandle*,NSError*))callback;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -0,0 +1,82 @@
|
||||
//
|
||||
// NSFileHandle+Async.m
|
||||
// TNSWidgets
|
||||
//
|
||||
// Created by Osei Fortune on 03/05/2023.
|
||||
// Copyright © 2023 Telerik A D. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSFileHandle+Async.h"
|
||||
|
||||
@implementation NSFileHandle(Async)
|
||||
|
||||
- (void)appendData:(NSData *)data completion:(void (^)(NSError*))callback {
|
||||
dispatch_queue_t asyncQueue = dispatch_queue_create("org.nativescript.TNSWidgets.fileHandle", NULL);
|
||||
dispatch_async(asyncQueue, ^(void) {
|
||||
NSError *error = nil;
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
[self seekToEndReturningOffset:nil error:&error];
|
||||
[self writeData:data error:&error];
|
||||
} else {
|
||||
@try {
|
||||
[self seekToEndOfFile];
|
||||
[self writeData:data];
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
NSMutableDictionary * info = [NSMutableDictionary dictionary];
|
||||
[info setValue:exception.name forKey:@"ExceptionName"];
|
||||
[info setValue:exception.reason forKey:@"ExceptionReason"];
|
||||
[info setValue:exception.callStackReturnAddresses forKey:@"ExceptionCallStackReturnAddresses"];
|
||||
[info setValue:exception.callStackSymbols forKey:@"ExceptionCallStackSymbols"];
|
||||
[info setValue:exception.userInfo forKey:@"ExceptionUserInfo"];
|
||||
|
||||
error = [[NSError alloc] initWithDomain:@"" code: 1 userInfo:info];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^(void) {
|
||||
callback(error);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
+ (void)fileHandleWith:(NSString *)path data:(NSData *)data completion:(void (^)(NSFileHandle*, NSError*))callback {
|
||||
dispatch_queue_t asyncQueue = dispatch_queue_create("org.nativescript.TNSWidgets.fileHandle", NULL);
|
||||
dispatch_async(asyncQueue, ^(void) {
|
||||
NSFileHandle* handle = [NSFileHandle fileHandleForWritingAtPath: path];
|
||||
NSError *error = nil;
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
[handle seekToEndReturningOffset:nil error:&error];
|
||||
[handle writeData:data error:&error];
|
||||
} else {
|
||||
@try {
|
||||
[handle seekToEndOfFile];
|
||||
[handle writeData:data];
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
NSMutableDictionary * info = [NSMutableDictionary dictionary];
|
||||
[info setValue:exception.name forKey:@"ExceptionName"];
|
||||
[info setValue:exception.reason forKey:@"ExceptionReason"];
|
||||
[info setValue:exception.callStackReturnAddresses forKey:@"ExceptionCallStackReturnAddresses"];
|
||||
[info setValue:exception.callStackSymbols forKey:@"ExceptionCallStackSymbols"];
|
||||
[info setValue:exception.userInfo forKey:@"ExceptionUserInfo"];
|
||||
|
||||
error = [[NSError alloc] initWithDomain:@"" code: 1 userInfo:info];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^(void) {
|
||||
callback(handle,error);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
@ -22,3 +22,4 @@ FOUNDATION_EXPORT const unsigned char TNSWidgetsVersionString[];
|
||||
#import "TNSProcess.h"
|
||||
#import "NSString+Async.h"
|
||||
#import "NSData+Async.h"
|
||||
#import "NSFileHandle+Async.h"
|
||||
|
Reference in New Issue
Block a user