diff --git a/src/wp/FileTransfer.cs b/src/wp/FileTransfer.cs index a04e949..8d79f84 100644 --- a/src/wp/FileTransfer.cs +++ b/src/wp/FileTransfer.cs @@ -31,11 +31,13 @@ namespace WPCordovaClassLib.Cordova.Commands // This class stores the State of the request. public HttpWebRequest request; public TransferOptions options; + public bool isCancelled; public DownloadRequestState() { request = null; options = null; + isCancelled = false; } } @@ -179,6 +181,38 @@ namespace WPCordovaClassLib.Cordova.Commands } } + /// + /// Represents a singular progress event to be passed back to javascript + /// + [DataContract] + public class FileTransferProgress + { + /// + /// Is the length of the response known? + /// + [DataMember(Name = "lengthComputable", IsRequired = true)] + public bool LengthComputable { get; set; } + /// + /// amount of bytes loaded + /// + [DataMember(Name = "loaded", IsRequired = true)] + public long BytesLoaded { get; set; } + /// + /// Total bytes + /// + [DataMember(Name = "total", IsRequired = false)] + public long BytesTotal { get; set; } + + public FileTransferProgress(long bTotal = 0, long bLoaded = 0) + { + LengthComputable = bTotal > 0; + BytesLoaded = bLoaded; + BytesTotal = bTotal; + } + + + } + /// /// Upload options /// @@ -270,7 +304,7 @@ namespace WPCordovaClassLib.Cordova.Commands InProcDownloads[uploadOptions.Id] = reqState; - webRequest.BeginGetRequestStream(WriteCallback, reqState); + webRequest.BeginGetRequestStream(uploadCallback, reqState); } catch (Exception ex) { @@ -288,7 +322,6 @@ namespace WPCordovaClassLib.Cordova.Commands string temp = jsonHeaders.StartsWith("{") ? jsonHeaders.Substring(1) : jsonHeaders; temp = temp.EndsWith("}") ? temp.Substring(0,temp.Length - 1) : temp; - // "\"Authorization\":\"Basic Y29yZG92YV91c2VyOmNvcmRvdmFfcGFzc3dvcmQ=\"" string[] strHeaders = temp.Split(','); for (int n = 0; n < strHeaders.Length; n++) @@ -304,6 +337,7 @@ namespace WPCordovaClassLib.Cordova.Commands return result; } + public void download(string options) { TransferOptions downloadOptions = null; @@ -312,6 +346,7 @@ namespace WPCordovaClassLib.Cordova.Commands try { + // source, target, trustAllHosts, this._id, headers string[] optionStrings = JSON.JsonHelper.Deserialize(options); downloadOptions = new TransferOptions(); @@ -335,16 +370,94 @@ namespace WPCordovaClassLib.Cordova.Commands try { - Debug.WriteLine("Creating WebRequest for url : " + downloadOptions.Url); - webRequest = (HttpWebRequest)WebRequest.Create(downloadOptions.Url); - } - //catch (WebException webEx) - //{ + // is the URL a local app file? + if (downloadOptions.Url.StartsWith("x-wmapp0") || downloadOptions.Url.StartsWith("file:")) + { + using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) + { + // just copy from one area of iso-store to another ... + if (isoFile.FileExists(downloadOptions.Url)) + { + isoFile.CopyFile(downloadOptions.Url, downloadOptions.FilePath); + } + else + { + // need to unpack resource from the dll + string cleanUrl = downloadOptions.Url.Replace("x-wmapp0:", "").Replace("file:", ""); + Uri uri = new Uri(cleanUrl, UriKind.Relative); + var resource = Application.GetResourceStream(uri); - //} - catch (Exception) + if (resource != null) + { + // create the file destination + if (!isoFile.FileExists(downloadOptions.FilePath)) + { + var destFile = isoFile.CreateFile(downloadOptions.FilePath); + destFile.Close(); + } + + using (FileStream fileStream = new IsolatedStorageFileStream(downloadOptions.FilePath, FileMode.Open, FileAccess.Write, isoFile)) + { + long totalBytes = resource.Stream.Length; + int bytesRead = 0; + using (BinaryReader reader = new BinaryReader(resource.Stream)) + { + using (BinaryWriter writer = new BinaryWriter(fileStream)) + { + int BUFFER_SIZE = 1024; + byte[] buffer; + + while (true) + { + buffer = reader.ReadBytes(BUFFER_SIZE); + // fire a progress event ? + bytesRead += buffer.Length; + if (buffer.Length > 0) + { + writer.Write(buffer); + DispatchFileTransferProgress(bytesRead, totalBytes, callbackId); + } + else + { + writer.Close(); + reader.Close(); + fileStream.Close(); + break; + } + } + } + + } + } + } + } + + } + + File.FileEntry entry = File.FileEntry.GetEntry(downloadOptions.FilePath); + if (entry != null) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry), callbackId); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, File.NOT_FOUND_ERR), callbackId); + } + + return; + + } + else + { + // otherwise it is web-bound, we will actually download it + //Debug.WriteLine("Creating WebRequest for url : " + downloadOptions.Url); + webRequest = (HttpWebRequest)WebRequest.Create(downloadOptions.Url); + } + } + catch (Exception ex) { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(InvalidUrlError, downloadOptions.Url, null, 0))); + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, + new FileTransferError(InvalidUrlError, downloadOptions.Url, null, 0))); return; } @@ -365,10 +478,13 @@ namespace WPCordovaClassLib.Cordova.Commands } webRequest.BeginGetResponse(new AsyncCallback(downloadCallback), state); + // dispatch an event for progress ( 0 ) + var plugRes = new PluginResult(PluginResult.Status.OK, new FileTransferProgress()); + plugRes.KeepCallback = true; + plugRes.CallbackId = callbackId; + DispatchCommandResult(plugRes, callbackId); } - - } public void abort(string options) @@ -380,14 +496,16 @@ namespace WPCordovaClassLib.Cordova.Commands if (InProcDownloads.ContainsKey(id)) { DownloadRequestState state = InProcDownloads[id]; - state.request.Abort(); - state.request = null; - - callbackId = state.options.CallbackId; - InProcDownloads.Remove(id); - state = null; - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(FileTransfer.AbortError)), - callbackId); + state.isCancelled = true; + if (!state.request.HaveResponse) + { + state.request.Abort(); + InProcDownloads.Remove(id); + callbackId = state.options.CallbackId; + //state = null; + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(FileTransfer.AbortError)), + callbackId); + } } else @@ -396,6 +514,17 @@ namespace WPCordovaClassLib.Cordova.Commands } } + private void DispatchFileTransferProgress(long bytesLoaded, long bytesTotal, string callbackId) + { + // send a progress change event + FileTransferProgress progEvent = new FileTransferProgress(bytesTotal); + progEvent.BytesLoaded = bytesLoaded; + PluginResult plugRes = new PluginResult(PluginResult.Status.OK, progEvent); + plugRes.KeepCallback = true; + plugRes.CallbackId = callbackId; + DispatchCommandResult(plugRes, callbackId); + } + /// /// /// @@ -406,15 +535,12 @@ namespace WPCordovaClassLib.Cordova.Commands HttpWebRequest request = reqState.request; string callbackId = reqState.options.CallbackId; - - if (InProcDownloads.ContainsKey(reqState.options.Id)) - { - InProcDownloads.Remove(reqState.options.Id); - } - try { HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult); + + // send a progress change event + DispatchFileTransferProgress(0, response.ContentLength, callbackId); using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) { @@ -441,9 +567,10 @@ namespace WPCordovaClassLib.Cordova.Commands buffer = reader.ReadBytes(BUFFER_SIZE); // fire a progress event ? bytesRead += buffer.Length; - if (buffer.Length > 0) + if (buffer.Length > 0 && !reqState.isCancelled) { writer.Write(buffer); + DispatchFileTransferProgress(bytesRead, totalBytes, callbackId); } else { @@ -452,16 +579,30 @@ namespace WPCordovaClassLib.Cordova.Commands fileStream.Close(); break; } + System.Threading.Thread.Sleep(1); } } } - - } + if (reqState.isCancelled) + { + isoFile.DeleteFile(reqState.options.FilePath); + } + + } + + if (reqState.isCancelled) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(AbortError)), + callbackId); + + } + else + { + File.FileEntry entry = new File.FileEntry(reqState.options.FilePath); + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry), callbackId); } - File.FileEntry entry = new File.FileEntry(reqState.options.FilePath); - DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry), callbackId); } catch (IsolatedStorageException) { @@ -515,6 +656,13 @@ namespace WPCordovaClassLib.Cordova.Commands new FileTransferError(FileNotFoundError)), callbackId); } + + //System.Threading.Thread.Sleep(1000); + if (InProcDownloads.ContainsKey(reqState.options.Id)) + { + InProcDownloads.Remove(reqState.options.Id); + } + } @@ -523,7 +671,7 @@ namespace WPCordovaClassLib.Cordova.Commands /// Read file from Isolated Storage and sends it to server /// /// - private void WriteCallback(IAsyncResult asynchronousResult) + private void uploadCallback(IAsyncResult asynchronousResult) { DownloadRequestState reqState = (DownloadRequestState)asynchronousResult.AsyncState; HttpWebRequest webRequest = reqState.request; @@ -538,6 +686,8 @@ namespace WPCordovaClassLib.Cordova.Commands byte[] boundaryBytes = System.Text.Encoding.UTF8.GetBytes(lineStart + Boundary + lineEnd); string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"" + lineEnd + lineEnd + "{1}" + lineEnd; + + if (!string.IsNullOrEmpty(reqState.options.Params)) { Dictionary paramMap = parseHeaders(reqState.options.Params); @@ -558,24 +708,34 @@ namespace WPCordovaClassLib.Cordova.Commands return; } + byte[] endRequest = System.Text.Encoding.UTF8.GetBytes(lineEnd + lineStart + Boundary + lineStart + lineEnd); + long totalBytesToSend = 0; + using (FileStream fileStream = new IsolatedStorageFileStream(reqState.options.FilePath, FileMode.Open, isoFile)) - { + { + string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"" + lineEnd + "Content-Type: {2}" + lineEnd + lineEnd; string header = string.Format(headerTemplate, reqState.options.FileKey, reqState.options.FileName, reqState.options.MimeType); byte[] headerBytes = System.Text.Encoding.UTF8.GetBytes(header); - requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); - requestStream.Write(headerBytes, 0, headerBytes.Length); + byte[] buffer = new byte[4096]; int bytesRead = 0; + totalBytesToSend = fileStream.Length; + requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); + + requestStream.Write(headerBytes, 0, headerBytes.Length); + while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0) { // TODO: Progress event requestStream.Write(buffer, 0, bytesRead); bytesSent += bytesRead; + DispatchFileTransferProgress(bytesSent, totalBytesToSend, callbackId); + System.Threading.Thread.Sleep(1); } } - byte[] endRequest = System.Text.Encoding.UTF8.GetBytes(lineEnd + lineStart + Boundary + lineStart + lineEnd); + requestStream.Write(endRequest, 0, endRequest.Length); } }