diff --git a/src/NativeFileSystem.js b/src/NativeFileSystem.js index 5ee46b12eea..801d49a1c83 100644 --- a/src/NativeFileSystem.js +++ b/src/NativeFileSystem.js @@ -4,7 +4,6 @@ var NativeFileSystem = { - /** showOpenDialog * * @param {bool} allowMultipleSelection @@ -14,29 +13,29 @@ var NativeFileSystem = { * @param {string[]} fileTypes * @param {function} resultCallback * @constructor - */ + */ showOpenDialog: function ( allowMultipleSelection, - chooseDirectories, + chooseDirectories, title, initialPath, fileTypes, successCallback, - errorCallback ) { + errorCallback ) + { if( !successCallback ) return; - + var files = brackets.fs.showOpenDialog( allowMultipleSelection, - chooseDirectories, + chooseDirectories, title, initialPath, fileTypes, - function( err, data ){ + function( err, data ) { if( ! err ) successCallback( data ); else if (errorCallback) errorCallback(NativeFileSystem._nativeToFileError(err)); - }); - + }); }, /** requestNativeFileSystem @@ -54,14 +53,14 @@ var NativeFileSystem = { else if (errorCallback) { 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. @@ -69,28 +68,27 @@ var NativeFileSystem = { 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. + // 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; + case brackets.fs.PATH_EXISTS_ERR: + error = FileError.PATH_EXISTS_ERR; + break; } - return new NativeFileSystem.FileError(error); } }; @@ -106,7 +104,7 @@ NativeFileSystem.Entry = function( fullPath, isDirectory) { this.isFile = !isDirectory; // IMPLEMENT LATER void getMetadata (MetadataCallback successCallback, optional ErrorCallback errorCallback); this.fullPath = fullPath; - + // Extract name from fullPath this.name = null; // default if extraction fails if( fullPath ){ @@ -114,7 +112,7 @@ NativeFileSystem.Entry = function( fullPath, isDirectory) { if( pathParts.length > 0 ) this.name = pathParts.pop(); } - + // 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); @@ -130,12 +128,160 @@ NativeFileSystem.Entry = function( fullPath, isDirectory) { * @param {string} name * @constructor * @extends {Entry} - */ + */ NativeFileSystem.FileEntry = function( name ) { NativeFileSystem.Entry.call(this, name, false); - + // TODO: make FileEntry actually inherit from Entry by modifying prototype. I don't know how to do this yet. - + +}; + +/** createWriter + * + * Creates a new FileWriter associated with the file that this FileEntry represents. + * + * @param {function} successCallback + * @param {function} errorCallback + */ +NativeFileSystem.FileEntry.prototype.createWriter = function( successCallback, errorCallback ) { + var fileEntry = this; + + // [NoInterfaceObject] + // interface FileWriter : FileSaver + var _FileWriter = function( data ) { + NativeFileSystem.FileSaver.call(this, data); + + // _FileWriter private memeber vars + this._length = 0; + this._position = 0; + + // initialize file length + // TODO (jasonsj): handle async + brackets.fs.readFile( fileEntry.fullPath, "utf8", function(err, contents) { + if ( contents ) + this._length = contents.length; + }); + }; + + _FileWriter.prototype.length = function( ) { + return this._length; + }; + + _FileWriter.prototype.position = function( ) { + return this._position; + }; + + // TODO (jasonsj): handle Blob data instead of string + _FileWriter.prototype.write = function( data ) { + if ( !data ) + throw new Error(); + + if ( this.readyState === NativeFileSystem.FileSaver.WRITING ) + throw new NativeFileSystem.FileException( NativeFileSystem.FileException.INVALID_STATE_ERR ); + + this.readyState = NativeFileSystem.FileSaver.WRITING; + + if ( this.onwritestart ) { + // TODO (jasonsj): progressevent + this.onwritestart(); + } + + var self = this; + + brackets.fs.writeFile( fileEntry.fullPath, data, "utf8", function( err ) { + if ( self.onerror ) { + self.onerror ( NativeFileSystem._nativeToFileError( err ) ); + + // TODO (jasonsj): partial write, update length and position + } + else { + // successful completion of a write + self.position += data.size; + } + + // DONE is set regardless of error + this.readyState = NativeFileSystem.FileSaver.DONE; + + if ( self.onwrite ) { + // TODO (jasonsj): progressevent + self.onwrite(); + } + + if ( this.onwriteend ) { + // TODO (jasonsj): progressevent + self.onwriteend(); + } + }); + }; + + _FileWriter.prototype.seek = function( offset ) { + }; + + _FileWriter.prototype.truncate = function( size ) { + }; + + successCallback( new _FileWriter() ); +}; + + +NativeFileSystem.FileException = function ( code ){ + this.code = code || 0; +}; + +// FileException constants +Object.defineProperties(NativeFileSystem.FileException, + { NOT_FOUND_ERR: { value: 1 } + , SECURITY_ERR: { value: 2 } + , ABORT_ERR: { value: 3 } + , NOT_READABLE_ERR: { value: 4 } + , ENCODING_ERR: { value: 5 } + , NO_MODIFICATION_ALLOWED_ERR: { value: 6 } + , INVALID_STATE_ERR: { value: 7 } + , SYNTAX_ERR: { value: 8 } + , QUOTA_EXCEEDED_ERR: { value: 10 } +}); + +/** class: FileSaver + * + * @param {Blob} data + * @constructor + */ +NativeFileSystem.FileSaver = function( data ) { + // FileSaver private member vars + this._data = data; + this._readyState = NativeFileSystem.FileSaver.INIT; + this._error = null; +}; + +// FileSaver constants +Object.defineProperties(NativeFileSystem.FileSaver, + { INIT: { value: 1 } + , WRITING: { value: 2 } + , DONE: { value: 3 } +}); + +// FileSaver methods + +// TODO (jasonsj): http://dev.w3.org/2009/dap/file-system/file-writer.html#widl-FileSaver-abort-void +NativeFileSystem.FileSaver.prototype.abort = function() { + // If readyState is DONE or INIT, terminate this overall series of steps without doing anything else.. + if (_readyState == FileSaver.INIT || _readyState == FileSaver.DONE) + return; + + // Terminate any steps having to do with writing a file. + + // Set the error attribute to a FileError object with the code ABORT_ERR. + _error = new NativeFileSystem.FileError(FileError.ABORT_ERR); + + // Set readyState to DONE. + _readyState = FileSaver.DONE; + + // Dispatch a progress event called abort + // Dispatch a progress event called writeend + // Stop dispatching any further progress events. + // Terminate this overall set of steps. + + return err; }; /** file @@ -146,9 +292,9 @@ NativeFileSystem.FileEntry = function( name ) { * @param {function} errorCallback */ NativeFileSystem.FileEntry.prototype.file = function( successCallback, errorCallback ){ - var newFile = new NativeFileSystem.File( this ); + var newFile = new NativeFileSystem.File( this ); successCallback( newFile ); - + // TODO Ty: error handling // errorCallback }; @@ -156,18 +302,18 @@ NativeFileSystem.FileEntry.prototype.file = function( successCallback, errorCall /* TODO Jason NativeFileSystem.FileEntry.prototype.createfileerror = function( successCallback, errorCallback ){ - -}; */ +}; +*/ /** class: DirectoryEntry * * @constructor * @param {string} name * @extends {Entry} - */ + */ NativeFileSystem.DirectoryEntry = function( name ) { NativeFileSystem.Entry.call(this, name, true); - + // TODO: make DirectoryEntry actually inherit from Entry by modifying prototype. I don't know how to do this yet. // IMPLEMENT LATERvoid getFile (DOMString path, optional Flags options, optional EntryCallback successCallback, optional ErrorCallback errorCallback); @@ -179,15 +325,95 @@ NativeFileSystem.DirectoryEntry = function( name ) { NativeFileSystem.DirectoryEntry.prototype.createReader = function() { var dirReader = new NativeFileSystem.DirectoryReader(); dirReader._directory = this; - + return dirReader; }; +/** + * Creates or looks up a file. + * http://dev.w3.org/2009/dap/file-system/pub/FileSystem/#widl-DirectoryEntry-getFile + * + * @param {string} path Either an absolute path or a relative path from this + * DirectoryEntry to the file to be looked up or created. It is an error + * to attempt to create a file whose immediate parent does not yet + * exist. + * @param {object} options + * @param {function(entry)} successCallback + * @param {function(err)} errorCallback + */ +NativeFileSystem.DirectoryEntry.prototype.getFile = function( path, options, successCallback, errorCallback ) { + // TODO (jasonsj): handle absolute paths + var fileFullPath = this.fullPath + "/" + path; + + var createFileEntry = function () { + if ( successCallback ) { + successCallback( new NativeFileSystem.FileEntry( fileFullPath ) ); + } + } + + var createFileError = function ( err ) { + if ( errorCallback ) { + errorCallback( NativeFileSystem._nativeToFileError( err ) ); + } + } + + // Use stat() to check if file exists + brackets.fs.stat( fileFullPath, function( err, stats ) { + if ( ( err === brackets.fs.NO_ERROR ) ) { + // NO_ERROR implies the path already exists + + // throw error if the file the path is a directory + if ( stats.isDirectory() ) { + if ( errorCallback ) { + errorCallback( new NativeFileSystem.FileError( FileError.TYPE_MISMATCH_ERR ) ); + } + + return; + } + + // throw error if the file exists but create is exclusive + if ( options.create && options.exclusive ) { + if ( errorCallback ) { + errorCallback( new NativeFileSystem.FileError( FileError.PATH_EXISTS_ERR ) ); + } + + return; + } + + // Create a file entry for the existing file. If create == true, + // a file entry is created without error. + createFileEntry(); + } else if ( err === brackets.fs.ERR_NOT_FOUND ) { + // ERR_NOT_FOUND implies we write a new, empty file + + // create the file + if ( options.create ) { + brackets.fs.writeFile( fileFullPath, "", "utf8", function( err ) { + if ( err ) + createFileError( err ); + else + createFileEntry(); + }); + + return; + } + + // throw error if file not found and the create == false + if ( errorCallback ) { + errorCallback( new NativeFileSystem.FileError( FileError.NOT_FOUND_ERR ) ); + } + } else { + // all other brackets.fs.stat() errors + createFileError( err ); + } + }); +}; + /** class: DirectoryReader - */ + */ NativeFileSystem.DirectoryReader = function() { - + }; @@ -196,7 +422,7 @@ NativeFileSystem.DirectoryReader = function() { * @param {function} successCallback * @param {function} errorCallback * @returns {Entry[]} - */ + */ NativeFileSystem.DirectoryReader.prototype.readEntries = function( successCallback, errorCallback ){ var rootPath = this._directory.fullPath; var jsonList = brackets.fs.readdir( rootPath, function( err, filelist ) { @@ -205,57 +431,57 @@ NativeFileSystem.DirectoryReader.prototype.readEntries = function( successCallba var entries = []; filelist.forEach(function(item){ var itemFullPath = rootPath + "/" + item; - + brackets.fs.stat( itemFullPath, function( err, statData) { - + if( !err ){ if( statData.isDirectory() ) entries.push( new NativeFileSystem.DirectoryEntry( itemFullPath ) ); - else if( statData.isFile() ) + else if( statData.isFile() ) entries.push( new NativeFileSystem.FileEntry( itemFullPath ) ); } else if (errorCallback) { errorCallback(NativeFileSystem._nativeToFileError(err)); } - - }) + + }) }); - successCallback( entries ); + successCallback( entries ); } else if (errorCallback) { errorCallback(NativeFileSystem._nativeToFileError(err)); } - }); + }); }; /** class: FileReader * * @extends {EventTarget} - */ + */ NativeFileSystem.FileReader = function() { // Todo Ty: this classes should extend EventTarget - + // async read methods // IMPLEMENT LATER void readAsArrayBuffer(Blob blob); // IMPLEMENT LATER void readAsBinaryString(Blob blob); // IMPLEMENT LATER void readAsDataURL(Blob blob); - + // IMPLEMENT LATER void abort(); - + // states this.EMPTY = 0; this.LOADING = 1; this.DONE = 2; - + // readyState is read only this.readyState = this.EMPTY; - + // File or Blob data // IMPLEMENT LATER readonly attribute any result; // IMPLEMENT LATER readonly attribute DOMError error; - + // event handler attributes this.onloadstart = null; this.onprogress = null; @@ -264,41 +490,41 @@ NativeFileSystem.FileReader = function() { this.onerror = null; this.onloadend = null; - + }; /** readAsText * * @param {Blob} blob * @param {string} encoding - */ + */ NativeFileSystem.FileReader.prototype.readAsText = function( blob, encoding) { var self = this; if( !encoding ) encoding = "utf-8"; - + if( this.readyState == this.LOADING ) throw new InvalidateStateError(); - + this.readyState = this.LOADING; - + if( this.onloadstart ) this.onloadstart(); // todo params - + brackets.fs.readFile( blob._fullPath, encoding, function( err, data) { - + // TODO: the event objects passed to these event handlers is fake and incomplete right now - var fakeEvent = + var fakeEvent = { loaded: 0 , total: 0 }; - + // The target for this event is the FileReader and the data/err result is stored in the FileReader fakeEvent.target = this; this.result = data; this.error = NativeFileSystem._nativeToFileError(err);; - + if( err ){ this.readyState = this.DONE; if( self.onerror ){ @@ -307,26 +533,26 @@ NativeFileSystem.FileReader.prototype.readAsText = function( blob, encoding) { } else{ this.readyState = this.DONE; - + // TODO: this should be the file/blob size, but we don't have code to get that yet, so for know assume a file size of 1 // and since we read the file in one go, assume 100% after the first read fakeEvent.loaded = 1; fakeEvent.total = 1; - + if( self.onprogress ) - self.onprogress(fakeEvent); - + self.onprogress(fakeEvent); + // TODO: this.onabort not currently supported since our native implementation doesn't support it // if( self.onabort ) - // self.onabort(fakeEvent); - + // self.onabort(fakeEvent); + if( self.onload ) self.onload( fakeEvent ); - + if( self.onloadend ) self.onloadend(); } - + }); }; @@ -334,18 +560,18 @@ NativeFileSystem.FileReader.prototype.readAsText = function( blob, encoding) { * * @constructor * param {Entry} entry - */ + */ NativeFileSystem.Blob = function ( fullPath ){ this._fullPath = fullPath; // IMPLEMENT LATER readonly attribute unsigned long long size; // IMPLEMENT LATER readonly attribute DOMString type; - + //slice Blob into byte-ranged chunks - + // IMPLEMENT LATER Blob slice(optional long long start, // optional long long end, - // optional DOMString contentType); + // optional DOMString contentType); }; /** class: File @@ -353,11 +579,11 @@ NativeFileSystem.Blob = function ( fullPath ){ * @constructor * param {Entry} entry * @extends {Blob} - */ + */ NativeFileSystem.File = function ( entry ){ NativeFileSystem.Blob.call( this, entry.fullPath ); - //IMPLEMENT LATER get name() { return this.entry.name; } + // IMPLEMENT LATER get name() { return this.entry.name; } // IMPLEMENT LATER get lastModifiedDate() { return } use stat to get mod date }; @@ -373,7 +599,6 @@ NativeFileSystem.File = function ( entry ){ * @param {number} code The error code to return with this FileError. Must be * one of the codes defined in the FileError class. */ -NativeFileSystem.FileError = function(code) { +NativeFileSystem.FileError = function( code ) { this.code = code || 0; -}; - +}; \ No newline at end of file diff --git a/test/spec/NativeFileSystem-test.js b/test/spec/NativeFileSystem-test.js index e5527b55e15..f43f242bcc2 100644 --- a/test/spec/NativeFileSystem-test.js +++ b/test/spec/NativeFileSystem-test.js @@ -1,220 +1,473 @@ describe("NativeFileSystem", function(){ - beforeEach(function() { - this.path = SpecRunnerUtils.getTestPath("/spec/NativeFileSystem-test-files"); - }); - - describe("Reading a directory", function() { - beforeEach(function() { - this.addMatchers({ - toContainDirectoryWithName: function(expected) { - for (var i = 0 ; i < this.actual.length; ++i) { - if (this.actual[i].isDirectory && this.actual[i].name === expected) { - return true; - } - } - return false; - } - , toContainFileWithName: function(expected) { - for (var i = 0 ; i < this.actual.length; ++i) { - if (this.actual[i].isFile && this.actual[i].name === expected) { - return true; - } - } - return false; - } - }); + this.path = SpecRunnerUtils.getTestPath("/spec/NativeFileSystem-test-files"); + this.file1content = "Here is file1\n"; }); - it("should read a directory from disk", function() { - var entries = null; - var readComplete = false; - - var nfs = NativeFileSystem.requestNativeFileSystem(this.path, requestNativeFileSystemSuccessCB); - 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"); - }); - } - }); - - it("should return an error if the directory doesn't exist", function() { - var successCalled = false, errorCalled = false, error = null; - NativeFileSystem.requestNativeFileSystem(this.path + '/nonexistent-dir', 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.NOT_FOUND_ERR); + describe("Reading a directory", function() { + + beforeEach(function() { + this.addMatchers({ + toContainDirectoryWithName: function(expected) { + for (var i = 0 ; i < this.actual.length; ++i) { + if (this.actual[i].isDirectory && this.actual[i].name === expected) { + return true; + } + } + return false; + } + , toContainFileWithName: function(expected) { + for (var i = 0 ; i < this.actual.length; ++i) { + if (this.actual[i].isFile && this.actual[i].name === expected) { + return true; + } + } + return false; + } + }); }); - }); - - it("should return an error if you pass a bad parameter", function() { - var successCalled = false, errorCalled = false, error = null; - 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); - }); - }); - - it("should be okay to not pass an error callback", function() { - var entries = null; - NativeFileSystem.requestNativeFileSystem(this.path, function(data) { - entries = data; - }); - - waitsFor(function() { return entries != null; }, 1000); - - runs(function() { - expect(entries).not.toBe(null); + + it("should read a directory from disk", function() { + var entries = null; + var readComplete = false; + + var nfs = NativeFileSystem.requestNativeFileSystem(this.path, requestNativeFileSystemSuccessCB); + 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"); + }); + } }); - }); - }); - - describe("Reading a file", function() { - it("should read a file from disk", function() { - var gotFile = false, readFile = false, gotError = false, content; - var fileEntry = new NativeFileSystem.FileEntry(this.path + "/file1"); - fileEntry.file(function(file) { - gotFile = true; - var reader = new NativeFileSystem.FileReader(); - reader.onload = function(event) { - readFile = true; - content = event.target.result; - }; - reader.onerror = function(event) { - gotError = true; - }; - reader.readAsText(file, "utf8"); - }); - - waitsFor(function() { return gotFile && readFile; }, 1000); - - runs(function() { - expect(gotFile).toBe(true); - expect(readFile).toBe(true); - expect(gotError).toBe(false); - expect(content).toBe("Here is file1\n"); + + it("should return an error if the directory doesn't exist", function() { + var successCalled = false, errorCalled = false, error = null; + NativeFileSystem.requestNativeFileSystem(this.path + '/nonexistent-dir', 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.NOT_FOUND_ERR); + }); + }); + + it("should return an error if you pass a bad parameter", function() { + var successCalled = false, errorCalled = false, error = null; + 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); + }); + }); + + it("should be okay to not pass an error callback", function() { + var entries = null; + NativeFileSystem.requestNativeFileSystem(this.path, function(data) { + entries = data; + }); + + waitsFor(function() { return entries != null; }, 1000); + + runs(function() { + expect(entries).not.toBe(null); + }); }); }); - - it("should return an error if the file is not found", function() { - var gotFile = false, readFile = false, errorCode; - var fileEntry = new NativeFileSystem.FileEntry(this.path + "/idontexist"); - fileEntry.file(function(file) { - gotFile = true; - var reader = new NativeFileSystem.FileReader(); - reader.onload = function(event) { - readFile = true; - }; - reader.onerror = function(event) { - errorCode = event.target.error.code; - }; - reader.readAsText(file, "utf8"); - }); - - waitsFor(function() { return gotFile && errorCode; }, 1000); - - runs(function() { - expect(gotFile).toBe(true); - expect(readFile).toBe(false); - expect(errorCode).toBe(FileError.NOT_FOUND_ERR); - }); - }); - - it("should fire appropriate events when the file is done loading", function() { - var gotFile = false, gotLoad = false, gotLoadStart = false, gotLoadEnd = false, + + describe("Reading a file", function() { + it("should read a file from disk", function() { + var gotFile = false, readFile = false, gotError = false, content; + var fileEntry = new NativeFileSystem.FileEntry(this.path + "/file1"); + fileEntry.file(function(file) { + gotFile = true; + var reader = new NativeFileSystem.FileReader(); + reader.onload = function(event) { + readFile = true; + content = event.target.result; + }; + reader.onerror = function(event) { + gotError = true; + }; + reader.readAsText(file, "utf8"); + }); + + waitsFor(function() { return gotFile && readFile; }, 1000); + + runs(function() { + expect(gotFile).toBe(true); + expect(readFile).toBe(true); + expect(gotError).toBe(false); + expect(content).toBe(this.file1content); + }); + }); + + it("should return an error if the file is not found", function() { + var gotFile = false, readFile = false, errorCode; + var fileEntry = new NativeFileSystem.FileEntry(this.path + "/idontexist"); + fileEntry.file(function(file) { + gotFile = true; + var reader = new NativeFileSystem.FileReader(); + reader.onload = function(event) { + readFile = true; + }; + reader.onerror = function(event) { + errorCode = event.target.error.code; + }; + reader.readAsText(file, "utf8"); + }); + + waitsFor(function() { return gotFile && errorCode; }, 1000); + + runs(function() { + expect(gotFile).toBe(true); + expect(readFile).toBe(false); + expect(errorCode).toBe(FileError.NOT_FOUND_ERR); + }); + }); + + it("should fire appropriate events when the file is done loading", function() { + var gotFile = false, gotLoad = false, gotLoadStart = false, gotLoadEnd = false, gotProgress = false, gotError = false, gotAbort = false; - var fileEntry = new NativeFileSystem.FileEntry(this.path + "/file1"); - fileEntry.file(function(file) { - gotFile = true; - var reader = new NativeFileSystem.FileReader(); - reader.onload = function(event) { - gotLoad = true; - }; - reader.onloadstart = function(event) { - gotLoadStart = true; - } - reader.onloadend = function(event) { - gotLoadEnd = true; - }; - reader.onprogress = function(event) { - gotProgress = true; - }; - reader.onerror = function(event) { - gotError = true; - }; - reader.onabort = function(event) { - gotAbort = true; - } - reader.readAsText(file, "utf8"); - }); - - waitsFor(function() { return gotLoad && gotLoadEnd && gotProgress; }, 1000); - - runs(function() { - expect(gotFile).toBe(true); - expect(gotLoadStart).toBe(true); - expect(gotLoad).toBe(true); - expect(gotLoadEnd).toBe(true); - expect(gotProgress).toBe(true); - expect(gotError).toBe(false); - expect(gotAbort).toBe(false); - }); + var fileEntry = new NativeFileSystem.FileEntry(this.path + "/file1"); + fileEntry.file(function(file) { + gotFile = true; + var reader = new NativeFileSystem.FileReader(); + reader.onload = function(event) { + gotLoad = true; + }; + reader.onloadstart = function(event) { + gotLoadStart = true; + } + reader.onloadend = function(event) { + gotLoadEnd = true; + }; + reader.onprogress = function(event) { + gotProgress = true; + }; + reader.onerror = function(event) { + gotError = true; + }; + reader.onabort = function(event) { + gotAbort = true; + } + reader.readAsText(file, "utf8"); + }); + + waitsFor(function() { return gotLoad && gotLoadEnd && gotProgress; }, 1000); + + runs(function() { + expect(gotFile).toBe(true); + expect(gotLoadStart).toBe(true); + expect(gotLoad).toBe(true); + expect(gotLoadEnd).toBe(true); + expect(gotProgress).toBe(true); + expect(gotError).toBe(false); + expect(gotAbort).toBe(false); + }); + }); + + it("should return an error but not crash if you create a bad FileEntry", function() { + var gotFile = false, readFile = false, gotError = false; + var fileEntry = new NativeFileSystem.FileEntry(null); + fileEntry.file(function(file) { + gotFile = true; + var reader = new NativeFileSystem.FileReader(); + reader.onload = function(event) { + readFile = true; + }; + reader.onerror = function(event) { + gotError = true; + }; + reader.readAsText(file, "utf8"); + }); + + waitsFor(function() { return gotError; }, 1000); + + runs(function() { + expect(gotFile).toBe(true); + expect(readFile).toBe(false); + expect(gotError).toBe(true); + }); + }); }); - - it("should return an error but not crash if you create a bad FileEntry", function() { - var gotFile = false, readFile = false, gotError = false; - var fileEntry = new NativeFileSystem.FileEntry(null); - fileEntry.file(function(file) { - gotFile = true; - var reader = new NativeFileSystem.FileReader(); - reader.onload = function(event) { - readFile = true; - }; - reader.onerror = function(event) { - gotError = true; - }; - reader.readAsText(file, "utf8"); - }); - - waitsFor(function() { return gotError; }, 1000); - - runs(function() { - expect(gotFile).toBe(true); - expect(readFile).toBe(false); - expect(gotError).toBe(true); - }); + + describe("Writing", function() { + + beforeEach( function() { + }); + + afterEach( function() { + }); + + it("should create new, zero-length files", function() { + var nfs = null; + + NativeFileSystem.requestNativeFileSystem( this.path, function( fs ) { + nfs = fs; + }); + + waitsFor( function() { return nfs }, 1000); + + var fileEntry = null; + var writeComplete = false; + + // create a new file exclusively + runs(function() { + var successCallback = function( entry ) { + fileEntry = entry; + writeComplete = true; + } + var errorCallback = function() { + writeComplete = true; + }; + + // FIXME (jasonsj): NativeFileSystem.root is missing + nfs.getFile("new-zero-length-file.txt", { create: true, exclusive: true }, successCallback, errorCallback ); + }); + + waitsFor( function() { return writeComplete; }, 1000 ); + + // fileEntry is non-null on success + runs(function() { + expect(fileEntry).not.toBe(null); + }); + + var actualContents = null; + + // read the new file + runs(function() { + brackets.fs.readFile( fileEntry.fullPath, "utf8", function ( err, contents ) { + actualContents = contents; + }); + }); + + // wait for content to be read + waitsFor( function() { return (actualContents !== null); }, 1000 ); + + // verify actual content to be empty + runs(function() { + expect(actualContents).toEqual(""); + + // cleanup + var self = this; + brackets.fs.unlink(fileEntry.fullPath, function( err ) { + if ( err !== brackets.fs.NO_ERROR ) + self.fail("Failed to delete " + fileEntry.fullPath); + }); + }); + }); + + it("should report an error when a file does not exist and create = false", function() { + var nfs = null; + + NativeFileSystem.requestNativeFileSystem( this.path, function( fs ) { + nfs = fs; + }); + + waitsFor( function() { return nfs }, 1000); + + var fileEntry = null; + var writeComplete = false; + var error = null; + + // create a new file exclusively + runs(function() { + var successCallback = function( entry ) { + fileEntry = entry; + writeComplete = true; + } + var errorCallback = function( err ) { + error = err; + writeComplete = true; + }; + + // FIXME (jasonsj): NativeFileSystem.root is missing + nfs.getFile("does-not-exist.txt", { create: false }, successCallback, errorCallback ); + }); + + waitsFor( function() { return writeComplete; }, 1000 ); + + // fileEntry is null on error + runs(function() { + expect(fileEntry).toBe(null); + expect(error.code).toBe(FileError.NOT_FOUND_ERR) + }); + }); + + it("should return an error if file exists and exclusive is true", function() { + var nfs = null; + + NativeFileSystem.requestNativeFileSystem( this.path, function( fs ) { + nfs = fs; + }); + + waitsFor( function() { return nfs }, 1000); + + var fileEntry = null; + var writeComplete = false; + var error = null; + + // try to create a new file exclusively when the file name already exists + runs(function() { + var successCallback = function( entry ) { + fileEntry = entry; + writeComplete = true; + } + var errorCallback = function( err ) { + error = err; + writeComplete = true; + }; + + // FIXME (jasonsj): NativeFileSystem.root is missing + nfs.getFile("file1", { create: true, exclusive: true }, successCallback, errorCallback ); + }); + + // wait for success or error to return + waitsFor( function() { return writeComplete; }, 1000 ); + + runs(function() { + // fileEntry will be null when errorCallback is handled + expect(fileEntry).toBe(null); + + // errorCallback should be called with PATH_EXISTS_ERR + expect(error.code).toEqual(FileError.PATH_EXISTS_ERR); + }); + }); + + it("should return an error if the path is a directory", function() { + var nfs = null; + + NativeFileSystem.requestNativeFileSystem( this.path, function( fs ) { + nfs = fs; + }); + + waitsFor( function() { return nfs }, 1000); + + var fileEntry = null; + var writeComplete = false; + var error = null; + + // try to write to a path that is a directory instead of a file + runs(function() { + var successCallback = function( entry ) { + fileEntry = entry; + writeComplete = true; + } + var errorCallback = function( err ) { + error = err; + writeComplete = true; + }; + + // FIXME (jasonsj): NativeFileSystem.root is missing + nfs.getFile("dir1", { create: false }, successCallback, errorCallback ); + }); + + // wait for success or error to return + waitsFor( function() { return writeComplete; }, 1000 ); + + runs(function() { + // fileEntry will be null when errorCallback is handled + expect(fileEntry).toBe(null); + + // errorCallback should be called with TYPE_MISMATCH_ERR + expect(error.code).toEqual(FileError.TYPE_MISMATCH_ERR); + }); + }); + + it("should create overwrite files with new content", function() { + var nfs = null; + + NativeFileSystem.requestNativeFileSystem( this.path, function( fs ) { + nfs = fs; + }); + + waitsFor( function() { return nfs }, 1000); + + var fileEntry = null; + var writeComplete = false; + var error = null; + + runs(function() { + var successCallback = function( entry ) { + fileEntry = entry; + + fileEntry.createWriter( function ( fileWriter ) { + fileWriter.onwriteend = function( e ) { + writeComplete = true; + }; + fileWriter.onerror = function( err ) { + writeComplete = true; + }; + + // TODO (jasonsj): BlobBulder + fileWriter.write( "FileWriter.write" ); + }); + } + var errorCallback = function() { + writeComplete = true; + }; + + nfs.getFile( "file1", { create: false }, successCallback, errorCallback ); + }); + + waitsFor( function() { return writeComplete && fileEntry; }, 1000 ); + + var actualContents = null; + + runs(function() { + brackets.fs.readFile( fileEntry.fullPath, "utf8", function ( err, contents ) { + actualContents = contents; + }); + }); + + waitsFor( function() { return !!actualContents; }, 1000 ); + + runs(function() { + expect(actualContents).toEqual("FileWriter.write"); + + // reset file1 content + brackets.fs.writeFile( this.path + "/file1", this.file1content, "utf8" ); + }); + }); + + xit("should append to existing files", function() { + this.fail("TODO (jasonsj): not supported for sprint 1"); + }); + + xit("should seek into a file before writing", function() { + this.fail("TODO (jasonsj): not supported for sprint 1"); + }); + + xit("should truncate files", function() { + this.fail("TODO (jasonsj): not supported for sprint 1"); + }); }); - }); });