diff --git a/tests/app/file-system/file-system-tests.ts b/tests/app/file-system/file-system-tests.ts index 2f19fcd82..cd13d34cd 100644 --- a/tests/app/file-system/file-system-tests.ts +++ b/tests/app/file-system/file-system-tests.ts @@ -201,6 +201,46 @@ export var testFileReadWriteBinary = function () { // << file-system-read-binary }; +export var testFileReadWriteBinaryAsync = function () { + // >> file-system-read-binary-async + var fileName = "logo.png"; + + var sourceFile = fs.File.fromPath(__dirname + "/assets/" + fileName); + var destinationFile = fs.knownFolders.documents().getFile(fileName); + + // Read the file + sourceFile.read() + .then(function (source) { + // Succeeded in reading the file + // >> (hide) + destinationFile.write(source).then(function () { + // Succeded in writing the file + destinationFile.read() + .then(function (destination) { + if (platform.device.os === platform.platformNames.ios) { + TKUnit.assertTrue(source.isEqualToData(destination)); + } else { + TKUnit.assertEqual(new java.io.File(sourceFile.path).length(), new java.io.File(destinationFile.path).length()); + } + + destinationFile.removeSync(); + }, function (error) { + TKUnit.assert(false, "Failed to read destination binary async"); + }); + }, function (error) { + // Failed to write the file. + TKUnit.assert(false, "Failed to write binary async"); + }); + // << (hide) + }, function (error) { + // Failed to read the file. + // >> (hide) + TKUnit.assert(false, "Failed to read binary async"); + // << (hide) + }); + // << file-system-read-binary-async +}; + export var testGetKnownFolders = function () { // >> file-system-known-folders // Getting the application's 'documents' folder. diff --git a/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/Async.java b/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/Async.java index bc1eb8ec1..265a905ce 100644 --- a/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/Async.java +++ b/tns-core-modules-widgets/android/widgets/src/main/java/org/nativescript/widgets/Async.java @@ -11,15 +11,22 @@ import android.util.Log; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.Closeable; +import java.io.DataInputStream; +import java.io.FileOutputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; import java.net.CookieHandler; import java.net.CookieManager; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; +import java.nio.CharBuffer; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -585,4 +592,278 @@ public class Async { } } } + + public static class File { + + public static void readText(final String path, final String encoding, final CompleteCallback callback, final Object context) { + final android.os.Handler mHandler = new android.os.Handler(); + threadPoolExecutor().execute(new Runnable() { + @Override + public void run() { + final ReadTextTask task = new ReadTextTask(callback, context); + final String result = task.doInBackground(path, encoding); + mHandler.post(new Runnable() { + @Override + public void run() { + task.onPostExecute(result); + } + }); + } + }); + } + + public static void read(final String path, final CompleteCallback callback, final Object context) { + final android.os.Handler mHandler = new android.os.Handler(); + threadPoolExecutor().execute(new Runnable() { + @Override + public void run() { + final ReadTask task = new ReadTask(callback, context); + final byte[] result = task.doInBackground(path); + mHandler.post(new Runnable() { + @Override + public void run() { + task.onPostExecute(result); + } + }); + } + }); + } + + public static void writeText(final String path, final String content, final String encoding, final CompleteCallback callback, final Object context) { + final android.os.Handler mHandler = new android.os.Handler(); + threadPoolExecutor().execute(new Runnable() { + @Override + public void run() { + final WriteTextTask task = new WriteTextTask(callback, context); + final boolean result = task.doInBackground(path, content, encoding); + mHandler.post(new Runnable() { + @Override + public void run() { + task.onPostExecute(result); + } + }); + } + }); + } + + public static void write(final String path, final byte[] content, final CompleteCallback callback, final Object context) { + final android.os.Handler mHandler = new android.os.Handler(); + threadPoolExecutor().execute(new Runnable() { + @Override + public void run() { + final WriteTask task = new WriteTask(callback, context); + final boolean result = task.doInBackground(path, content); + mHandler.post(new Runnable() { + @Override + public void run() { + task.onPostExecute(result); + } + }); + } + }); + } + + static class ReadTextTask { + private CompleteCallback callback; + private Object context; + + public ReadTextTask(CompleteCallback callback, Object context) { + this.callback = callback; + this.context = context; + } + + protected String doInBackground(String... params) { + java.io.File javaFile = new java.io.File(params[0]); + FileInputStream stream = null; + + try { + stream = new FileInputStream(javaFile); + + InputStreamReader reader = new InputStreamReader(stream, params[1]); + + CharBuffer buffer = CharBuffer.allocate(81920); + StringBuilder sb = new StringBuilder(); + + while (reader.read(buffer) != -1) { + buffer.flip(); + sb.append(buffer); + buffer.clear(); + } + + reader.close(); + + return sb.toString(); + } catch (FileNotFoundException e) { + Log.e(TAG, "Failed to read file, FileNotFoundException: " + e.getMessage()); + return null; + } catch (UnsupportedEncodingException e) { + Log.e(TAG, "Failed to read file, UnsupportedEncodingException: " + e.getMessage()); + return null; + } catch (IOException e) { + Log.e(TAG, "Failed to read file, IOException: " + e.getMessage()); + return null; + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + Log.e(TAG, "Failed to close stream, IOException: " + e.getMessage()); + } + } + } + } + + protected void onPostExecute(final String result) { + if (result != null) { + this.callback.onComplete(result, this.context); + } else { + this.callback.onError("ReadTextTask returns no result.", this.context); + } + } + } + + static class ReadTask { + private CompleteCallback callback; + private Object context; + + public ReadTask(CompleteCallback callback, Object context) { + this.callback = callback; + this.context = context; + } + + protected byte[] doInBackground(String... params) { + java.io.File javaFile = new java.io.File(params[0]); + FileInputStream stream = null; + + try { + stream = new FileInputStream(javaFile); + + byte[] result = new byte[(int)javaFile.length()]; + + DataInputStream dataInputStream = new DataInputStream(stream); + dataInputStream.readFully(result); + + return result; + } catch (FileNotFoundException e) { + Log.e(TAG, "Failed to read file, FileNotFoundException: " + e.getMessage()); + return null; + } catch (IOException e) { + Log.e(TAG, "Failed to read file, IOException: " + e.getMessage()); + return null; + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + Log.e(TAG, "Failed to close stream, IOException: " + e.getMessage()); + } + } + } + } + + protected void onPostExecute(final byte[] result) { + if (result != null) { + this.callback.onComplete(result, this.context); + } else { + this.callback.onError("ReadTask returns no result.", this.context); + } + } + } + + static class WriteTextTask { + private CompleteCallback callback; + private Object context; + + public WriteTextTask(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); + + OutputStreamWriter writer = new OutputStreamWriter(stream, params[2]); + + writer.write(params[1]); + writer.close(); + + return true; + } catch (FileNotFoundException e) { + Log.e(TAG, "Failed to write file, FileNotFoundException: " + e.getMessage()); + return false; + } catch (UnsupportedEncodingException e) { + Log.e(TAG, "Failed to write file, UnsupportedEncodingException: " + 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("WriteTextTask returns no result.", this.context); + } + } + } + + static class WriteTask { + private CompleteCallback callback; + private Object context; + + public WriteTask(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); + stream.write(content, 0, content.length); + + return true; + } catch (FileNotFoundException e) { + Log.e(TAG, "Failed to write 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("WriteTask returns no result.", this.context); + } + } + } + + } } diff --git a/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets.xcodeproj/project.pbxproj b/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets.xcodeproj/project.pbxproj index 05f34d003..05bb798cd 100644 --- a/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets.xcodeproj/project.pbxproj +++ b/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets.xcodeproj/project.pbxproj @@ -15,6 +15,10 @@ B8E76F5B212C2F4E009CFCE2 /* UIView+PropertyBag.m in Sources */ = {isa = PBXBuildFile; fileRef = B8E76F59212C2F4E009CFCE2 /* UIView+PropertyBag.m */; }; B8E76F5E212C3134009CFCE2 /* UIView+PassThroughParent.h in Headers */ = {isa = PBXBuildFile; fileRef = B8E76F5C212C3134009CFCE2 /* UIView+PassThroughParent.h */; settings = {ATTRIBUTES = (Public, ); }; }; B8E76F5F212C3134009CFCE2 /* UIView+PassThroughParent.m in Sources */ = {isa = PBXBuildFile; fileRef = B8E76F5D212C3134009CFCE2 /* UIView+PassThroughParent.m */; }; + D004030F22F781A50089EAD8 /* NSString+Async.h in Headers */ = {isa = PBXBuildFile; fileRef = D004030D22F781A50089EAD8 /* NSString+Async.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 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 */; }; 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, ); }; }; @@ -43,6 +47,10 @@ B8E76F59212C2F4E009CFCE2 /* UIView+PropertyBag.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+PropertyBag.m"; sourceTree = ""; }; B8E76F5C212C3134009CFCE2 /* UIView+PassThroughParent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+PassThroughParent.h"; sourceTree = ""; }; B8E76F5D212C3134009CFCE2 /* UIView+PassThroughParent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+PassThroughParent.m"; sourceTree = ""; }; + D004030D22F781A50089EAD8 /* NSString+Async.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+Async.h"; sourceTree = ""; }; + D004030E22F781A50089EAD8 /* NSString+Async.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+Async.m"; sourceTree = ""; }; + D004031122FA27D60089EAD8 /* NSData+Async.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSData+Async.h"; sourceTree = ""; }; + D004031222FA27D60089EAD8 /* NSData+Async.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSData+Async.m"; sourceTree = ""; }; F915D3531EC9EF5E00071914 /* TNSProcess.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TNSProcess.m; path = ../TNSProcess.m; sourceTree = ""; }; F915D3541EC9EF5E00071914 /* TNSProcess.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TNSProcess.h; path = ../TNSProcess.h; sourceTree = ""; }; F98F5CAF1CD0EFEA00978308 /* TNSWidgets.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TNSWidgets.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -100,6 +108,8 @@ F98F5CB21CD0EFEA00978308 /* TNSWidgets.h */, 8B7321CD1D097ECD00884AC6 /* TNSLabel.h */, 8B7321CE1D097ECD00884AC6 /* TNSLabel.m */, + D004030D22F781A50089EAD8 /* NSString+Async.h */, + D004030E22F781A50089EAD8 /* NSString+Async.m */, F98F5CC91CD0F09E00978308 /* UIImage+TNSBlocks.h */, F98F5CCA1CD0F09E00978308 /* UIImage+TNSBlocks.m */, F98F5CB41CD0EFEA00978308 /* Info.plist */, @@ -109,6 +119,8 @@ B8E76F59212C2F4E009CFCE2 /* UIView+PropertyBag.m */, B8E76F5C212C3134009CFCE2 /* UIView+PassThroughParent.h */, B8E76F5D212C3134009CFCE2 /* UIView+PassThroughParent.m */, + D004031122FA27D60089EAD8 /* NSData+Async.h */, + D004031222FA27D60089EAD8 /* NSData+Async.m */, ); path = TNSWidgets; sourceTree = ""; @@ -132,9 +144,11 @@ F915D3561EC9EF5E00071914 /* TNSProcess.h in Headers */, F98F5CB31CD0EFEA00978308 /* TNSWidgets.h in Headers */, B8E76F5E212C3134009CFCE2 /* UIView+PassThroughParent.h in Headers */, + D004031322FA27D60089EAD8 /* NSData+Async.h in Headers */, F98F5CCB1CD0F09E00978308 /* UIImage+TNSBlocks.h in Headers */, B8E76F52212C2DA2009CFCE2 /* NSObject+Swizzling.h in Headers */, B8E76F5A212C2F4E009CFCE2 /* UIView+PropertyBag.h in Headers */, + D004030F22F781A50089EAD8 /* NSString+Async.h in Headers */, 8B7321CF1D097ECD00884AC6 /* TNSLabel.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -200,6 +214,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = F98F5CA51CD0EFEA00978308; @@ -239,6 +254,8 @@ F915D3551EC9EF5E00071914 /* TNSProcess.m in Sources */, F98F5CCC1CD0F09E00978308 /* UIImage+TNSBlocks.m in Sources */, B8E76F53212C2DA2009CFCE2 /* NSObject+Swizzling.m in Sources */, + D004031022F781A50089EAD8 /* NSString+Async.m in Sources */, + D004031422FA27D60089EAD8 /* NSData+Async.m in Sources */, B8E76F5B212C2F4E009CFCE2 /* UIView+PropertyBag.m in Sources */, B8E76F5F212C3134009CFCE2 /* UIView+PassThroughParent.m in Sources */, ); diff --git a/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets/NSData+Async.h b/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets/NSData+Async.h new file mode 100644 index 000000000..36508cc54 --- /dev/null +++ b/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets/NSData+Async.h @@ -0,0 +1,24 @@ +// +// NSData+Async.h +// TNSWidgets +// +// Created by Peter Staev on 7.08.19. +// Copyright © 2019 Telerik A D. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSData (Async) + ++ (void)dataWithContentsOfFile:(nonnull NSString*)path + completion:(void (^) (NSData*))callback; + +- (void)writeToFile:(nonnull NSString*) path + atomically:(BOOL)atomically + completion:(void (^) ())callback; + +@end + +NS_ASSUME_NONNULL_END diff --git a/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets/NSData+Async.m b/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets/NSData+Async.m new file mode 100644 index 000000000..2f6b0a37b --- /dev/null +++ b/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets/NSData+Async.m @@ -0,0 +1,42 @@ +// +// NSData+Async.m +// TNSWidgets +// +// Created by Peter Staev on 7.08.19. +// Copyright © 2019 Telerik A D. All rights reserved. +// + +#import "NSData+Async.h" + +@implementation NSData (Async) + ++ (void)dataWithContentsOfFile:(nonnull NSString*)path + completion:(void (^) (NSData*))callback { + + dispatch_queue_t asyncQueue = dispatch_queue_create("org.nativescript.TNSWidgets.data", NULL); + dispatch_async(asyncQueue, ^(void) { + NSData *output = [NSData dataWithContentsOfFile:path]; + dispatch_async(dispatch_get_main_queue(), ^(void) { + callback(output); + }); + + }); +} + +- (void)writeToFile:(nonnull NSString*) path + atomically:(BOOL)atomically + completion:(void (^) ())callback { + + dispatch_queue_t asyncQueue = dispatch_queue_create("org.nativescript.TNSWidgets.data", NULL); + dispatch_async(asyncQueue, ^(void) { + [self writeToFile:path + atomically:atomically]; + + dispatch_async(dispatch_get_main_queue(), ^(void) { + callback(); + }); + + }); +} + +@end diff --git a/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets/NSString+Async.h b/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets/NSString+Async.h new file mode 100644 index 000000000..2f0592262 --- /dev/null +++ b/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets/NSString+Async.h @@ -0,0 +1,25 @@ +// +// NSString+Async.h +// TNSWidgets +// +// Created by Peter Staev on 5.08.19. +// Copyright © 2019 Telerik A D. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSString (Async) + ++ (void)stringWithContentsOfFile:(nonnull NSString*)path + encoding:(NSStringEncoding)enc + completion:(void (^) (NSString*, NSError*))callback; + +- (void)writeToFile:(nonnull NSString*) path + atomically:(BOOL)atomically + encoding:(NSStringEncoding)enc + completion:(void (^) (NSError*))callback; +@end + +NS_ASSUME_NONNULL_END diff --git a/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets/NSString+Async.m b/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets/NSString+Async.m new file mode 100644 index 000000000..ec1147e74 --- /dev/null +++ b/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets/NSString+Async.m @@ -0,0 +1,50 @@ +// +// NSString+Async.m +// TNSWidgets +// +// Created by Peter Staev on 5.08.19. +// Copyright © 2019 Telerik A D. All rights reserved. +// + +#import "NSString+Async.h" + +@implementation NSString (Async) + ++ (void)stringWithContentsOfFile:(nonnull NSString*)path + encoding:(NSStringEncoding)enc + completion:(void (^) (NSString*, NSError*))callback { + + dispatch_queue_t asyncQueue = dispatch_queue_create("org.nativescript.TNSWidgets.string", NULL); + dispatch_async(asyncQueue, ^(void) { + NSError *error = nil; + NSString *output = [NSString stringWithContentsOfFile:path + encoding:enc + error:&error]; + dispatch_async(dispatch_get_main_queue(), ^(void) { + callback(output, error); + }); + + }); +} + +- (void)writeToFile:(nonnull NSString*)path + atomically:(BOOL)atomically + encoding:(NSStringEncoding)enc + completion:(void (^) (NSError*))callback { + + dispatch_queue_t asyncQueue = dispatch_queue_create("org.nativescript.TNSWidgets.string", NULL); + dispatch_async(asyncQueue, ^(void) { + NSError *error = nil; + [self writeToFile:path + atomically:atomically + encoding:enc + error:&error]; + + dispatch_async(dispatch_get_main_queue(), ^(void) { + callback(error); + }); + + }); +} + +@end diff --git a/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets/TNSWidgets.h b/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets/TNSWidgets.h index 453d7cd6e..7da1d2adb 100644 --- a/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets/TNSWidgets.h +++ b/tns-core-modules-widgets/ios/TNSWidgets/TNSWidgets/TNSWidgets.h @@ -20,3 +20,5 @@ FOUNDATION_EXPORT const unsigned char TNSWidgetsVersionString[]; #import "UIView+PassThroughParent.h" #import "TNSLabel.h" #import "TNSProcess.h" +#import "NSString+Async.h" +#import "NSData+Async.h" diff --git a/tns-core-modules/file-system/file-system-access.android.ts b/tns-core-modules/file-system/file-system-access.android.ts index d5088bee7..8c2a7077f 100644 --- a/tns-core-modules/file-system/file-system-access.android.ts +++ b/tns-core-modules/file-system/file-system-access.android.ts @@ -218,7 +218,30 @@ export class FileSystemAccess { return this.getLogicalRootPath() + "/app"; } - public read(path: string, onError?: (error: any) => any) { + public read = this.readSync.bind(this); + + public readAsync(path: string): Promise { + return new Promise((resolve, reject) => { + try { + org.nativescript.widgets.Async.File.read( + path, + new org.nativescript.widgets.Async.CompleteCallback({ + onComplete: (result: number[]) => { + resolve(result); + }, + onError: (err) => { + reject(new Error(err)); + } + }), + null, + ); + } catch (ex) { + reject(ex); + } + }); + } + + public readSync(path: string, onError?: (error: any) => any) { try { const javaFile = new java.io.File(path); const stream = new java.io.FileInputStream(javaFile); @@ -234,7 +257,31 @@ export class FileSystemAccess { } } - public write(path: string, bytes: native.Array, onError?: (error: any) => any) { + public write = this.writeSync.bind(this); + + public writeAsync(path: string, bytes: native.Array): Promise { + return new Promise((resolve, reject) => { + try { + org.nativescript.widgets.Async.File.write( + path, + bytes, + new org.nativescript.widgets.Async.CompleteCallback({ + onComplete: () => { + resolve(); + }, + onError: (err) => { + reject(new Error(err)); + } + }), + null, + ); + } catch (ex) { + reject(ex); + } + }); + } + + public writeSync(path: string, bytes: native.Array, onError?: (error: any) => any) { try { const javaFile = new java.io.File(path); const stream = new java.io.FileOutputStream(javaFile); @@ -247,7 +294,40 @@ export class FileSystemAccess { } } - public readText(path: string, onError?: (error: any) => any, encoding?: any) { + public readText = this.readTextSync.bind(this); + + public readTextAsync(path: string, encoding?: any): Promise { + let actualEncoding = encoding; + if (!actualEncoding) { + actualEncoding = textModule.encoding.UTF_8; + } + + return new Promise((resolve, reject) => { + try { + org.nativescript.widgets.Async.File.readText( + path, + actualEncoding, + new org.nativescript.widgets.Async.CompleteCallback({ + onComplete: (result: string) => { + if (actualEncoding === textModule.encoding.UTF_8) { + // Remove UTF8 BOM if present. http://www.rgagnon.com/javadetails/java-handle-utf8-file-with-bom.html + result = FileSystemAccess._removeUtf8Bom(result); + } + resolve(result); + }, + onError: (err) => { + reject(new Error(err)); + } + }), + null, + ); + } catch (ex) { + reject(ex); + } + }); + } + + public readTextSync(path: string, onError?: (error: any) => any, encoding?: any) { try { const javaFile = new java.io.File(path); const stream = new java.io.FileInputStream(javaFile); @@ -302,7 +382,37 @@ export class FileSystemAccess { return s; } - public writeText(path: string, content: string, onError?: (error: any) => any, encoding?: any) { + public writeText = this.writeTextSync.bind(this); + + public writeTextAsync(path: string, content: string, encoding?: any): Promise { + let actualEncoding = encoding; + if (!actualEncoding) { + actualEncoding = textModule.encoding.UTF_8; + } + + return new Promise((resolve, reject) => { + try { + org.nativescript.widgets.Async.File.writeText( + path, + content, + actualEncoding, + new org.nativescript.widgets.Async.CompleteCallback({ + onComplete: () => { + resolve(); + }, + onError: (err) => { + reject(new Error(err)); + } + }), + null, + ); + } catch (ex) { + reject(ex); + } + }); + } + + public writeTextSync(path: string, content: string, onError?: (error: any) => any, encoding?: any) { try { const javaFile = new java.io.File(path); const stream = new java.io.FileOutputStream(javaFile); diff --git a/tns-core-modules/file-system/file-system-access.d.ts b/tns-core-modules/file-system/file-system-access.d.ts index e89e3dca8..883c77f12 100644 --- a/tns-core-modules/file-system/file-system-access.d.ts +++ b/tns-core-modules/file-system/file-system-access.d.ts @@ -130,6 +130,23 @@ export class FileSystemAccess { */ readText(path: string, onError?: (error: any) => any, encoding?: any): string; + /** + * Reads a text from a file with a given path. + * @param path The path to the source file. + * @param encoding (optional) If set reads the text with the specified encoding (default UTF-8). + * Returns Promise of the text read. + */ + readTextAsync(path: string, encoding?: any): Promise; + + /** + * Reads a text from a file with a given path. + * @param path The path to the source file. + * @param onError (optional) A callback function to use if any error occurs. + * @param encoding (optional) If set reads the text with the specified encoding (default UTF-8). + * Returns the text read. + */ + readTextSync(path: string, onError?: (error: any) => any, encoding?: any): string; + /** * Reads a binary content from a file with a given path. * @param path The path to the source file. @@ -138,6 +155,21 @@ export class FileSystemAccess { */ read(path: string, onError?: (error: any) => any): any; + /** + * Reads a binary content from a file with a given path. + * @param path The path to the source file. + * Returns a Promise with the binary content read. + */ + readAsync(path: string): Promise; + + /** + * Reads a binary content from a file with a given path. + * @param path The path to the source file. + * @param onError (optional) A callback function to use if any error occurs. + * Returns the binary content read. + */ + readSync(path: string, onError?: (error: any) => any): any; + /** * Writes a text to a file with a given path. * @param path The path to the source file. @@ -147,6 +179,23 @@ export class FileSystemAccess { */ writeText(path: string, content: string, onError?: (error: any) => any, encoding?: any); + /** + * Writes a text to a file with a given path. + * @param path The path to the source file. + * @param content The content which will be written to the file. + * @param encoding (optional) If set writes the text with the specified encoding (default UTF-8). + */ + writeTextAsync(path: string, content: string, encoding?: any): Promise; + + /** + * Writes a text to a file with a given path. + * @param path The path to the source file. + * @param content The content which will be written to the file. + * @param onError (optional) A callback function to use if any error occurs. + * @param encoding (optional) If set writes the text with the specified encoding (default UTF-8). + */ + writeTextSync(path: string, content: string, onError?: (error: any) => any, encoding?: any); + /** * Writes a binary to a file with a given path. * @param path The path to the source file. @@ -155,6 +204,21 @@ export class FileSystemAccess { */ write(path: string, content: any, onError?: (error: any) => any); + /** + * Writes a binary to a file with a given path. + * @param path The path to the source file. + * @param content The content which will be written to the file. + */ + writeAsync(path: string, content: any): Promise; + + /** + * Writes a binary to a file with a given path. + * @param path The path to the source file. + * @param content The content which will be written to the file. + * @param onError (optional) A callback function to use if any error occurs. + */ + writeSync(path: string, content: any, onError?: (error: any) => any); + /** * Gets extension of the file with a given path. * @param path A path to the file. diff --git a/tns-core-modules/file-system/file-system-access.ios.ts b/tns-core-modules/file-system/file-system-access.ios.ts index 0761f49aa..d63f4b4a8 100644 --- a/tns-core-modules/file-system/file-system-access.ios.ts +++ b/tns-core-modules/file-system/file-system-access.ios.ts @@ -257,7 +257,31 @@ export class FileSystemAccess { return ios.getCurrentAppPath(); } - public readText(path: string, onError?: (error: any) => any, encoding?: any) { + public readText = this.readTextSync.bind(this); + + public readTextAsync(path: string, encoding?: any) { + const actualEncoding = encoding || textEncoding.UTF_8; + + return new Promise((resolve, reject) => { + try { + (NSString as any).stringWithContentsOfFileEncodingCompletion( + path, + actualEncoding, + (result, error) => { + if (error) { + reject(error); + } else { + resolve(result.toString()); + } + }, + ); + } catch (ex) { + reject(new Error("Failed to read file at path '" + path + "': " + ex)); + } + }); + } + + public readTextSync(path: string, onError?: (error: any) => any, encoding?: any) { const actualEncoding = encoding || textEncoding.UTF_8; try { @@ -271,7 +295,19 @@ export class FileSystemAccess { } } - public read(path: string, onError?: (error: any) => any): NSData { + public read = this.readSync.bind(this); + + public readAsync(path: string): Promise { + return new Promise((resolve, reject) => { + try { + (NSData as any).dataWithContentsOfFileCompletion(path, resolve); + } catch (ex) { + reject(new Error("Failed to read file at path '" + path + "': " + ex)); + } + }); + } + + public readSync(path: string, onError?: (error: any) => any): NSData { try { return NSData.dataWithContentsOfFile(path); } catch (ex) { @@ -281,7 +317,33 @@ export class FileSystemAccess { } } - public writeText(path: string, content: string, onError?: (error: any) => any, encoding?: any) { + public writeText = this.writeTextSync.bind(this); + + public writeTextAsync(path: string, content: string, encoding?: any): Promise { + const nsString = NSString.stringWithString(content); + const actualEncoding = encoding || textEncoding.UTF_8; + + return new Promise((resolve, reject) => { + try { + (nsString as any).writeToFileAtomicallyEncodingCompletion( + path, + true, + actualEncoding, + (error) => { + if (error) { + reject(error); + } else { + resolve(); + } + }, + ); + } catch (ex) { + reject(new Error("Failed to write file at path '" + path + "': " + ex)); + } + }); + } + + public writeTextSync(path: string, content: string, onError?: (error: any) => any, encoding?: any) { const nsString = NSString.stringWithString(content); const actualEncoding = encoding || textEncoding.UTF_8; @@ -296,7 +358,23 @@ export class FileSystemAccess { } } - public write(path: string, content: NSData, onError?: (error: any) => any) { + public write = this.writeSync.bind(this); + + public writeAsync(path: string, content: NSData): Promise { + return new Promise((resolve, reject) => { + try { + (content as any).writeToFileAtomicallyCompletion( + path, + true, + () => { resolve(); }, + ); + } catch (ex) { + reject(new Error("Failed to write file at path '" + path + "': " + ex)); + } + }); + } + + public writeSync(path: string, content: NSData, onError?: (error: any) => any) { try { content.writeToFileAtomically(path, true); } catch (ex) { diff --git a/tns-core-modules/file-system/file-system.d.ts b/tns-core-modules/file-system/file-system.d.ts index 900ae80ca..a21ca661a 100644 --- a/tns-core-modules/file-system/file-system.d.ts +++ b/tns-core-modules/file-system/file-system.d.ts @@ -96,6 +96,11 @@ export class File extends FileSystemEntity { */ readTextSync(onError?: (error: any) => any, encoding?: string): string; + /** + * Reads the binary content of the file asynchronously. + */ + read(): Promise; + /** * Reads the binary content of the file synchronously. * @param onError An optional function to be called if some IO-error occurs. @@ -117,6 +122,12 @@ export class File extends FileSystemEntity { */ writeTextSync(content: string, onError?: (error: any) => any, encoding?: string): void; + /** + * Writes the provided binary content to the file. + * @param content The binary content to be saved to the file. + */ + write(content: any): Promise; + /** * Writes the provided binary content to the file synchronously. * @param content The binary content to be saved to the file. diff --git a/tns-core-modules/file-system/file-system.ts b/tns-core-modules/file-system/file-system.ts index dce06ee92..24ada6e62 100644 --- a/tns-core-modules/file-system/file-system.ts +++ b/tns-core-modules/file-system/file-system.ts @@ -207,6 +207,31 @@ export class File extends FileSystemEntity { return getFileAccess().getFileSize(this.path); } + public read(): Promise { + return new Promise((resolve, reject) => { + try { + this.checkAccess(); + } catch (ex) { + reject(ex); + + return; + } + + this._locked = true; + + getFileAccess().readAsync(this.path).then( + (result) => { + resolve(result); + this._locked = false; + }, + (error) => { + reject(error); + this._locked = false; + }, + ); + }); + } + public readSync(onError?: (error: any) => any): any { this.checkAccess(); @@ -220,7 +245,7 @@ export class File extends FileSystemEntity { } }; - const content = getFileAccess().read(this.path, localError); + const content = getFileAccess().readSync(this.path, localError); this._locked = false; @@ -228,6 +253,31 @@ export class File extends FileSystemEntity { } + public write(content: any): Promise { + return new Promise((resolve, reject) => { + try { + this.checkAccess(); + } catch (ex) { + reject(ex); + + return; + } + + this._locked = true; + + getFileAccess().writeAsync(this.path, content).then( + () => { + resolve(); + this._locked = false; + }, + (error) => { + reject(error); + this._locked = false; + }, + ); + }); + } + public writeSync(content: any, onError?: (error: any) => any): void { this.checkAccess(); @@ -242,7 +292,7 @@ export class File extends FileSystemEntity { } }; - getFileAccess().write(this.path, content, localError); + getFileAccess().writeSync(this.path, content, localError); } finally { this._locked = false; } @@ -250,16 +300,26 @@ export class File extends FileSystemEntity { public readText(encoding?: string): Promise { return new Promise((resolve, reject) => { - let hasError = false; - const localError = (error) => { - hasError = true; - reject(error); - }; + try { + this.checkAccess(); + } catch (ex) { + reject(ex); - const content = this.readTextSync(localError, encoding); - if (!hasError) { - resolve(content); + return; } + + this._locked = true; + + getFileAccess().readTextAsync(this.path, encoding).then( + (result) => { + resolve(result); + this._locked = false; + }, + (error) => { + reject(error); + this._locked = false; + }, + ); }); } @@ -277,7 +337,7 @@ export class File extends FileSystemEntity { } }; - const content = getFileAccess().readText(this.path, localError, encoding); + const content = getFileAccess().readTextSync(this.path, localError, encoding); this._locked = false; return content; @@ -285,16 +345,26 @@ export class File extends FileSystemEntity { public writeText(content: string, encoding?: string): Promise { return new Promise((resolve, reject) => { - let hasError = false; - const localError = function (error) { - hasError = true; - reject(error); - }; + try { + this.checkAccess(); + } catch (ex) { + reject(ex); - this.writeTextSync(content, localError, encoding); - if (!hasError) { - resolve(); + return; } + + this._locked = true; + + getFileAccess().writeTextAsync(this.path, content, encoding).then( + () => { + resolve(); + this._locked = false; + }, + (error) => { + reject(error); + this._locked = false; + }, + ); }); } @@ -312,8 +382,7 @@ export class File extends FileSystemEntity { } }; - // TODO: Asyncronous - getFileAccess().writeText(this.path, content, localError, encoding); + getFileAccess().writeTextSync(this.path, content, localError, encoding); } finally { this._locked = false; } diff --git a/tns-platform-declarations/android/org.nativescript.widgets.d.ts b/tns-platform-declarations/android/org.nativescript.widgets.d.ts index dc17e7638..af1127ef9 100644 --- a/tns-platform-declarations/android/org.nativescript.widgets.d.ts +++ b/tns-platform-declarations/android/org.nativescript.widgets.d.ts @@ -17,6 +17,13 @@ export function download(url: string, callback: CompleteCallback, context: any); } + export module File { + export function readText(path: string, encoding: string, callback: CompleteCallback, context: any); + export function read(path: string, callback: CompleteCallback, context: any); + export function writeText(path: string, content: string, encoding: string, callback: CompleteCallback, context: any); + export function write(path: string, content: native.Array, callback: CompleteCallback, context: any); + } + export module Http { export class KeyValuePair { public key: string;