From ce89af26435795ffdd00822ac4d3bd878c4542ac Mon Sep 17 00:00:00 2001 From: atanasovg Date: Wed, 23 Apr 2014 18:48:28 +0300 Subject: [PATCH] Added the iOS implementation of the FileSystem API. --- FileSystem/file_system_access.android.ts | 2 +- FileSystem/file_system_access.ios.ts | 270 +++++++++++++++++--- UserPreferences/user_preferences.android.ts | 6 +- UserPreferences/user_preferences.ios.ts | 4 +- Utils/utils_android.ts | 47 ++-- Utils/utils_ios.ts | 41 ++- declarations.ios.d.ts | 35 ++- 7 files changed, 312 insertions(+), 93 deletions(-) diff --git a/FileSystem/file_system_access.android.ts b/FileSystem/file_system_access.android.ts index 2a9c3d143..db098f24b 100644 --- a/FileSystem/file_system_access.android.ts +++ b/FileSystem/file_system_access.android.ts @@ -346,7 +346,7 @@ export class FileSystemAccess { } } - public getFileExtension(path: string): string { + private getFileExtension(path: string): string { var dotIndex = path.lastIndexOf("."); if (dotIndex && dotIndex >= 0 && dotIndex < path.length) { return path.substring(dotIndex); diff --git a/FileSystem/file_system_access.ios.ts b/FileSystem/file_system_access.ios.ts index 614db1583..ffcf27496 100644 --- a/FileSystem/file_system_access.ios.ts +++ b/FileSystem/file_system_access.ios.ts @@ -1,89 +1,287 @@ import app_module = require("Application/application"); +import utilsModule = require("Utils/utils_ios"); + +// TODO: Implement all the APIs receiving callback using async blocks +// TODO: Check whether we need try/catch blocks for the iOS implementation export class FileSystemAccess { private keyFileType = "NSFileType"; private keyModificationTime = "NSFileModificationDate"; private keyReadonly = "NSFileImmutable"; - - public getReadonly(path: string): boolean { - // TODO: Not implemented - return false; - } + private documentDir = 9; + private cachesDir = 13; + private userDomain = 1; + private NSUTF8StringEncoding = 4; public getLastModified(path: string): Date { - // TODO: Not implemented - return undefined; + var fileManager = Foundation.NSFileManager.defaultManager(); + var attributes = fileManager.attributesOfItemAtPathError(path, null); + + if (attributes) { + var date = attributes.objectForKey(this.keyModificationTime); + var interval = date.timeIntervalSince1970(); + + // time interval is in seconds, Date constructor expects milliseconds, hence this multiply by 1000 + return new Date(interval * 1000); + } else { + return new Date(); + } } - public getParent(path: string, onSuccess: (path: string) => any, onError?: (error: any) => any) { - // TODO: Not implemented + public getParent(path: string, onError?: (error: any) => any): { path: string; name: string } { + try { + var fileManager = Foundation.NSFileManager.defaultManager(); + var nsString = Foundation.NSString.initWithString(path); + + var parentPath = nsString.stringByDeletingLastPathComponent(); + var name = fileManager.displayNameAtPath(parentPath); + + return { + path: parentPath.toString(), + name: name + }; + } + catch (exception) { + if (onError) { + onError(exception); + } + + return undefined; + } } - public getFile(path: string, onSuccess: (path: string) => any, onError?: (error: any) => any) { - // TODO: Not implemented + public getFile(path: string, onError?: (error: any) => any): { path: string; name: string; extension: string } { + try { + var fileManager = Foundation.NSFileManager.defaultManager(); + var exists = fileManager.fileExistsAtPath(path); + + if (!exists) { + if (!fileManager.createFileAtPathContentsAttributes(path, null, null)) { + if (onError) { + onError(new Error("Failed to create folder at path '" + path + "'")); + } + + return undefined; + } + } + + var fileName = fileManager.displayNameAtPath(path); + + return { + path: path, + name: fileName, + extension: this.getFileExtension(path) + }; + } + catch (exception) { + if (onError) { + onError(exception); + } + + return undefined; + } } - public getFolder(path: string, onSuccess: (path: string) => any, onError?: (error: any) => any) { - // TODO: Not implemented + public getFolder(path: string, onError?: (error: any) => any): { path: string; name: string } { + try { + var fileManager = Foundation.NSFileManager.defaultManager(); + var exists = this.folderExists(path); + + if (!exists) { + if (!fileManager.createDirectoryAtPathWithIntermediateDirectoriesAttributesError(path, true, null, null)) { + // error + if (onError) { + onError(new Error("Failed to create folder at path '" + path + "'")); + } + + return undefined; + } + } + + var dirName = fileManager.displayNameAtPath(path); + + return { + path: path, + name: dirName + }; + } + catch (ex) { + if (onError) { + onError(new Error("Failed to create folder at path '" + path + "'")); + } + + return undefined; + } } - public enumFiles(path: string, onSuccess: (files: Array) => any, onError?: (error: any) => any) { - // TODO: Not implemented + public enumFiles(path: string, onSuccess: (files: Array<{ path: string; name: string; extension: string }>) => any, onError?: (error: any) => any) { + try { + var fileManager = Foundation.NSFileManager.defaultManager(); + var files = fileManager.contentsOfDirectoryAtPathError(path, null); + + if (!files) { + if (onError) { + onError(new Error("Failed to enum files for forlder '" + path + "'")); + } + + return; + } + + var fileInfos = new Array<{ path: string; name: string; extension: string }>(); + var file, + i, + info; + + + for (i = 0; i < files.count(); i++) { + file = files.objectAtIndex(i); + + info = { + path: this.concatPath(path, file), + name: file + }; + + if (!this.folderExists(file)) { + info.extension = this.getFileExtension(info.path); + } + + fileInfos.push(info); + } + + if (onSuccess) { + onSuccess(fileInfos); + } + } + catch (ex) { + if (onError) { + onError(ex); + } + } } public fileExists(path: string): boolean { - // TODO: Not implemented - return false; + var fileManager = Foundation.NSFileManager.defaultManager(); + return fileManager.fileExistsAtPath(path); } public folderExists(path: string): boolean { - // TODO: Not implemented - return false; + var fileManager = Foundation.NSFileManager.defaultManager(); + + var buffer = NativePointer.create(PrimitiveType.BOOL, 1); + var exists = fileManager.fileExistsAtPathIsDirectory(path, buffer); + + var isDir = buffer[0] && buffer[0] > 0; + + buffer.delete(); + + return exists && isDir; } public concatPath(left: string, right: string): string { - // TODO: Not implemented - return undefined; + // TODO: This probably is not efficient, we may try concatenation with the "/" character + var nsArray = utilsModule.Collections.jsArrayToNSArray([left, right]); + var nsString = Foundation.NSString.pathWithComponents(nsArray); + + return nsString.toString(); } public deleteFile(path: string, onSuccess?: () => any, onError?: (error: any) => any) { - // TODO: Not implemented + this.deleteEntity(path, onSuccess, onError); } public deleteFolder(path: string, isKnown?: boolean, onSuccess?: () => any, onError?: (error: any) => any) { - // TODO: Not implemented + this.deleteEntity(path, onSuccess, onError); } public emptyFolder(path: string, onSuccess?: () => any, onError?: (error: any) => any) { - // TODO: Not implemented + var fileManager = Foundation.NSFileManager.defaultManager(); + + var filesEnum = function (files: Array<{ path: string; name: string; extension: string }>) { + var i; + for (i = 0; i < files.length; i++) { + if (!fileManager.removeItemAtPathError(files[i].path, null)) { + if (onError) { + onError(new Error("Failed to empty folder '" + path + "'")); + } + + return; + } + } + + if (onSuccess) { + onSuccess(); + } + } + + this.enumFiles(path, filesEnum, onError); } - public rename(path: string, onSuccess?: () => any, onError?: (error: any) => any) { - // TODO: No implementation + public rename(path: string, newPath: string, onSuccess?: () => any, onError?: (error: any) => any) { + var fileManager = Foundation.NSFileManager.defaultManager(); + if (!fileManager.moveItemAtPathToPathError(path, newPath, null)) { + if (onError) { + onError(new Error("Failed to rename '" + path + "' to '" + newPath + "'")); + } + } else if (onSuccess) { + onSuccess(); + } } public getDocumentsFolderPath(): string { - // TODO: Not implemented - return undefined; + return this.getKnownPath(this.documentDir); } public getTempFolderPath(): string { - // TODO: Not implemented - return undefined; + return this.getKnownPath(this.cachesDir); } public readText(path: string, onSuccess: (content: string) => any, onError?: (error: any) => any) { - // TODO: Not implemented + var nsString = Foundation.NSString.stringWithContentsOfFileEncodingError(path, this.NSUTF8StringEncoding, null); + if (!nsString) { + if (onError) { + onError(new Error("Failed to read file at path '" + path + "'")); + } + } else if (onSuccess) { + onSuccess(nsString.toString()); + } } public writeText(path: string, content: string, onSuccess?: () => any, onError?: (error: any) => any) { - // TODO: Not implemented + var nsString = Foundation.NSString.initWithString(content); + + // TODO: verify the useAuxiliaryFile parameter should be false + if (!nsString.writeToFileAtomicallyEncodingError(path, false, this.NSUTF8StringEncoding, null)) { + if (onError) { + onError(new Error("Failed to write to file '" + path + "'")); + } + } else if (onSuccess) { + onSuccess(); + } } - //private getKnownPath(folderType: number): string { - // var fileManager = Foundation.NSFileManager.defaultManager(); + private getKnownPath(folderType: number): string { + var fileManager = Foundation.NSFileManager.defaultManager(); + var paths = fileManager.URLsForDirectoryInDomains(folderType, this.userDomain); + var url = paths.objectAtIndex(0); + return url.path(); + } - // return folder; - //} + private getFileExtension(path: string): string { + var url = Foundation.NSURL.fileURLWithPathIsDirectory(path, false); + return url.pathExtension(); + } + + private deleteEntity(path: string, onSuccess?: () => any, onError?: (error: any) => any) { + var fileManager = Foundation.NSFileManager.defaultManager(); + if (!fileManager.removeItemAtPathError(path, null)) { + if (onError) { + onError(new Error("Failed to delete file at path '" + path + "'")); + } + } else { + if (onSuccess) { + onSuccess(); + } + } + } } \ No newline at end of file diff --git a/UserPreferences/user_preferences.android.ts b/UserPreferences/user_preferences.android.ts index 48406f2cd..6035983ba 100644 --- a/UserPreferences/user_preferences.android.ts +++ b/UserPreferences/user_preferences.android.ts @@ -52,9 +52,9 @@ export class UserPreferences { if ("undefined" == typeof defaultValue) { defaultValue = []; } - var hashSet = utils_module.tk.utils.Collections.stringArrayToStringSet(defaultValue); + var hashSet = utils_module.Collections.stringArrayToStringSet(defaultValue); var res = this.sharedPreferences.getStringSet(key, hashSet); - return utils_module.tk.utils.Collections.stringSetToStringArray(res); + return utils_module.Collections.stringSetToStringArray(res); } public setBoolean(key: string, value: boolean) { @@ -90,7 +90,7 @@ export class UserPreferences { public setStrings(key: string, values: string[]) { var editor = this.sharedPreferences.edit(); - var hashSet = utils_module.tk.utils.Collections.stringArrayToStringSet(values); + var hashSet = utils_module.Collections.stringArrayToStringSet(values); editor.putStringSet(key, hashSet); editor.commit(); } diff --git a/UserPreferences/user_preferences.ios.ts b/UserPreferences/user_preferences.ios.ts index 4ee529414..02c40d975 100644 --- a/UserPreferences/user_preferences.ios.ts +++ b/UserPreferences/user_preferences.ios.ts @@ -66,7 +66,7 @@ export class UserPreferences { public getStrings(key: string, defaultValue?: string[]): string[] { if (this.containsKey(key)) { var nsArray = this.userDefaults.stringArrayForKey(key); - var jsArray = utils_module.tk.utils.Collections.nsArrayToJSArray(nsArray); + var jsArray = utils_module.Collections.nsArrayToJSArray(nsArray); return jsArray; } if ("undefined" == typeof defaultValue) { @@ -101,7 +101,7 @@ export class UserPreferences { } public setStrings(key: string, values: string[]) { - var nsArray = utils_module.tk.utils.Collections.jsArrayToNSArray(values); + var nsArray = utils_module.Collections.jsArrayToNSArray(values); this.userDefaults.setObjectForKey(nsArray, key); this.userDefaults.synchronize(); } diff --git a/Utils/utils_android.ts b/Utils/utils_android.ts index 96d6923fe..3acfe9f2f 100644 --- a/Utils/utils_android.ts +++ b/Utils/utils_android.ts @@ -1,29 +1,24 @@ -export module tk { - // TODO: this should be properly organized - export module utils { - export class Collections { - public static stringArrayToStringSet(str: string[]): any { - var hashSet = new java.util.HashSet(); - if ("undefined" != typeof str) { - for (var element in str) { - hashSet.add('' + str[element]); - } - } - return hashSet; - } - - public static stringSetToStringArray(stringSet: any): string[] { - var arr = []; - if ("undefined" != typeof stringSet) { - var it = stringSet.iterator(); - while (it.hasNext()) { - var element = '' + it.next(); - arr.push(element); - } - } - - return arr; +export class Collections { + public static stringArrayToStringSet(str: string[]): any { + var hashSet = new java.util.HashSet(); + if ("undefined" != typeof str) { + for (var element in str) { + hashSet.add('' + str[element]); } } + return hashSet; } -} \ No newline at end of file + + public static stringSetToStringArray(stringSet: any): string[] { + var arr = []; + if ("undefined" != typeof stringSet) { + var it = stringSet.iterator(); + while (it.hasNext()) { + var element = '' + it.next(); + arr.push(element); + } + } + + return arr; + } +} \ No newline at end of file diff --git a/Utils/utils_ios.ts b/Utils/utils_ios.ts index 97c8c9d84..d355c3252 100644 --- a/Utils/utils_ios.ts +++ b/Utils/utils_ios.ts @@ -1,27 +1,22 @@ -export module tk { - // TODO: this should be properly organized - export module utils { - export class Collections { - public static jsArrayToNSArray(str: string[]): any { - var arr = new Foundation.NSMutableArray(); - if ("undefined" != typeof str) { - for (var element in str) { - arr.addObject(str[element]); - } - } - return arr; - } - - public static nsArrayToJSArray(a: any): string[] { - var arr = []; - if ("undefined" != typeof a) { - for (var i = 0; i < a.count(); i++) { - arr.push(a.objectAtIndex(i)); - } - } - - return arr; +export class Collections { + public static jsArrayToNSArray(str: string[]): any { + var arr = new Foundation.NSMutableArray(); + if ("undefined" != typeof str) { + for (var element in str) { + arr.addObject(str[element]); } } + return arr; + } + + public static nsArrayToJSArray(a: any): string[] { + var arr = []; + if ("undefined" != typeof a) { + for (var i = 0; i < a.count(); i++) { + arr.push(a.objectAtIndex(i)); + } + } + + return arr; } } \ No newline at end of file diff --git a/declarations.ios.d.ts b/declarations.ios.d.ts index 17bb8af5b..cc6caec84 100644 --- a/declarations.ios.d.ts +++ b/declarations.ios.d.ts @@ -47,6 +47,9 @@ declare module UIKit { } declare module Foundation { + export class NSError extends NSObject { + } + export class NSObject { static extends(...optionalParams: any[]): any; } @@ -62,13 +65,36 @@ declare module Foundation { export class NSFileManager { static defaultManager(): NSFileManager; URLsForDirectoryInDomains(directory: number, mask: number): any; + attributesOfItemAtPathError(path: string, error: any): any; + fileExistsAtPath(path: string): boolean; + fileExistsAtPathIsDirectory(path: string, isDir: boolean): boolean; + createDirectoryAtPathWithIntermediateDirectoriesAttributesError(path: string, intermediateDirs: boolean, attributes: any, error: any): boolean; + displayNameAtPath(path: string): string; + createFileAtPathContentsAttributes(path: string, data: any, attributes: any): boolean; + enumeratorAtPath(path: string): any; + contentsOfDirectoryAtPathError(path: string, error: any); + removeItemAtPathError(path: string, error: any): boolean; + moveItemAtPathToPathError(sourcePath: string, destPath: string, error: any); + contentsAtPath(path: string): NSData; + } + + export class NSData extends NSObject { + } export class NSString { static initWithString(s: string): NSString; static initWithDataEncoding(data: any, encoding: any): any; + static pathWithComponents(paths: NSArray): NSString; + static stringWithContentsOfFileEncodingError(path: string, encoding: number, error: any): NSString; stringByDeletingLastPathComponent(): string; + stringByDeletingPathExtension(): string; dataUsingEncoding(encoding: number): any; + writeToFileAtomicallyEncodingError(path: string, atomically: boolean, encoding: number, error: any): boolean; + } + + export class NSArray extends NSObject { + static arrayWithObjectsWithArguments(...params: any[]): NSArray; } export class NSURLSessionConfiguration { @@ -84,10 +110,12 @@ declare module Foundation { } export class NSURL { - static URLWithString(url: string): any; + static URLWithString(url: string): NSURL; + static fileURLWithPathIsDirectory(path: string, isDirectory: boolean): NSURL; path(): string; relativePath(): string; relativeString(): string; + pathExtension(): string; } export class NSDate { @@ -128,4 +156,7 @@ declare module CoreLocation { function CLLocationCoordinate2DMake(latitude: number, longitude: number) : any; } -*/ \ No newline at end of file + +declare var NativePointer: any; +declare var PrimitiveType: any; +declare var RefValue: any;