From 67928f7ddf43140a2f853badbda2bdb773c5c02a Mon Sep 17 00:00:00 2001 From: Ty Voliter Date: Tue, 13 Dec 2011 16:41:34 -0800 Subject: [PATCH 1/4] Updated nativeFileSystem to use new posix api Glenn submitted to the native-file-api branch --- src/NativeFileSystem.js | 144 ++++++++++++++++++++++++++++++---------- src/brackets.js | 61 ++++++++++++++++- 2 files changed, 168 insertions(+), 37 deletions(-) diff --git a/src/NativeFileSystem.js b/src/NativeFileSystem.js index 1efedc348e4..f31c4dca33f 100644 --- a/src/NativeFileSystem.js +++ b/src/NativeFileSystem.js @@ -16,17 +16,29 @@ window.NativeFileSystem = { title, initialPath, fileTypes, - resultCallback ) { + successCallback, + errorCallback ) { - if( !resultCallback ) + + if( !successCallback ) return null; - var files = brackets.file.showOpenDialog( allowMultipleSelection, + if( !errorCallback ) + return null; + + var files = brackets.fs.showOpenDialog( allowMultipleSelection, chooseDirectories, title, initialPath, fileTypes, - resultCallback ); + showOpenDialogCB ); + + function showOpenDialogCB( err, data ){ + if( ! err ) + successCallback( data ); + else + errorCallback( err ); + } }, @@ -38,21 +50,29 @@ window.NativeFileSystem = { */ requestNativeFileSystem: function( path, successCallback, errorCallback ){ - // TODO: assumes path is a directory right now. Need to error check - // TODO: don't actually need to get the listing here, but should verify the directory exists - var entryList = brackets.file.getDirectoryListing(path); - if (entryList) { - var files = JSON.parse(entryList); - var root = new DirectoryEntry( path ); - return root; - } - else { - return null; + // TODO: use stat instead to verify directory exists + brackets.fs.readdir(path, readdirCB); + + function readdirCB( err, data ){ + if( !err ){ + var root = new DirectoryEntry( path ); + successCallback( root ); + } + else{ + // TODO NJ: error translation + // errorCallback( error ); + } } } + + + + }; + + /** class: Entry * * @param {string} name @@ -73,7 +93,7 @@ Entry = function( fullPath, isDirectory) { this.name = pathParts.pop(); } - // IMPLEMENT LATERvar filesystem; + // IMPLEMENT LATER var filesystem; // IMPLEMENT LATER void moveTo (DirectoryEntry parent, optional DOMString newName, optional EntryCallback successCallback, optional ErrorCallback errorCallback); // IMPLEMENT LATER void copyTo (DirectoryEntry parent, optional DOMString newName, optional EntryCallback successCallback, optional ErrorCallback errorCallback); // IMPLEMENT LATER DOMString toURL (optional DOMString mimeType); @@ -139,31 +159,87 @@ DirectoryReader = function() { */ DirectoryReader.prototype.readEntries = function( successCallback, errorCallback ){ var rootPath = this._directory.fullPath; - var jsonList = brackets.file.getDirectoryListing( rootPath ); - var nameList = JSON.parse(jsonList); - - // Create entries for each name - var entries = []; - nameList.forEach(function(item){ - // Ignore names starting with "." - if (item.indexOf(".") != 0) { - var itemFullPath = rootPath + "/" + item; + var jsonList = brackets.fs.readdir( rootPath, readEntriesCB ); + + + function readEntriesCB( err, filelist ) { + if( ! err ){ - if( brackets.file.isDirectory( itemFullPath ) ) { - entries.push( new DirectoryEntry( itemFullPath ) ); - } - else { - entries.push( new FileEntry( itemFullPath ) ); - } + // Create entries for each name + var entries = []; + filelist.forEach(function(item){ + // Ignore names starting with "." + if (item.indexOf(".") != 0) { + var itemFullPath = rootPath + "/" + item; + + brackets.fs.stat( item, function( err, data) { + } + ) + + if( brackets.fs.isDirectory( itemFullPath ) ) { + entries.push( new DirectoryEntry( itemFullPath ) ); + } + else { + entries.push( new FileEntry( itemFullPath ) ); + } + } + }); + + successCallback( entries ); } - }); - - + else{ + // TODO NJ: error translation + // errorCallback( error ); + } + } - successCallback( entries ); + // TODO: error handling }; +/* +interface FileReader: EventTarget { + + // async read methods + // IMPLEMENT LATER void readAsArrayBuffer(Blob blob); + // IMPLEMENT LATER void readAsBinaryString(Blob blob); + void readAsText(Blob blob, optional DOMString encoding); + // IMPLEMENT LATER void readAsDataURL(Blob blob); + + // IMPLEMENT LATER void abort(); + + // states constants + var EMPTY = 0; + var LOADING = 1; + var DONE = 2; + + + var readyState; + get readyState() { return readyState; } + + // File or Blob data + var result; + get result() { return result; } + + var error; + get result() { return error; } + + + // event handler attributes + [TreatNonCallableAsNull] attribute Function? onloadstart; + [TreatNonCallableAsNull] attribute Function? onprogress; + [TreatNonCallableAsNull] attribute Function? onload; + [TreatNonCallableAsNull] attribute Function? onabort; + [TreatNonCallableAsNull] attribute Function? onerror; + [TreatNonCallableAsNull] attribute Function? onloadend; + +}; + +FileReader.prototype.readAsText = function(blob, encoding ){ + +} + +*/ diff --git a/src/brackets.js b/src/brackets.js index a2c9d89ab63..d2c617ec431 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -13,10 +13,50 @@ $(document).ready(function() { // Temporary button to test file directory traversa; $("#menu-file-open").click(function(){ if (!inBrowser) { - window.NativeFileSystem.showOpenDialog(false, true, "Choose a folder", null, null, showOpenDialogCallback); + window.NativeFileSystem.showOpenDialog( false, true, "Choose a folder", + null, null, + showOpenDialogSuccessCallback, + showOpenDialogErrorCallback); + + /* + // TEST CODE + var reader = new FileReader(); + reader.onerror = errorHandler; + + reader.onabort = function(e) { + alert('File read cancelled'); + }; + + reader.onloadstart = function(e) { + console.log( "loading" ); + }; + + + // Read in the image file as a binary string. + reader.readAsText(file); + + + function errorHandler(evt) { + switch(evt.target.error.code) { + case evt.target.error.NOT_FOUND_ERR: + alert('File Not Found!'); + break; + case evt.target.error.NOT_READABLE_ERR: + alert('File is not readable'); + break; + case evt.target.error.ABORT_ERR: + break; // noop + default: + alert('An error occurred reading this file.'); + }; + } + +*/ + } }); + // Implements the 'Run Tests' menu to bring up the Jasmine unit test window var testWindow = null; $("#menu-runtests").click(function(){ @@ -34,21 +74,36 @@ $(document).ready(function() { } }); - function showOpenDialogCallback( files ) { + function showOpenDialogErrorCallback( err ){ + console.log( err ) + } + + function showOpenDialogSuccessCallback( files ) { var folderName = files instanceof Array ? files[0] : files; + var nestingLevel = 0; if (folderName != "") { - var rootEntry = window.NativeFileSystem.requestNativeFileSystem( folderName, null, null ); // TODO: add callbacks + window.NativeFileSystem.requestNativeFileSystem( folderName, + requestNativeFileSystemSuccessCB, requestNativeFileSystemErrorCB ); // TODO: add callbacks + + } + + function requestNativeFileSystemSuccessCB( rootEntry ){ var nestingLevel = 0; if( rootEntry && rootEntry.isDirectory ) readDirectory( rootEntry ); } + function requestNativeFileSystemErrorCB( err){ + console.log( err ); + } + // Test directory traversal function readDirectory( entry ){ + var reader = entry.createReader(); reader.readEntries( dirReaderSuccessCB, dirReaderErrorCB); } From c44fcf73b15e6a612bad0f8f997a7d3fd15dc217 Mon Sep 17 00:00:00 2001 From: Ty Voliter Date: Tue, 13 Dec 2011 17:02:53 -0800 Subject: [PATCH 2/4] fixed failing unit test in NativeFileSystem-test.js --- src/NativeFileSystem.js | 133 +++++++++-------------------- test/spec/NativeFileSystem-test.js | 40 +++++---- 2 files changed, 63 insertions(+), 110 deletions(-) diff --git a/src/NativeFileSystem.js b/src/NativeFileSystem.js index f31c4dca33f..171447cb921 100644 --- a/src/NativeFileSystem.js +++ b/src/NativeFileSystem.js @@ -11,7 +11,7 @@ window.NativeFileSystem = { * @param {function} resultCallback * @constructor */ - showOpenDialog: function ( allowMultipleSelection, + showOpenDialog: function ( allowMultipleSelection, chooseDirectories, title, initialPath, @@ -20,25 +20,22 @@ window.NativeFileSystem = { errorCallback ) { - if( !successCallback ) - return null; + if( !successCallback || ! errorCallback) + return; - if( !errorCallback ) - return null; - - var files = brackets.fs.showOpenDialog( allowMultipleSelection, - chooseDirectories, - title, - initialPath, - fileTypes, - showOpenDialogCB ); + var files = brackets.fs.showOpenDialog( allowMultipleSelection, + chooseDirectories, + title, + initialPath, + fileTypes, + function( err, data ){ + if( ! err ) + successCallback( data ); + else + errorCallback( err ); + }); - function showOpenDialogCB( err, data ){ - if( ! err ) - successCallback( data ); - else - errorCallback( err ); - } + }, @@ -51,23 +48,18 @@ window.NativeFileSystem = { requestNativeFileSystem: function( path, successCallback, errorCallback ){ // TODO: use stat instead to verify directory exists - brackets.fs.readdir(path, readdirCB); - - function readdirCB( err, data ){ - if( !err ){ - var root = new DirectoryEntry( path ); - successCallback( root ); - } - else{ - // TODO NJ: error translation - // errorCallback( error ); - } - } + brackets.fs.stat(path, function( err, data ){ + if( !err ){ + var root = new DirectoryEntry( path ); + successCallback( root ); + } + else{ + // TODO NJ: error translation + // errorCallback( error ); + } + }); } - - - }; @@ -159,12 +151,8 @@ DirectoryReader = function() { */ DirectoryReader.prototype.readEntries = function( successCallback, errorCallback ){ var rootPath = this._directory.fullPath; - var jsonList = brackets.fs.readdir( rootPath, readEntriesCB ); - - - function readEntriesCB( err, filelist ) { + var jsonList = brackets.fs.readdir( rootPath, function( err, filelist ) { if( ! err ){ - // Create entries for each name var entries = []; filelist.forEach(function(item){ @@ -172,16 +160,19 @@ DirectoryReader.prototype.readEntries = function( successCallback, errorCallback if (item.indexOf(".") != 0) { var itemFullPath = rootPath + "/" + item; - brackets.fs.stat( item, function( err, data) { - } - ) + brackets.fs.stat( itemFullPath, function( err, statData) { - if( brackets.fs.isDirectory( itemFullPath ) ) { - entries.push( new DirectoryEntry( itemFullPath ) ); - } - else { - entries.push( new FileEntry( itemFullPath ) ); - } + if( !err ){ + if( statData.isDirectory( itemFullPath ) ) + entries.push( new DirectoryEntry( itemFullPath ) ); + else if( statData.isFile( itemFullPath ) ) + entries.push( new FileEntry( itemFullPath ) ); + } + else { + // TODO NJ: handle errors + } + + }) } }); @@ -191,55 +182,9 @@ DirectoryReader.prototype.readEntries = function( successCallback, errorCallback // TODO NJ: error translation // errorCallback( error ); } - } - - - - // TODO: error handling -}; - -/* -interface FileReader: EventTarget { - - // async read methods - // IMPLEMENT LATER void readAsArrayBuffer(Blob blob); - // IMPLEMENT LATER void readAsBinaryString(Blob blob); - void readAsText(Blob blob, optional DOMString encoding); - // IMPLEMENT LATER void readAsDataURL(Blob blob); - - // IMPLEMENT LATER void abort(); - - // states constants - var EMPTY = 0; - var LOADING = 1; - var DONE = 2; - - - var readyState; - get readyState() { return readyState; } - - // File or Blob data - var result; - get result() { return result; } - - var error; - get result() { return error; } - - - // event handler attributes - [TreatNonCallableAsNull] attribute Function? onloadstart; - [TreatNonCallableAsNull] attribute Function? onprogress; - [TreatNonCallableAsNull] attribute Function? onload; - [TreatNonCallableAsNull] attribute Function? onabort; - [TreatNonCallableAsNull] attribute Function? onerror; - [TreatNonCallableAsNull] attribute Function? onloadend; - + }); }; -FileReader.prototype.readAsText = function(blob, encoding ){ - -} -*/ diff --git a/test/spec/NativeFileSystem-test.js b/test/spec/NativeFileSystem-test.js index 98e10b69156..ca3c9f919a1 100644 --- a/test/spec/NativeFileSystem-test.js +++ b/test/spec/NativeFileSystem-test.js @@ -36,22 +36,30 @@ describe("NativeFileSystem", function(){ var entries = null; var readComplete = false; - var nfs = window.NativeFileSystem.requestNativeFileSystem(path); - var reader = nfs.createReader(); - - var successCallback = function(e) { entries = e; readComplete = true; } - // TODO: not sure what parameters error callback will take because it's not implemented yet - var errorCallback = function() { readComplete = true; } - - reader.readEntries(successCallback, errorCallback); - - waitsFor(function() { return readComplete; }, 1000); - - runs(function() { - expect(entries).toContainDirectoryWithName("dir1"); - expect(entries).toContainFileWithName("file1"); - expect(entries).not.toContainFileWithName("file2"); - }); + var nfs = window.NativeFileSystem.requestNativeFileSystem(path, requestNativeFileSystemSuccessCB, requestNativeFileSystemErrorCB ); + + + function requestNativeFileSystemSuccessCB( nfs ){ + var reader = nfs.createReader(); + + var successCallback = function(e) { entries = e; readComplete = true; } + // TODO: not sure what parameters error callback will take because it's not implemented yet + var errorCallback = function() { readComplete = true; } + + reader.readEntries(successCallback, errorCallback); + + waitsFor(function() { return readComplete; }, 1000); + + runs(function() { + expect(entries).toContainDirectoryWithName("dir1"); + expect(entries).toContainFileWithName("file1"); + expect(entries).not.toContainFileWithName("file2"); + }); + } + + function requestNativeFileSystemErrorCB( err){ + // TODO: what should be done here? + } }); }); }); From 2df3ec86218be280f3320152f2e6e5ea93fbfb7a Mon Sep 17 00:00:00 2001 From: Ty Voliter Date: Tue, 13 Dec 2011 17:22:13 -0800 Subject: [PATCH 3/4] removing sample failing test SampleTest.js and making errorCallback in showOpenDialog optional --- src/NativeFileSystem.js | 6 +++--- test/SpecRunner.html | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/NativeFileSystem.js b/src/NativeFileSystem.js index 171447cb921..fad541f6e21 100644 --- a/src/NativeFileSystem.js +++ b/src/NativeFileSystem.js @@ -20,7 +20,7 @@ window.NativeFileSystem = { errorCallback ) { - if( !successCallback || ! errorCallback) + if( !successCallback ) return; var files = brackets.fs.showOpenDialog( allowMultipleSelection, @@ -31,8 +31,8 @@ window.NativeFileSystem = { function( err, data ){ if( ! err ) successCallback( data ); - else - errorCallback( err ); + else if( errorCallback ) + errorCallback( err ); }); diff --git a/test/SpecRunner.html b/test/SpecRunner.html index 1e383df4d8b..0a6f2b9e959 100644 --- a/test/SpecRunner.html +++ b/test/SpecRunner.html @@ -24,7 +24,6 @@ - From fbbdc2a8a58c279a0ffdc65aa2b183e6dd63d79e Mon Sep 17 00:00:00 2001 From: njadbe Date: Tue, 13 Dec 2011 18:01:30 -0800 Subject: [PATCH 4/4] Added error handling for directory read and unit tests for some errors --- src/NativeFileSystem.js | 137 ++++++++++++++++++++--------- test/spec/NativeFileSystem-test.js | 50 ++++++++--- test/spec/SampleTest.js | 6 -- 3 files changed, 134 insertions(+), 59 deletions(-) diff --git a/src/NativeFileSystem.js b/src/NativeFileSystem.js index 171447cb921..42cd8646861 100644 --- a/src/NativeFileSystem.js +++ b/src/NativeFileSystem.js @@ -18,8 +18,7 @@ window.NativeFileSystem = { fileTypes, successCallback, errorCallback ) { - - + if( !successCallback || ! errorCallback) return; @@ -32,9 +31,8 @@ window.NativeFileSystem = { if( ! err ) successCallback( data ); else - errorCallback( err ); - }); - + errorCallback(NativeFileSystem._nativeToFileError(err)); + }); }, @@ -46,25 +44,55 @@ window.NativeFileSystem = { * @param {function} errorCallback */ requestNativeFileSystem: function( path, successCallback, errorCallback ){ - - // TODO: use stat instead to verify directory exists brackets.fs.stat(path, function( err, data ){ - if( !err ){ - var root = new DirectoryEntry( path ); - successCallback( root ); - } - else{ - // TODO NJ: error translation - // errorCallback( error ); - } - }); - } + if( !err ){ + var root = new DirectoryEntry( path ); + successCallback( root ); + } + else{ + errorCallback(NativeFileSystem._nativeToFileError(err)); + } + }); + }, + _nativeToFileError: function(nativeErr) { + // The HTML file spec says SECURITY_ERR is a catch-all to be used in situations + // not covered by other error codes. + var error = FileError.SECURITY_ERR; + + switch (nativeErr) { + // We map ERR_UNKNOWN and ERR_INVALID_PARAMS to SECURITY_ERR, + // since there aren't specific mappings for these. + case brackets.fs.ERR_UNKNOWN: + case brackets.fs.ERR_INVALID_PARAMS: + error = FileError.SECURITY_ERR; + break; + + case brackets.fs.ERR_NOT_FOUND: + error = FileError.NOT_FOUND_ERR; + break; + case brackets.fs.ERR_CANT_READ: + error = FileError.NOT_READABLE_ERR; + break; + + // It might seem like you should use FileError.ENCODING_ERR for this, + // but according to the spec that's for malformed URLs. + case brackets.fs.ERR_UNSUPPORTED_ENCODING: + error = FileError.SECURITY_ERR; + break; + + case brackets.fs.ERR_CANT_WRITE: + error = FileError.NO_MODIFICATION_ALLOWED_ERR; + break; + case brackets.fs.ERR_OUT_OF_SPACE: + error = FileError.QUOTA_EXCEEDED_ERR; + break; + } + + return new FileError(error); + } }; - - - /** class: Entry * * @param {string} name @@ -143,7 +171,7 @@ DirectoryReader = function() { }; -/** readEntires +/** readEntries * * @param {function} successCallback * @param {function} errorCallback @@ -156,35 +184,58 @@ DirectoryReader.prototype.readEntries = function( successCallback, errorCallback // Create entries for each name var entries = []; filelist.forEach(function(item){ - // Ignore names starting with "." - if (item.indexOf(".") != 0) { - var itemFullPath = rootPath + "/" + item; + var itemFullPath = rootPath + "/" + item; + + brackets.fs.stat( itemFullPath, function( err, statData) { + + if( !err ){ + if( statData.isDirectory( itemFullPath ) ) + entries.push( new DirectoryEntry( itemFullPath ) ); + else if( statData.isFile( itemFullPath ) ) + entries.push( new FileEntry( itemFullPath ) ); + } + else { + errorCallback(NativeFileSystem._nativeToFileError(err)); + } - brackets.fs.stat( itemFullPath, function( err, statData) { - - if( !err ){ - if( statData.isDirectory( itemFullPath ) ) - entries.push( new DirectoryEntry( itemFullPath ) ); - else if( statData.isFile( itemFullPath ) ) - entries.push( new FileEntry( itemFullPath ) ); - } - else { - // TODO NJ: handle errors - } - - }) - } - }); + }) + }); successCallback( entries ); } else{ - // TODO NJ: error translation - // errorCallback( error ); + errorCallback(NativeFileSystem._nativeToFileError(err)); } }); }; +/** class: FileError + * + * Implementation of HTML file API error code return class. Note that the + * various HTML file API specs are not consistent in their definition of + * some error code values like ABORT_ERR; I'm using the definitions from + * the Directories and System spec since it seems to be the most + * comprehensive. + * + * @constructor + * @param {number} code The error code to return with this FileError. Must be + * one of the codes defined in the FileError class. + */ +FileError = function(code) { + this.code = code || 0; +}; - - +$.extend(FileError, { + NOT_FOUND_ERR: 1, + SECURITY_ERR: 2, + ABORT_ERR: 3, + NOT_READABLE_ERR: 4, + ENCODING_ERR: 5, + NO_MODIFICATION_ALLOWED_ERR: 6, + INVALID_STATE_ERR: 7, + SYNTAX_ERR: 8, + INVALID_MODIFICATION_ERR: 9, + QUOTA_EXCEEDED_ERR: 10, + TYPE_MISMATCH_ERR: 11, + PATH_EXISTS_ERR: 12 +}); diff --git a/test/spec/NativeFileSystem-test.js b/test/spec/NativeFileSystem-test.js index ca3c9f919a1..9c9cfba4655 100644 --- a/test/spec/NativeFileSystem-test.js +++ b/test/spec/NativeFileSystem-test.js @@ -21,9 +21,6 @@ describe("NativeFileSystem", function(){ return false; } }); - }); - - it("should read a directory from disk", function() { //TODO: Make this relative -- right now, asking for "." gives "/" //Want to be able to simply use: @@ -32,13 +29,14 @@ describe("NativeFileSystem", function(){ path = path.substr("file://".length); path = path.substr(0,path.lastIndexOf("/")+1); path = path + "spec/NativeFileSystem-test-files"; + this.path = path; + }); + it("should read a directory from disk", function() { var entries = null; var readComplete = false; - var nfs = window.NativeFileSystem.requestNativeFileSystem(path, requestNativeFileSystemSuccessCB, requestNativeFileSystemErrorCB ); - - + var nfs = window.NativeFileSystem.requestNativeFileSystem(this.path, requestNativeFileSystemSuccessCB); function requestNativeFileSystemSuccessCB( nfs ){ var reader = nfs.createReader(); @@ -55,11 +53,43 @@ describe("NativeFileSystem", function(){ expect(entries).toContainFileWithName("file1"); expect(entries).not.toContainFileWithName("file2"); }); - } + } + }); + + it("should return an error if the directory doesn't exist", function() { + var successCalled = false, errorCalled = false, error = null; + window.NativeFileSystem.requestNativeFileSystem(this.path + '/nonexistent-dir', function(data) { + successCalled = true; + }, function(err) { + errorCalled = true; + error = err; + }); + + waitsFor(function() { return successCalled || errorCalled; }, 1000); - function requestNativeFileSystemErrorCB( err){ - // TODO: what should be done here? - } + runs(function() { + expect(successCalled).toBe(false); + expect(errorCalled).toBe(true); + expect(error.code).toBe(FileError.NOT_FOUND_ERR); + }); }); + + it("should return an error if you pass a bad parameter", function() { + var successCalled = false, errorCalled = false, error = null; + window.NativeFileSystem.requestNativeFileSystem(0xDEADBEEF, function(data) { + successCalled = true; + }, function(err) { + errorCalled = true; + error = err; + }); + + waitsFor(function() { return successCalled || errorCalled; }, 1000); + + runs(function() { + expect(successCalled).toBe(false); + expect(errorCalled).toBe(true); + expect(error.code).toBe(FileError.SECURITY_ERR); + }); + }) }); }); diff --git a/test/spec/SampleTest.js b/test/spec/SampleTest.js index 56d8db8fb8b..bfb4f862659 100644 --- a/test/spec/SampleTest.js +++ b/test/spec/SampleTest.js @@ -33,9 +33,3 @@ describe("Brackets", function(){ }); }); }); - -describe("Failure", function() { - it("is expected sometimes", function() { - expect("failure").toEqual("sometimes"); - }); -}); \ No newline at end of file