From 495beffdd109bfe9d822e6d106d7afcfc34efadf Mon Sep 17 00:00:00 2001 From: atanasovg Date: Wed, 16 Apr 2014 19:22:41 +0300 Subject: [PATCH] FileSystem API - tests + polish --- FileSystem/file_system.d.ts | 68 +++++--- FileSystem/file_system.ts | 200 +++++++++++++---------- FileSystem/file_system_access.android.ts | 160 ++++++++++++------ FileSystem/file_system_access.d.ts | 12 +- 4 files changed, 272 insertions(+), 168 deletions(-) diff --git a/FileSystem/file_system.d.ts b/FileSystem/file_system.d.ts index 4205f7ac1..dd2a28a70 100644 --- a/FileSystem/file_system.d.ts +++ b/FileSystem/file_system.d.ts @@ -1,5 +1,7 @@ export declare class FileSystemEntity { - public readonly: boolean; + /** + * Gets the Date object specifying the last time this entity was modified. + */ public lastModified: Date; /** * Gets the name of the entity. @@ -18,9 +20,9 @@ */ public delete(onSuccess?: () => any, onError?: (error: any) => any); /** - * Renames the current entity using the specified name. - */ - public rename(newName: string, onSuccess?: Function, onError?: Function); + * Renames the current entity using the specified name. + */ + public rename(newName: string, onSuccess?: () => any, onError?: (error) => any); } export declare class File extends FileSystemEntity { @@ -35,7 +37,7 @@ export declare class File extends FileSystemEntity { /** * Gets or creates a File entity at the specified path. */ - public static fromPath(path: string, onSuccess: (file: File) => any, onError?: (error: any) => any); + public static fromPath(path: string, onError?: (error: any) => any): File; /** * Checks whether a File with the specified path already exists. */ @@ -59,31 +61,42 @@ export declare class Folder extends FileSystemEntity { /** * Gets or creates a Folder entity at the specified path. */ - public static fromPath(path: string, onSuccess: (folder: Folder) => any, onError?: (error: any) => any); + public static fromPath(path: string, onError?: (error: any) => any): Folder; + /** * Checks whether a Folder with the specified path already exists. */ public static exists(path: string): boolean; + /** * Checks whether this Folder contains a file with the specified name. */ public containsFile(name: string): boolean; + + /** + * Checks whether this Folder contains a Folder with the specified name. + */ + public containsFolder(name: string): boolean; + /** * Deletes all the files and folders (recursively), contained within this Folder. */ public empty(onSuccess?: () => any, onError?: (error: any) => any); + /** * Gets or creates a File entity with the specified name within this Folder. */ - public getFile(name: string, onSuccess: (file: File) => any, onError?: (error: any) => any); + public getFile(name: string, onError?: (error: any) => any): File; + /** * Gets or creates a Folder entity with the specified name within this Folder. */ - public getFolder(name: string, onSuccess: (folder: Folder) => any, onError?: (error: any) => any); + public getFolder(name: string, onError?: (error: any) => any): Folder; + /** - * Gets all the top-level files residing within this Folder. - */ - public enumFiles(onSuccess: (files: Array) => any, onError?: (error: any) => any); + * Gets all the top-level FileSystem entities residing within this Folder. + */ + public enumEntities(onSuccess: (entities: Array) => any, onError?: (error: any) => any); } /** @@ -91,14 +104,14 @@ export declare class Folder extends FileSystemEntity { */ export declare class KnownFolders { /** - * Gets the Documents folder available for the current application. This Folder is private for the application and not accessible from Users/External apps. - */ - public static Documents(): Folder; + * Gets the Documents folder available for the current application. This Folder is private for the application and not accessible from Users/External apps. + */ + public static documents(): Folder; /** - * Gets the Temporary (Caches) folder available for the current application. This Folder is private for the application and not accessible from Users/External apps. - */ - public static Temporary(): Folder; + * Gets the Temporary (Caches) folder available for the current application. This Folder is private for the application and not accessible from Users/External apps. + */ + public static temporary(): Folder; } /** @@ -106,13 +119,15 @@ export declare class KnownFolders { */ export declare class FileAccess { constructor(file: File); + /** - * Unlocks the file and allows other operations over it. - */ + * Unlocks the file and allows other operations over it. + */ public release(); + /** - * Gets the underlying File instance. - */ + * Gets the underlying File instance. + */ file: File; } @@ -120,15 +135,18 @@ export declare class FileAccess { * Enables reading the content of a File entity. */ export declare class FileReader extends FileAccess { - /** - * Reads the content of the underlying File as a UTF8 encoded string. - */ - public readText(onSuccess: (content: string) => any, onError?: (error: any) => any); +/** +* Reads the content of the underlying File as a UTF8 encoded string. +*/ +public readText(onSuccess: (content: string) => any, onError?: (error: any) => any); } /** * Enables saving data to a File entity. */ export declare class FileWriter extends FileAccess { + /** + * Enables saving string to a File entity. + */ public writeText(content: string, onSuccess?: () => any, onError?: (error: any) => any); } \ No newline at end of file diff --git a/FileSystem/file_system.ts b/FileSystem/file_system.ts index d206a0d62..3a545f2d1 100644 --- a/FileSystem/file_system.ts +++ b/FileSystem/file_system.ts @@ -1,6 +1,5 @@ import file_access_module = require("FileSystem/file_system_access"); - // The FileSystemAccess implementation, used through all the APIs. var fileAccess; var getFileAccess = function (): file_access_module.FileSystemAccess { @@ -9,7 +8,7 @@ var getFileAccess = function (): file_access_module.FileSystemAccess { } return fileAccess; -} +}; // we are defining these as private variables within the IO scope and will use them to access the corresponding properties for each FSEntity instance. // this allows us to encapsulate (hide) the explicit property setters and force the users go through the exposed APIs to receive FSEntity instances. @@ -18,9 +17,36 @@ var pathProperty = "_path"; var isKnownProperty = "_isKnown"; var fileLockedProperty = "_locked"; var extensionProperty = "_extension"; -var readonlyProperty = "_readonly"; var lastModifiedProperty = "_lastModified"; +var createFile = function (info: { path: string; name: string; extension: string }) { + var file = new File(); + file[pathProperty] = info.path; + file[nameProperty] = info.name; + file[extensionProperty] = info.extension; + + return file; +}; + +var createFolder = function (info: { path: string; name: string; }) { + var documents = KnownFolders.documents(); + if (info.path === documents.path) { + return documents; + } + + var temp = KnownFolders.temporary(); + if (info.path === temp.path) { + return temp; + } + + var folder = new Folder(); + + folder[pathProperty] = info.path; + folder[nameProperty] = info.name; + + return folder; +}; + /** * Represents the basic file system entity - a File or a Folder. */ @@ -29,13 +55,14 @@ export class FileSystemEntity { * Gets the Folder object representing the parent of this entity. Will be null for a root folder like Documents or Temporary. */ public getParent(onError?: (error: any) => any): Folder { - var path = getFileAccess().getParent(this.path, onError); + var folderInfo = getFileAccess().getParent(this.path, onError); + if (!folderInfo) { + return undefined; + } - var folder = new Folder(); - folder[pathProperty] = path; - - return folder; + return createFolder(folderInfo); } + /** * Deletes the current entity from the file system. */ @@ -46,35 +73,65 @@ export class FileSystemEntity { getFileAccess().deleteFolder(this.path, this[isKnownProperty], onSuccess, onError); } } + /** * Renames the current entity using the specified name. */ public rename(newName: string, onSuccess?: () => any, onError?: (error: any) => any) { - // TODO: No implementation + if (this instanceof Folder) { + if (this[isKnownProperty]) { + if (onError) { + onError(new Error("Cannot rename known folder.")); + } + + return; + } + } + + var parentFolder = this.getParent(); + if (!parentFolder) { + if (onError) { + onError(new Error("No parent folder.")); + } + + return; + } + + var fileAccess = getFileAccess(); + var path = parentFolder.path; + var newPath = fileAccess.concatPath(path, newName); + + var that = this; + var localSucceess = function () { + that[pathProperty] = newPath; + that[nameProperty] = newName; + + if (that instanceof File) { + that[extensionProperty] = fileAccess.getFileExtension(newPath); + } + + if (onSuccess) { + onSuccess(); + } + } + + fileAccess.rename(this.path, newPath, localSucceess, onError); } + /** * Gets the name of the entity. */ get name(): string { return this[nameProperty]; } + /** * Gets the fully-qualified path (including the extension for a File) of the entity. */ get path(): string { return this[pathProperty]; } - /** - * Gets a value indicating whether this entity is read-only (no write persmissions). - */ - get readonly(): boolean { - var value = this[readonlyProperty]; - if (this[readonlyProperty] === undefined) { - value = this[readonlyProperty] = getFileAccess().getReadonly(this.path); - } - return value; - } /** * Gets the fully-qualified path (including the extension for a File) of the entity. */ @@ -95,16 +152,13 @@ export class File extends FileSystemEntity { /** * Gets the File instance associated with the specified path. */ - public static fromPath(path: string, onSuccess: (file: File) => any, onError?: (error: any) => any) { - var localSuccess = function (path: string) { - var file = new File(); - file[pathProperty] = path; - - if (onSuccess) { - onSuccess(file); - } + public static fromPath(path: string, onError?: (error: any) => any) { + var fileInfo = getFileAccess().getFile(path, onError); + if (!fileInfo) { + return undefined; } - getFileAccess().getFile(path, localSuccess, onError); + + return createFile(fileInfo); } /** * Checks whether a File with the specified path already exists. @@ -112,12 +166,7 @@ export class File extends FileSystemEntity { public static exists(path: string): boolean { return getFileAccess().fileExists(path); } - /** - * Deletes the current File from the file system. - */ - public delete(onSuccess?: () => any, onError?: (error: any) => any) { - getFileAccess().deleteFile(this.path, onSuccess, onError); - } + /** * Creates a FileReader object over this file and locks the file until the reader is released. */ @@ -162,15 +211,12 @@ export class Folder extends FileSystemEntity { * Attempts to access a Folder at the specified path and creates a new Folder if there is no existing one. */ public static fromPath(path: string, onSuccess: (folder: Folder) => any, onError?: (error: any) => any) { - var localSuccess = function (path: string) { - var folder = new Folder(); - folder[pathProperty] = path; - - if (onSuccess) { - onSuccess(folder); - } + var folderInfo = getFileAccess().getFolder(path, onError); + if (!folderInfo) { + return undefined; } - getFileAccess().getFolder(path, localSuccess, onError); + + return createFolder(folderInfo); } /** @@ -198,13 +244,6 @@ export class Folder extends FileSystemEntity { return fileAccess.folderExists(path); } - /** - * Deletes the current Folder (recursively) from the file system. - */ - public delete(onSuccess?: () => any, onError?: (error: any) => any) { - getFileAccess().deleteFolder(this.path, this.isKnown, onSuccess, onError); - } - /** * Deletes all the files and folders (recursively), contained within this Folder. */ @@ -222,60 +261,53 @@ export class Folder extends FileSystemEntity { /** * Attempts to open a File with the specified name within this Folder and optionally creates a new File if there is no existing one. */ - public getFile(name: string, onSuccess: (file: File) => any, onError?: (error: any) => any, createIfNonExisting?: boolean) { - var localSuccess = function (filePath: string) { - var newFile = new File(); - - newFile[pathProperty] = filePath; - newFile[nameProperty] = name; - - if (onSuccess) { - onSuccess(newFile); - } - } + public getFile(name: string, onError?: (error: any) => any): File { var fileAccess = getFileAccess(); var path = fileAccess.concatPath(this.path, name); - fileAccess.getFile(path, localSuccess, onError); + + var fileInfo = fileAccess.getFile(path, onError); + if (!fileInfo) { + return undefined; + } + + return createFile(fileInfo); } /** * Attempts to open a Folder with the specified name within this Folder and optionally creates a new Folder if there is no existing one. */ - public getFolder(name: string, onSuccess: (folder: Folder) => any, onError?: (error: any) => any) { - var localSuccess = function (filePath: string) { - var newFolder = new Folder(); - - newFolder[pathProperty] = filePath; - newFolder[nameProperty] = name; - - if (onSuccess) { - onSuccess(newFolder); - } - } - + public getFolder(name: string, onError?: (error: any) => any): Folder { var fileAccess = getFileAccess(); var path = fileAccess.concatPath(this.path, name); - fileAccess.getFolder(path, localSuccess, onError); + + var folderInfo = fileAccess.getFolder(path, onError); + if (!folderInfo) { + return undefined; + } + + return createFolder(folderInfo); } /** - * Gets all the top-level files residing within this Folder. + * Gets all the top-level FileSystem entities residing within this Folder. */ - public enumFiles(onSuccess: (files: Array) => any, onError?: (error: any) => any) { - var localSuccess = function (paths: Array) { + public enumEntities(onSuccess: (files: Array) => any, onError?: (error: any) => any) { + var localSuccess = function (fileInfos: Array<{ path: string; name: string; extension: string }>) { if (onSuccess) { - var files = new Array(); + var entities = new Array(); var i, path: string, - file: File; + entity: FileSystemEntity; - for (i = 0; i < files.length; i++) { - file = new File(); - file[pathProperty] = files[i]; - files.push(file); + for (i = 0; i < fileInfos.length; i++) { + if (fileInfos[i].extension) { + entities.push(createFile(fileInfos[i])); + } else { + entities.push(createFolder(fileInfos[i])); + } } - onSuccess(files); + onSuccess(entities); } } getFileAccess().enumFiles(this.path, localSuccess, onError); diff --git a/FileSystem/file_system_access.android.ts b/FileSystem/file_system_access.android.ts index 1d04f3561..ca827f148 100644 --- a/FileSystem/file_system_access.android.ts +++ b/FileSystem/file_system_access.android.ts @@ -3,23 +3,19 @@ export class FileSystemAccess { private _encoding = "UTF-8"; - private _pathSeparator = "/"; - - public getReadonly(path: string): boolean { - var javaFile = new java.io.File(path); - return javaFile.exists() && !javaFile.canWrite(); - } + private _pathSeparator = java.io.File.separator; public getLastModified(path: string): Date { var javaFile = new java.io.File(path); return new Date(javaFile.lastModified()); } - public getParent(path: string, onError?: (error: any) => any): string { + public getParent(path: string, onError?: (error: any) => any): { path: string; name: string } { try { var javaFile = new java.io.File(path); var parent = javaFile.getParentFile(); - return parent.getPath(); + + return { path: parent.getAbsolutePath(), name: parent.getName() }; } catch (exception) { // TODO: unified approach for error messages if (onError) { @@ -30,25 +26,24 @@ export class FileSystemAccess { } } - public getFile(path: string, onSuccess: (path: string) => any, onError?: (error: any) => any) { - this.ensureFile(new java.io.File(path), onSuccess, onError); + public getFile(path: string, onError?: (error: any) => any): { path: string; name: string; extension: string } { + return this.ensureFile(new java.io.File(path), false, onError); } - public getFolder(path: string, onSuccess: (path: string) => any, onError?: (error: any) => any) { + public getFolder(path: string, onError?: (error: any) => any): { path: string; name: string } { var javaFile = new java.io.File(path); - if (javaFile.exists() && !javaFile.isDirectory()) { - if (onError) { - onError("The path " + path + "exists and is not a directory"); - } - } else { - this.ensureFile(javaFile, onSuccess, onError); + var dirInfo = this.ensureFile(javaFile, true, onError); + if (!dirInfo) { + return undefined; } + + return { path: dirInfo.path, name: dirInfo.name }; } - public enumFiles(path: string, onSuccess: (files: Array) => any, onError?: (error: any) => any) { + public enumFiles(path: string, onSuccess: (files: Array<{ path: string; name: string; extension: string }>) => any, onError?: (error: any) => any) { try { var javaFile = new java.io.File(path); - if (!javaFile.isDirectory()) { + if (!javaFile.getCanonicalFile().isDirectory()) { if (onError) { onError("There is no folder existing at path " + path); } @@ -56,27 +51,31 @@ export class FileSystemAccess { return; } - if (javaFile.isDirectory()) { - // TODO: javaFile.listFiles() return native.Array but it is java array and not a js/ts array. - var filesList:any = javaFile.listFiles(); - var filePaths = new Array(); + var filesList: any = javaFile.listFiles(); + var fileInfos = new Array<{ path: string; name: string; extension: string }>(); - var length = filesList.length, - i, - filePath; + var length = filesList.length, + i, + filePath, + info; - for (i = 0; i < length; i++) { - javaFile = filesList[i]; + for (i = 0; i < length; i++) { + javaFile = filesList[i]; - if (javaFile.isFile()) { - filePath = javaFile.getPath(); - filesList.push(filePath); - } + info = { + path: javaFile.getAbsolutePath(), + name: javaFile.getName() + }; + + if (javaFile.isFile()) { + info.extension = this.getFileExtension(info.path); } - if (onSuccess) { - onSuccess(filePaths); - } + fileInfos.push(info); + } + + if (onSuccess) { + onSuccess(fileInfos); } } catch (exception) { @@ -93,7 +92,13 @@ export class FileSystemAccess { public folderExists(path: string): boolean { var file = new java.io.File(path); - return file.exists() && file.isDirectory(); + var exists = file.exists(); + var dir = file.isDirectory(); + var isFile = file.isFile(); + var hidden = file.isHidden(); + + // return file.exists() && file.getCanonicalFile().isDirectory(); + return exists && dir; } public concatPath(left: string, right: string): string { @@ -128,7 +133,7 @@ export class FileSystemAccess { public deleteFolder(path: string, isKnown?: boolean, onSuccess?: () => any, onError?: (error: any) => any) { try { var javaFile = new java.io.File(path); - if (!javaFile.isDirectory()) { + if (!javaFile.getCanonicalFile().isDirectory()) { if (onError) { onError({ message: "The specified parameter is not a Folder entity." }); } @@ -162,7 +167,7 @@ export class FileSystemAccess { public emptyFolder(path: string, onSuccess?: () => any, onError?: (error: any) => any) { try { var javaFile = new java.io.File(path); - if (!javaFile.isDirectory()) { + if (!javaFile.getCanonicalFile().isDirectory()) { if (onError) { onError({ message: "The specified parameter is not a Folder entity." }); } @@ -184,20 +189,48 @@ export class FileSystemAccess { } } - 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 javaFile = new java.io.File(path); + if (!javaFile.exists()) { + if (onError) { + onError(new Error("The file to rename does not exist")); + } + + return; + } + + var newFile = new java.io.File(newPath); + if (newFile.exists()) { + if (onError) { + onError(new Error("A file with the same name already exists.")); + } + + return; + } + + if (!javaFile.renameTo(newFile)) { + if (onError) { + onError(new Error("Failed to rename file '" + path + "' to '" + newPath + "'")); + } + + return; + } + + if (onSuccess) { + onSuccess(); + } } public getDocumentsFolderPath(): string { var context = app_module.tk.ui.Application.current.android.context; var dir: java.io.File = context.getFilesDir(); - return dir.getPath(); + return dir.getAbsolutePath(); } public getTempFolderPath(): string { var context = app_module.tk.ui.Application.current.android.context; var dir: java.io.File = context.getCacheDir(); - return dir.getPath(); + return dir.getAbsolutePath(); } public readText(path: string, onSuccess: (content: string) => any, onError?: (error: any) => any) { @@ -242,11 +275,10 @@ export class FileSystemAccess { var javaFile = new java.io.File(path); var stream = new java.io.FileOutputStream(javaFile); var writer = new java.io.OutputStreamWriter(stream, this._encoding); - var bufferedWriter = new java.io.BufferedWriter(writer); - - bufferedWriter.write(content); - bufferedWriter.close(); + writer.write(content); + writer.close(); + if (onSuccess) { onSuccess(); } @@ -266,7 +298,7 @@ export class FileSystemAccess { for (i = 0; i < filesList.length; i++) { childFile = filesList[i]; - if (childFile.isDirectory()) { + if (childFile.getCanonicalFile().isDirectory()) { success = this.deleteFolderContent(childFile); if (!success) { break; @@ -279,25 +311,47 @@ export class FileSystemAccess { return success; } - private ensureFile(javaFile: java.io.File, onSuccess: (path: string) => any, onError?: (error: any) => any) { + private ensureFile(javaFile: java.io.File, isFolder: boolean, onError?: (error: any) => any): { path: string; name: string; extension: string } { try { if (!javaFile.exists()) { - if (!javaFile.createNewFile()) { + var created; + if (isFolder) { + created = javaFile.mkdirs(); + } else { + created = javaFile.createNewFile(); + } + + if (!created) { // TODO: unified approach for error messages if (onError) { - onError("Failed to create new file for path " + javaFile.getPath()); + onError("Failed to create new java File for path " + javaFile.getAbsolutePath()); } + + return undefined; + } else { + javaFile.setReadable(true); + javaFile.setWritable(true); } } - if (onSuccess) { - onSuccess(javaFile.getPath()); - } + var path = javaFile.getAbsolutePath(); + return { path: path, name: javaFile.getName(), extension: this.getFileExtension(path) }; } catch (exception) { // TODO: unified approach for error messages if (onError) { onError(exception); } + + return undefined; } } + + public getFileExtension(path: string): string { + var dotIndex = path.lastIndexOf("."); + if (dotIndex && dotIndex >= 0 && dotIndex < path.length) { + return path.substring(dotIndex); + } + + return undefined; + } } \ No newline at end of file diff --git a/FileSystem/file_system_access.d.ts b/FileSystem/file_system_access.d.ts index 269e319c9..4b7db87ba 100644 --- a/FileSystem/file_system_access.d.ts +++ b/FileSystem/file_system_access.d.ts @@ -2,23 +2,23 @@ //@hidden export declare class FileSystemAccess { - getReadonly(path: string): boolean; getLastModified(path: string): Date; - getParent(path: string, onError?: (error: any) => any): string; - getFile(path: string, onSuccess: (path: string) => any, onError?: (error: any) => any); - getFolder(path: string, onSuccess: (path: string) => any, onError?: (error: any) => any); - enumFiles(path: string, onSuccess: (files: Array) => any, onError?: (error: any) => any); + getParent(path: string, onError?: (error: any) => any): { path: string; name: string }; + getFile(path: string, onError?: (error: any) => any): { path: string; name: string; extension: string }; + getFolder(path: string, onError?: (error: any) => any): { path: string; name: string }; + enumFiles(path: string, onSuccess: (files: Array<{ path: string; name: string; extension: string }>) => any, onError?: (error: any) => any); fileExists(path: string): boolean; folderExists(path: string): boolean; concatPath(left: string, right: string): string; deleteFile(path: string, onSuccess?: () => any, onError?: (error: any) => any); deleteFolder(path: string, isKnown: boolean, onSuccess?: () => any, onError?: (error: any) => any); emptyFolder(path: string, onSuccess?: () => any, onError?: (error: any) => any): void; - rename(path: string, onSuccess?: () => any, onError?: (error: any) => any): void; + rename(path: string, newPath: string, onSuccess?: () => any, onError?: (error: any) => any): void; getDocumentsFolderPath(): string; getTempFolderPath(): string; readText(path: string, onSuccess: (content: string) => any, onError?: (error: any) => any); writeText(path: string, content: string, onSuccess?: () => any, onError?: (error: any) => any); + getFileExtension(path: string): string; } \ No newline at end of file