/* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.IsolatedStorage; using System.Runtime.Serialization; using System.Security; using System.Text; using System.Windows; using System.Windows.Resources; using WPCordovaClassLib.Cordova.JSON; namespace WPCordovaClassLib.Cordova.Commands { /// /// Provides access to isolated storage /// public class File : BaseCommand { // Error codes public const int NOT_FOUND_ERR = 1; public const int SECURITY_ERR = 2; public const int ABORT_ERR = 3; public const int NOT_READABLE_ERR = 4; public const int ENCODING_ERR = 5; public const int NO_MODIFICATION_ALLOWED_ERR = 6; public const int INVALID_STATE_ERR = 7; public const int SYNTAX_ERR = 8; public const int INVALID_MODIFICATION_ERR = 9; public const int QUOTA_EXCEEDED_ERR = 10; public const int TYPE_MISMATCH_ERR = 11; public const int PATH_EXISTS_ERR = 12; // File system options public const int TEMPORARY = 0; public const int PERSISTENT = 1; public const int RESOURCE = 2; public const int APPLICATION = 3; /// /// Temporary directory name /// private readonly string TMP_DIRECTORY_NAME = "tmp"; /// /// Represents error code for callback /// [DataContract] public class ErrorCode { /// /// Error code /// [DataMember(IsRequired = true, Name = "code")] public int Code { get; set; } /// /// Creates ErrorCode object /// public ErrorCode(int code) { this.Code = code; } } /// /// Represents File action options. /// [DataContract] public class FileOptions { /// /// File path /// /// private string _fileName; [DataMember(Name = "fileName")] public string FilePath { get { return this._fileName; } set { int index = value.IndexOfAny(new char[] { '#', '?' }); this._fileName = index > -1 ? value.Substring(0, index) : value; } } /// /// Full entryPath /// [DataMember(Name = "fullPath")] public string FullPath { get; set; } /// /// Directory name /// [DataMember(Name = "dirName")] public string DirectoryName { get; set; } /// /// Path to create file/directory /// [DataMember(Name = "path")] public string Path { get; set; } /// /// The encoding to use to encode the file's content. Default is UTF8. /// [DataMember(Name = "encoding")] public string Encoding { get; set; } /// /// Uri to get file /// /// private string _uri; [DataMember(Name = "uri")] public string Uri { get { return this._uri; } set { int index = value.IndexOfAny(new char[] { '#', '?' }); this._uri = index > -1 ? value.Substring(0, index) : value; } } /// /// Size to truncate file /// [DataMember(Name = "size")] public long Size { get; set; } /// /// Data to write in file /// [DataMember(Name = "data")] public string Data { get; set; } /// /// Position the writing starts with /// [DataMember(Name = "position")] public int Position { get; set; } /// /// Type of file system requested /// [DataMember(Name = "type")] public int FileSystemType { get; set; } /// /// New file/directory name /// [DataMember(Name = "newName")] public string NewName { get; set; } /// /// Destination directory to copy/move file/directory /// [DataMember(Name = "parent")] public string Parent { get; set; } /// /// Options for getFile/getDirectory methods /// [DataMember(Name = "options")] public CreatingOptions CreatingOpt { get; set; } /// /// Creates options object with default parameters /// public FileOptions() { this.SetDefaultValues(new StreamingContext()); } /// /// Initializes default values for class fields. /// Implemented in separate method because default constructor is not invoked during deserialization. /// /// [OnDeserializing()] public void SetDefaultValues(StreamingContext context) { this.Encoding = "UTF-8"; this.FilePath = ""; this.FileSystemType = -1; } } /// /// Stores image info /// [DataContract] public class FileMetadata { [DataMember(Name = "fileName")] public string FileName { get; set; } [DataMember(Name = "fullPath")] public string FullPath { get; set; } [DataMember(Name = "type")] public string Type { get; set; } [DataMember(Name = "lastModifiedDate")] public string LastModifiedDate { get; set; } [DataMember(Name = "size")] public long Size { get; set; } public FileMetadata(string filePath) { if (string.IsNullOrEmpty(filePath)) { throw new FileNotFoundException("File doesn't exist"); } this.FullPath = filePath; this.Size = 0; this.FileName = string.Empty; using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { bool IsFile = isoFile.FileExists(filePath); bool IsDirectory = isoFile.DirectoryExists(filePath); if (!IsDirectory) { if (!IsFile) // special case, if isoFile cannot find it, it might still be part of the app-package { // attempt to get it from the resources Uri fileUri = new Uri(filePath, UriKind.Relative); StreamResourceInfo streamInfo = Application.GetResourceStream(fileUri); if (streamInfo != null) { this.Size = streamInfo.Stream.Length; this.FileName = filePath.Substring(filePath.LastIndexOf("/") + 1); } else { throw new FileNotFoundException("File doesn't exist"); } } else { using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(filePath, FileMode.Open, FileAccess.Read, isoFile)) { this.Size = stream.Length; } this.FileName = System.IO.Path.GetFileName(filePath); this.LastModifiedDate = isoFile.GetLastWriteTime(filePath).DateTime.ToString(); } } this.Type = MimeTypeMapper.GetMimeType(this.FileName); } } } /// /// Represents file or directory modification metadata /// [DataContract] public class ModificationMetadata { /// /// Modification time /// [DataMember] public string modificationTime { get; set; } } /// /// Represents file or directory entry /// [DataContract] public class FileEntry { /// /// File type /// [DataMember(Name = "isFile")] public bool IsFile { get; set; } /// /// Directory type /// [DataMember(Name = "isDirectory")] public bool IsDirectory { get; set; } /// /// File/directory name /// [DataMember(Name = "name")] public string Name { get; set; } /// /// Full path to file/directory /// [DataMember(Name = "fullPath")] public string FullPath { get; set; } /// /// URI encoded fullpath /// [DataMember(Name = "nativeURL")] public string NativeURL { set { } get { string escaped = Uri.EscapeUriString(this.FullPath); escaped = escaped.Replace("//", "/"); if (escaped.StartsWith("/")) { escaped = escaped.Insert(0, "/"); } return escaped; } } public bool IsResource { get; set; } public static FileEntry GetEntry(string filePath, bool bIsRes=false) { FileEntry entry = null; try { entry = new FileEntry(filePath, bIsRes); } catch (Exception ex) { Debug.WriteLine("Exception in GetEntry for filePath :: " + filePath + " " + ex.Message); } return entry; } /// /// Creates object and sets necessary properties /// /// public FileEntry(string filePath, bool bIsRes = false) { if (string.IsNullOrEmpty(filePath)) { throw new ArgumentException(); } if(filePath.Contains(" ")) { Debug.WriteLine("FilePath with spaces :: " + filePath); } using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { IsResource = bIsRes; IsFile = isoFile.FileExists(filePath); IsDirectory = isoFile.DirectoryExists(filePath); if (IsFile) { this.Name = Path.GetFileName(filePath); } else if (IsDirectory) { this.Name = this.GetDirectoryName(filePath); if (string.IsNullOrEmpty(Name)) { this.Name = "/"; } } else { if (IsResource) { this.Name = Path.GetFileName(filePath); } else { throw new FileNotFoundException(); } } try { this.FullPath = filePath.Replace('\\', '/'); // new Uri(filePath).LocalPath; } catch (Exception) { this.FullPath = filePath; } } } /// /// Extracts directory name from path string /// Path should refer to a directory, for example \foo\ or /foo. /// /// /// private string GetDirectoryName(string path) { if (String.IsNullOrEmpty(path)) { return path; } string[] split = path.Split(new char[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); if (split.Length < 1) { return null; } else { return split[split.Length - 1]; } } } /// /// Represents info about requested file system /// [DataContract] public class FileSystemInfo { /// /// file system type /// [DataMember(Name = "name", IsRequired = true)] public string Name { get; set; } /// /// Root directory entry /// [DataMember(Name = "root", EmitDefaultValue = false)] public FileEntry Root { get; set; } /// /// Creates class instance /// /// /// Root directory public FileSystemInfo(string name, FileEntry rootEntry = null) { Name = name; Root = rootEntry; } } [DataContract] public class CreatingOptions { /// /// Create file/directory if is doesn't exist /// [DataMember(Name = "create")] public bool Create { get; set; } /// /// Generate an exception if create=true and file/directory already exists /// [DataMember(Name = "exclusive")] public bool Exclusive { get; set; } } // returns null value if it fails. private string[] getOptionStrings(string options) { string[] optStings = null; try { optStings = JSON.JsonHelper.Deserialize(options); } catch (Exception) { DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), CurrentCommandCallbackId); } return optStings; } /// /// Gets amount of free space available for Isolated Storage /// /// No options is needed for this method public void getFreeDiskSpace(string options) { string callbackId = getOptionStrings(options)[0]; try { using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { DispatchCommandResult(new PluginResult(PluginResult.Status.OK, isoFile.AvailableFreeSpace), callbackId); } } catch (IsolatedStorageException) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); } catch (Exception ex) { if (!this.HandleException(ex)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); } } } /// /// Check if file exists /// /// File path public void testFileExists(string options) { IsDirectoryOrFileExist(options, false); } /// /// Check if directory exists /// /// directory name public void testDirectoryExists(string options) { IsDirectoryOrFileExist(options, true); } /// /// Check if file or directory exist /// /// File path/Directory name /// Flag to recognize what we should check public void IsDirectoryOrFileExist(string options, bool isDirectory) { string[] args = getOptionStrings(options); string callbackId = args[1]; FileOptions fileOptions = JSON.JsonHelper.Deserialize(args[0]); string filePath = args[0]; if (fileOptions == null) { DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId); } try { using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { bool isExist; if (isDirectory) { isExist = isoFile.DirectoryExists(fileOptions.DirectoryName); } else { isExist = isoFile.FileExists(fileOptions.FilePath); } DispatchCommandResult(new PluginResult(PluginResult.Status.OK, isExist), callbackId); } } catch (IsolatedStorageException) // default handler throws INVALID_MODIFICATION_ERR { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); } catch (Exception ex) { if (!this.HandleException(ex)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); } } } public void readAsDataURL(string options) { string[] optStrings = getOptionStrings(options); string filePath = optStrings[0]; int startPos = int.Parse(optStrings[1]); int endPos = int.Parse(optStrings[2]); string callbackId = optStrings[3]; if (filePath != null) { try { string base64URL = null; using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { if (!isoFile.FileExists(filePath)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); return; } string mimeType = MimeTypeMapper.GetMimeType(filePath); using (IsolatedStorageFileStream stream = isoFile.OpenFile(filePath, FileMode.Open, FileAccess.Read)) { string base64String = GetFileContent(stream); base64URL = "data:" + mimeType + ";base64," + base64String; } } DispatchCommandResult(new PluginResult(PluginResult.Status.OK, base64URL), callbackId); } catch (Exception ex) { if (!this.HandleException(ex)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); } } } } private byte[] readFileBytes(string filePath,int startPos,int endPos, IsolatedStorageFile isoFile) { byte[] buffer; using (IsolatedStorageFileStream reader = isoFile.OpenFile(filePath, FileMode.Open, FileAccess.Read)) { if (startPos < 0) { startPos = Math.Max((int)reader.Length + startPos, 0); } else if (startPos > 0) { startPos = Math.Min((int)reader.Length, startPos); } if (endPos > 0) { endPos = Math.Min((int)reader.Length, endPos); } else if (endPos < 0) { endPos = Math.Max(endPos + (int)reader.Length, 0); } buffer = new byte[endPos - startPos]; reader.Seek(startPos, SeekOrigin.Begin); reader.Read(buffer, 0, buffer.Length); } return buffer; } public void readAsArrayBuffer(string options) { string[] optStrings = getOptionStrings(options); string filePath = optStrings[0]; int startPos = int.Parse(optStrings[1]); int endPos = int.Parse(optStrings[2]); string callbackId = optStrings[3]; try { byte[] buffer; using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { if (!isoFile.FileExists(filePath)) { readResourceAsText(options); return; } buffer = readFileBytes(filePath, startPos, endPos, isoFile); } DispatchCommandResult(new PluginResult(PluginResult.Status.OK, buffer), callbackId); } catch (Exception ex) { if (!this.HandleException(ex, callbackId)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); } } } public void readAsBinaryString(string options) { string[] optStrings = getOptionStrings(options); string filePath = optStrings[0]; int startPos = int.Parse(optStrings[1]); int endPos = int.Parse(optStrings[2]); string callbackId = optStrings[3]; try { string result; using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { if (!isoFile.FileExists(filePath)) { readResourceAsText(options); return; } byte[] buffer = readFileBytes(filePath, startPos, endPos, isoFile); result = System.Text.Encoding.GetEncoding("iso-8859-1").GetString(buffer, 0, buffer.Length); } DispatchCommandResult(new PluginResult(PluginResult.Status.OK, result), callbackId); } catch (Exception ex) { if (!this.HandleException(ex, callbackId)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); } } } public void readAsText(string options) { string[] optStrings = getOptionStrings(options); string filePath = optStrings[0]; string encStr = optStrings[1]; int startPos = int.Parse(optStrings[2]); int endPos = int.Parse(optStrings[3]); string callbackId = optStrings[4]; try { string text = ""; using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { if (!isoFile.FileExists(filePath)) { readResourceAsText(options); return; } Encoding encoding = Encoding.GetEncoding(encStr); byte[] buffer = this.readFileBytes(filePath, startPos, endPos, isoFile); text = encoding.GetString(buffer, 0, buffer.Length); } // JIRA: https://issues.apache.org/jira/browse/CB-8792 // Need to perform additional serialization here because NativeExecution is always trying // to do JSON.parse() on command result. This leads to issue when trying to read JSON files var resultText = JsonHelper.Serialize(text); DispatchCommandResult(new PluginResult(PluginResult.Status.OK, resultText), callbackId); } catch (Exception ex) { if (!this.HandleException(ex, callbackId)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); } } } /// /// Reads application resource as a text /// /// Path to a resource public void readResourceAsText(string options) { string[] optStrings = getOptionStrings(options); string pathToResource = optStrings[0]; string encStr = optStrings[1]; int start = int.Parse(optStrings[2]); int endMarker = int.Parse(optStrings[3]); string callbackId = optStrings[4]; try { if (pathToResource.StartsWith("/")) { pathToResource = pathToResource.Remove(0, 1); } var resource = Application.GetResourceStream(new Uri(pathToResource, UriKind.Relative)); if (resource == null) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); return; } string text; StreamReader streamReader = new StreamReader(resource.Stream); text = streamReader.ReadToEnd(); DispatchCommandResult(new PluginResult(PluginResult.Status.OK, text), callbackId); } catch (Exception ex) { if (!this.HandleException(ex, callbackId)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); } } } public void truncate(string options) { string[] optStrings = getOptionStrings(options); string filePath = optStrings[0]; int size = int.Parse(optStrings[1]); string callbackId = optStrings[2]; try { long streamLength = 0; using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { if (!isoFile.FileExists(filePath)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); return; } using (FileStream stream = new IsolatedStorageFileStream(filePath, FileMode.Open, FileAccess.ReadWrite, isoFile)) { if (0 <= size && size <= stream.Length) { stream.SetLength(size); } streamLength = stream.Length; } } DispatchCommandResult(new PluginResult(PluginResult.Status.OK, streamLength), callbackId); } catch (Exception ex) { if (!this.HandleException(ex, callbackId)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); } } } //write:[filePath,data,position,isBinary,callbackId] public void write(string options) { string[] optStrings = getOptionStrings(options); string filePath = optStrings[0]; string data = optStrings[1]; int position = int.Parse(optStrings[2]); bool isBinary = bool.Parse(optStrings[3]); string callbackId = optStrings[4]; try { if (string.IsNullOrEmpty(data)) { Debug.WriteLine("Expected some data to be send in the write command to {0}", filePath); DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId); return; } byte[] dataToWrite = isBinary ? JSON.JsonHelper.Deserialize(data) : System.Text.Encoding.UTF8.GetBytes(data); using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { // create the file if not exists if (!isoFile.FileExists(filePath)) { var file = isoFile.CreateFile(filePath); file.Close(); } using (FileStream stream = new IsolatedStorageFileStream(filePath, FileMode.Open, FileAccess.ReadWrite, isoFile)) { if (0 <= position && position <= stream.Length) { stream.SetLength(position); } using (BinaryWriter writer = new BinaryWriter(stream)) { writer.Seek(0, SeekOrigin.End); writer.Write(dataToWrite); } } } DispatchCommandResult(new PluginResult(PluginResult.Status.OK, dataToWrite.Length), callbackId); } catch (Exception ex) { if (!this.HandleException(ex, callbackId)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); } } } /// /// Look up metadata about this entry. /// /// filePath to entry public void getMetadata(string options) { string[] optStings = getOptionStrings(options); string filePath = optStings[0]; string callbackId = optStings[1]; if (filePath != null) { try { using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { if (isoFile.FileExists(filePath)) { DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new ModificationMetadata() { modificationTime = isoFile.GetLastWriteTime(filePath).DateTime.ToString() }), callbackId); } else if (isoFile.DirectoryExists(filePath)) { string modTime = isoFile.GetLastWriteTime(filePath).DateTime.ToString(); DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new ModificationMetadata() { modificationTime = modTime }), callbackId); } else { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); } } } catch (IsolatedStorageException) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); } catch (Exception ex) { if (!this.HandleException(ex)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); } } } } /// /// Returns a File that represents the current state of the file that this FileEntry represents. /// /// filePath to entry /// public void getFileMetadata(string options) { string[] optStings = getOptionStrings(options); string filePath = optStings[0]; string callbackId = optStings[1]; if (!string.IsNullOrEmpty(filePath)) { try { FileMetadata metaData = new FileMetadata(filePath); DispatchCommandResult(new PluginResult(PluginResult.Status.OK, metaData), callbackId); } catch (IsolatedStorageException) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); } catch (Exception ex) { if (!this.HandleException(ex)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_READABLE_ERR), callbackId); } } } else { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); } } /// /// Look up the parent DirectoryEntry containing this Entry. /// If this Entry is the root of IsolatedStorage, its parent is itself. /// /// public void getParent(string options) { string[] optStings = getOptionStrings(options); string filePath = optStings[0]; string callbackId = optStings[1]; if (filePath != null) { try { if (string.IsNullOrEmpty(filePath)) { DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION),callbackId); return; } using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { FileEntry entry; if (isoFile.FileExists(filePath) || isoFile.DirectoryExists(filePath)) { string path = this.GetParentDirectory(filePath); entry = FileEntry.GetEntry(path); DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry),callbackId); } else { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId); } } } catch (Exception ex) { if (!this.HandleException(ex)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId); } } } } public void remove(string options) { string[] args = getOptionStrings(options); string filePath = args[0]; string callbackId = args[1]; if (filePath != null) { try { if (filePath == "/" || filePath == "" || filePath == @"\") { throw new Exception("Cannot delete root file system") ; } using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { if (isoFile.FileExists(filePath)) { isoFile.DeleteFile(filePath); } else { if (isoFile.DirectoryExists(filePath)) { isoFile.DeleteDirectory(filePath); } else { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId); return; } } DispatchCommandResult(new PluginResult(PluginResult.Status.OK),callbackId); } } catch (Exception ex) { if (!this.HandleException(ex)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR),callbackId); } } } } public void removeRecursively(string options) { string[] args = getOptionStrings(options); string filePath = args[0]; string callbackId = args[1]; if (filePath != null) { if (string.IsNullOrEmpty(filePath)) { DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION),callbackId); } else { if (removeDirRecursively(filePath, callbackId)) { DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId); } } } } public void readEntries(string options) { string[] args = getOptionStrings(options); string filePath = args[0]; string callbackId = args[1]; if (filePath != null) { try { if (string.IsNullOrEmpty(filePath)) { DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION),callbackId); return; } using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { if (isoFile.DirectoryExists(filePath)) { string path = File.AddSlashToDirectory(filePath); List entries = new List(); string[] files = isoFile.GetFileNames(path + "*"); string[] dirs = isoFile.GetDirectoryNames(path + "*"); foreach (string file in files) { entries.Add(FileEntry.GetEntry(path + file)); } foreach (string dir in dirs) { entries.Add(FileEntry.GetEntry(path + dir + "/")); } DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entries),callbackId); } else { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId); } } } catch (Exception ex) { if (!this.HandleException(ex)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR),callbackId); } } } } public void requestFileSystem(string options) { // TODO: try/catch string[] optVals = getOptionStrings(options); //FileOptions fileOptions = new FileOptions(); int fileSystemType = int.Parse(optVals[0]); double size = double.Parse(optVals[1]); string callbackId = optVals[2]; IsolatedStorageFile.GetUserStoreForApplication(); if (size > (10 * 1024 * 1024)) // 10 MB, compier will clean this up! { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, QUOTA_EXCEEDED_ERR), callbackId); return; } try { if (size != 0) { using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { long availableSize = isoFile.AvailableFreeSpace; if (size > availableSize) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, QUOTA_EXCEEDED_ERR), callbackId); return; } } } if (fileSystemType == PERSISTENT) { // TODO: this should be in it's own folder to prevent overwriting of the app assets, which are also in ISO DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileSystemInfo("persistent", FileEntry.GetEntry("/"))), callbackId); } else if (fileSystemType == TEMPORARY) { using (IsolatedStorageFile isoStorage = IsolatedStorageFile.GetUserStoreForApplication()) { if (!isoStorage.FileExists(TMP_DIRECTORY_NAME)) { isoStorage.CreateDirectory(TMP_DIRECTORY_NAME); } } string tmpFolder = "/" + TMP_DIRECTORY_NAME + "/"; DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileSystemInfo("temporary", FileEntry.GetEntry(tmpFolder))), callbackId); } else if (fileSystemType == RESOURCE) { DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileSystemInfo("resource")), callbackId); } else if (fileSystemType == APPLICATION) { DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileSystemInfo("application")), callbackId); } else { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId); } } catch (Exception ex) { if (!this.HandleException(ex)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId); } } } public void resolveLocalFileSystemURI(string options) { string[] optVals = getOptionStrings(options); string uri = optVals[0].Split('?')[0]; string callbackId = optVals[1]; if (uri != null) { // a single '/' is valid, however, '/someDir' is not, but '/tmp//somedir' and '///someDir' are valid if (uri.StartsWith("/") && uri.IndexOf("//") < 0 && uri != "/") { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId); return; } try { // fix encoded spaces string path = Uri.UnescapeDataString(uri); FileEntry uriEntry = FileEntry.GetEntry(path); if (uriEntry != null) { DispatchCommandResult(new PluginResult(PluginResult.Status.OK, uriEntry), callbackId); } else { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); } } catch (Exception ex) { if (!this.HandleException(ex, callbackId)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId); } } } } public void copyTo(string options) { TransferTo(options, false); } public void moveTo(string options) { TransferTo(options, true); } public void getFile(string options) { GetFileOrDirectory(options, false); } public void getDirectory(string options) { GetFileOrDirectory(options, true); } #region internal functionality /// /// Retrieves the parent directory name of the specified path, /// /// Path /// Parent directory name private string GetParentDirectory(string path) { if (String.IsNullOrEmpty(path) || path == "/") { return "/"; } if (path.EndsWith(@"/") || path.EndsWith(@"\")) { return this.GetParentDirectory(Path.GetDirectoryName(path)); } string result = Path.GetDirectoryName(path); if (result == null) { result = "/"; } return result; } private bool removeDirRecursively(string fullPath,string callbackId) { try { if (fullPath == "/") { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR),callbackId); return false; } using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { if (isoFile.DirectoryExists(fullPath)) { string tempPath = File.AddSlashToDirectory(fullPath); string[] files = isoFile.GetFileNames(tempPath + "*"); if (files.Length > 0) { foreach (string file in files) { isoFile.DeleteFile(tempPath + file); } } string[] dirs = isoFile.GetDirectoryNames(tempPath + "*"); if (dirs.Length > 0) { foreach (string dir in dirs) { if (!removeDirRecursively(tempPath + dir, callbackId)) { return false; } } } isoFile.DeleteDirectory(fullPath); } else { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR),callbackId); } } } catch (Exception ex) { if (!this.HandleException(ex)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR),callbackId); return false; } } return true; } private bool CanonicalCompare(string pathA, string pathB) { string a = pathA.Replace("//", "/"); string b = pathB.Replace("//", "/"); return a.Equals(b, StringComparison.OrdinalIgnoreCase); } /* * copyTo:["fullPath","parent", "newName"], * moveTo:["fullPath","parent", "newName"], */ private void TransferTo(string options, bool move) { // TODO: try/catch string[] optStrings = getOptionStrings(options); string fullPath = optStrings[0]; string parent = optStrings[1]; string newFileName = optStrings[2]; string callbackId = optStrings[3]; char[] invalids = Path.GetInvalidPathChars(); if (newFileName.IndexOfAny(invalids) > -1 || newFileName.IndexOf(":") > -1 ) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId); return; } try { if ((parent == null) || (string.IsNullOrEmpty(parent)) || (string.IsNullOrEmpty(fullPath))) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); return; } string parentPath = File.AddSlashToDirectory(parent); string currentPath = fullPath; using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { bool isFileExist = isoFile.FileExists(currentPath); bool isDirectoryExist = isoFile.DirectoryExists(currentPath); bool isParentExist = isoFile.DirectoryExists(parentPath); if ( ( !isFileExist && !isDirectoryExist ) || !isParentExist ) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); return; } string newName; string newPath; if (isFileExist) { newName = (string.IsNullOrEmpty(newFileName)) ? Path.GetFileName(currentPath) : newFileName; newPath = Path.Combine(parentPath, newName); // sanity check .. // cannot copy file onto itself if (CanonicalCompare(newPath,currentPath)) //(parent + newFileName)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, INVALID_MODIFICATION_ERR), callbackId); return; } else if (isoFile.DirectoryExists(newPath)) { // there is already a folder with the same name, operation is not allowed DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, INVALID_MODIFICATION_ERR), callbackId); return; } else if (isoFile.FileExists(newPath)) { // remove destination file if exists, in other case there will be exception isoFile.DeleteFile(newPath); } if (move) { isoFile.MoveFile(currentPath, newPath); } else { isoFile.CopyFile(currentPath, newPath, true); } } else { newName = (string.IsNullOrEmpty(newFileName)) ? currentPath : newFileName; newPath = Path.Combine(parentPath, newName); if (move) { // remove destination directory if exists, in other case there will be exception // target directory should be empty if (!newPath.Equals(currentPath) && isoFile.DirectoryExists(newPath)) { isoFile.DeleteDirectory(newPath); } isoFile.MoveDirectory(currentPath, newPath); } else { CopyDirectory(currentPath, newPath, isoFile); } } FileEntry entry = FileEntry.GetEntry(newPath); if (entry != null) { DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry), callbackId); } else { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); } } } catch (Exception ex) { if (!this.HandleException(ex, callbackId)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId); } } } private bool HandleException(Exception ex, string cbId="") { bool handled = false; string callbackId = String.IsNullOrEmpty(cbId) ? this.CurrentCommandCallbackId : cbId; if (ex is SecurityException) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, SECURITY_ERR), callbackId); handled = true; } else if (ex is FileNotFoundException) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); handled = true; } else if (ex is ArgumentException) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId); handled = true; } else if (ex is IsolatedStorageException) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, INVALID_MODIFICATION_ERR), callbackId); handled = true; } else if (ex is DirectoryNotFoundException) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); handled = true; } return handled; } private void CopyDirectory(string sourceDir, string destDir, IsolatedStorageFile isoFile) { string path = File.AddSlashToDirectory(sourceDir); bool bExists = isoFile.DirectoryExists(destDir); if (!bExists) { isoFile.CreateDirectory(destDir); } destDir = File.AddSlashToDirectory(destDir); string[] files = isoFile.GetFileNames(path + "*"); if (files.Length > 0) { foreach (string file in files) { isoFile.CopyFile(path + file, destDir + file,true); } } string[] dirs = isoFile.GetDirectoryNames(path + "*"); if (dirs.Length > 0) { foreach (string dir in dirs) { CopyDirectory(path + dir, destDir + dir, isoFile); } } } private string RemoveExtraSlash(string path) { if (path.StartsWith("//")) { path = path.Remove(0, 1); path = RemoveExtraSlash(path); } return path; } private string ResolvePath(string parentPath, string path) { string absolutePath = null; if (path.Contains("..")) { if (parentPath.Length > 1 && parentPath.StartsWith("/") && parentPath !="/") { parentPath = RemoveExtraSlash(parentPath); } string fullPath = Path.GetFullPath(Path.Combine(parentPath, path)); absolutePath = fullPath.Replace(Path.GetPathRoot(fullPath), @"//"); } else { absolutePath = Path.Combine(parentPath + "/", path); } return absolutePath; } private void GetFileOrDirectory(string options, bool getDirectory) { FileOptions fOptions = new FileOptions(); string[] args = getOptionStrings(options); fOptions.FullPath = args[0]; fOptions.Path = args[1]; string callbackId = args[3]; try { fOptions.CreatingOpt = JSON.JsonHelper.Deserialize(args[2]); } catch (Exception) { DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION), callbackId); return; } try { if ((string.IsNullOrEmpty(fOptions.Path)) || (string.IsNullOrEmpty(fOptions.FullPath))) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); return; } string path; if (fOptions.Path.Split(':').Length > 2) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId); return; } try { path = ResolvePath(fOptions.FullPath, fOptions.Path); } catch (Exception) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ENCODING_ERR), callbackId); return; } using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { bool isFile = isoFile.FileExists(path); bool isDirectory = isoFile.DirectoryExists(path); bool create = (fOptions.CreatingOpt == null) ? false : fOptions.CreatingOpt.Create; bool exclusive = (fOptions.CreatingOpt == null) ? false : fOptions.CreatingOpt.Exclusive; if (create) { if (exclusive && (isoFile.FileExists(path) || isoFile.DirectoryExists(path))) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, PATH_EXISTS_ERR), callbackId); return; } // need to make sure the parent exists // it is an error to create a directory whose immediate parent does not yet exist // see issue: https://issues.apache.org/jira/browse/CB-339 string[] pathParts = path.Split('/'); string builtPath = pathParts[0]; for (int n = 1; n < pathParts.Length - 1; n++) { builtPath += "/" + pathParts[n]; if (!isoFile.DirectoryExists(builtPath)) { Debug.WriteLine(String.Format("Error :: Parent folder \"{0}\" does not exist, when attempting to create \"{1}\"",builtPath,path)); DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); return; } } if ((getDirectory) && (!isDirectory)) { isoFile.CreateDirectory(path); } else { if ((!getDirectory) && (!isFile)) { IsolatedStorageFileStream fileStream = isoFile.CreateFile(path); fileStream.Close(); } } } else // (not create) { if ((!isFile) && (!isDirectory)) { if (path.IndexOf("//www") == 0) { Uri fileUri = new Uri(path.Remove(0,2), UriKind.Relative); StreamResourceInfo streamInfo = Application.GetResourceStream(fileUri); if (streamInfo != null) { FileEntry _entry = FileEntry.GetEntry(fileUri.OriginalString,true); DispatchCommandResult(new PluginResult(PluginResult.Status.OK, _entry), callbackId); //using (BinaryReader br = new BinaryReader(streamInfo.Stream)) //{ // byte[] data = br.ReadBytes((int)streamInfo.Stream.Length); //} } else { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); } } else { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); } return; } if (((getDirectory) && (!isDirectory)) || ((!getDirectory) && (!isFile))) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, TYPE_MISMATCH_ERR), callbackId); return; } } FileEntry entry = FileEntry.GetEntry(path); if (entry != null) { DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry), callbackId); } else { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NOT_FOUND_ERR), callbackId); } } } catch (Exception ex) { if (!this.HandleException(ex)) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, NO_MODIFICATION_ALLOWED_ERR), callbackId); } } } private static string AddSlashToDirectory(string dirPath) { if (dirPath.EndsWith("/")) { return dirPath; } else { return dirPath + "/"; } } /// /// Returns file content in a form of base64 string /// /// File stream /// Base64 representation of the file private string GetFileContent(Stream stream) { int streamLength = (int)stream.Length; byte[] fileData = new byte[streamLength + 1]; stream.Read(fileData, 0, streamLength); stream.Close(); return Convert.ToBase64String(fileData); } #endregion } }