blob: e5858951e5ead76c1624125be0fc0aaf5c32c4a1 [file] [log] [blame]
/*
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.IO;
using System.IO.IsolatedStorage;
using System.Net;
using System.Runtime.Serialization;
using System.Windows;
using System.Security;
using System.Diagnostics;
namespace WPCordovaClassLib.Cordova.Commands
{
public class FileTransfer : BaseCommand
{
public class DownloadRequestState
{
// This class stores the State of the request.
public HttpWebRequest request;
public DownloadOptions options;
public DownloadRequestState()
{
request = null;
options = null;
}
}
/// <summary>
/// Boundary symbol
/// </summary>
private string Boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x");
// Error codes
public const int FileNotFoundError = 1;
public const int InvalidUrlError = 2;
public const int ConnectionError = 3;
/// <summary>
/// Options for downloading file
/// </summary>
[DataContract]
public class DownloadOptions
{
/// <summary>
/// File path to download to
/// </summary>
[DataMember(Name = "filePath", IsRequired = true)]
public string FilePath { get; set; }
/// <summary>
/// Server address to the file to download
/// </summary>
[DataMember(Name = "url", IsRequired = true)]
public string Url { get; set; }
}
/// <summary>
/// Options for uploading file
/// </summary>
[DataContract]
public class UploadOptions
{
/// <summary>
/// File path to upload
/// </summary>
[DataMember(Name = "filePath", IsRequired = true)]
public string FilePath { get; set; }
/// <summary>
/// Server address
/// </summary>
[DataMember(Name = "server", IsRequired = true)]
public string Server { get; set; }
/// <summary>
/// File key
/// </summary>
[DataMember(Name = "fileKey")]
public string FileKey { get; set; }
/// <summary>
/// File name on the server
/// </summary>
[DataMember(Name = "fileName")]
public string FileName { get; set; }
/// <summary>
/// File Mime type
/// </summary>
[DataMember(Name = "mimeType")]
public string MimeType { get; set; }
/// <summary>
/// Additional options
/// </summary>
[DataMember(Name = "params")]
public string Params { get; set; }
/// <summary>
/// Flag to recognize if we should trust every host (only in debug environments)
/// </summary>
[DataMember(Name = "debug")]
public bool Debug { get; set; }
/// <summary>
/// Creates options object with default parameters
/// </summary>
public UploadOptions()
{
this.SetDefaultValues(new StreamingContext());
}
/// <summary>
/// Initializes default values for class fields.
/// Implemented in separate method because default constructor is not invoked during deserialization.
/// </summary>
/// <param name="context"></param>
[OnDeserializing()]
public void SetDefaultValues(StreamingContext context)
{
this.FileKey = "file";
this.FileName = "image.jpg";
this.MimeType = "image/jpeg";
}
}
/// <summary>
/// Uploading response info
/// </summary>
[DataContract]
public class FileUploadResult
{
/// <summary>
/// Amount of sent bytes
/// </summary>
[DataMember(Name = "bytesSent")]
public long BytesSent { get; set; }
/// <summary>
/// Server response code
/// </summary>
[DataMember(Name = "responseCode")]
public long ResponseCode { get; set; }
/// <summary>
/// Server response
/// </summary>
[DataMember(Name = "response", EmitDefaultValue = false)]
public string Response { get; set; }
/// <summary>
/// Creates FileUploadResult object with response values
/// </summary>
/// <param name="bytesSent">Amount of sent bytes</param>
/// <param name="responseCode">Server response code</param>
/// <param name="response">Server response</param>
public FileUploadResult(long bytesSent, long responseCode, string response)
{
this.BytesSent = bytesSent;
this.ResponseCode = responseCode;
this.Response = response;
}
}
/// <summary>
/// Represents transfer error codes for callback
/// </summary>
[DataContract]
public class FileTransferError
{
/// <summary>
/// Error code
/// </summary>
[DataMember(Name = "code", IsRequired = true)]
public int Code { get; set; }
/// <summary>
/// The source URI
/// </summary>
[DataMember(Name = "source", IsRequired = true)]
public string Source { get; set; }
/// <summary>
/// The target URI
/// </summary>
[DataMember(Name = "target", IsRequired = true)]
public string Target { get; set; }
/// <summary>
/// The http status code response from the remote URI
/// </summary>
[DataMember(Name = "http_status", IsRequired = true)]
public int HttpStatus { get; set; }
/// <summary>
/// Creates FileTransferError object
/// </summary>
/// <param name="errorCode">Error code</param>
public FileTransferError(int errorCode)
{
this.Code = errorCode;
this.Source = null;
this.Target = null;
this.HttpStatus = 0;
}
public FileTransferError(int errorCode, string source, string target, int status)
{
this.Code = errorCode;
this.Source = source;
this.Target = target;
this.HttpStatus = status;
}
}
/// <summary>
/// Upload options
/// </summary>
private UploadOptions uploadOptions;
/// <summary>
/// Bytes sent
/// </summary>
private long bytesSent;
/// <summary>
/// sends a file to a server
/// </summary>
/// <param name="options">Upload options</param>
public void upload(string options)
{
Debug.WriteLine("options = " + options);
options = options.Replace("{}", "null");
try
{
try
{
string[] args = JSON.JsonHelper.Deserialize<string[]>(options);
uploadOptions = JSON.JsonHelper.Deserialize<UploadOptions>(args[0]);
}
catch (Exception)
{
DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
return;
}
Uri serverUri;
try
{
serverUri = new Uri(uploadOptions.Server);
}
catch (Exception)
{
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(InvalidUrlError, uploadOptions.Server, null, 0)));
return;
}
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(serverUri);
webRequest.ContentType = "multipart/form-data;boundary=" + Boundary;
webRequest.Method = "POST";
webRequest.BeginGetRequestStream(WriteCallback, webRequest);
}
catch (Exception)
{
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(ConnectionError)));
}
}
public void download(string options)
{
DownloadOptions downloadOptions = null;
HttpWebRequest webRequest = null;
try
{
string[] optionStrings = JSON.JsonHelper.Deserialize<string[]>(options);
downloadOptions = new DownloadOptions();// JSON.JsonHelper.Deserialize<DownloadOptions>(options);
downloadOptions.Url = optionStrings[0];
downloadOptions.FilePath = optionStrings[1];
}
catch (Exception)
{
DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
return;
}
try
{
webRequest = (HttpWebRequest)WebRequest.Create(downloadOptions.Url);
}
catch (Exception)
{
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(InvalidUrlError, downloadOptions.Url, null, 0)));
return;
}
if (downloadOptions != null && webRequest != null)
{
DownloadRequestState state = new DownloadRequestState();
state.options = downloadOptions;
state.request = webRequest;
webRequest.BeginGetResponse(new AsyncCallback(downloadCallback), state);
}
}
/// <summary>
///
/// </summary>
/// <param name="asynchronousResult"></param>
private void downloadCallback(IAsyncResult asynchronousResult)
{
DownloadRequestState reqState = (DownloadRequestState)asynchronousResult.AsyncState;
HttpWebRequest request = reqState.request;
try
{
HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);
using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
{
// create the file if not exists
if (!isoFile.FileExists(reqState.options.FilePath))
{
var file = isoFile.CreateFile(reqState.options.FilePath);
file.Close();
}
using (FileStream fileStream = new IsolatedStorageFileStream(reqState.options.FilePath, FileMode.Open, FileAccess.Write, isoFile))
{
long totalBytes = response.ContentLength;
int bytesRead = 0;
using (BinaryReader reader = new BinaryReader(response.GetResponseStream()))
{
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);
}
else
{
writer.Close();
reader.Close();
fileStream.Close();
break;
}
}
}
}
}
}
WPCordovaClassLib.Cordova.Commands.File.FileEntry entry = new WPCordovaClassLib.Cordova.Commands.File.FileEntry(reqState.options.FilePath);
DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry));
}
catch (IsolatedStorageException)
{
// Trying to write the file somewhere within the IsoStorage.
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(FileNotFoundError)));
}
catch (SecurityException)
{
// Trying to write the file somewhere not allowed.
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(FileNotFoundError)));
}
catch (WebException webex)
{
// TODO: probably need better work here to properly respond with all http status codes back to JS
// Right now am jumping through hoops just to detect 404.
if ((webex.Status == WebExceptionStatus.ProtocolError && ((HttpWebResponse)webex.Response).StatusCode == HttpStatusCode.NotFound) || webex.Status == WebExceptionStatus.UnknownError)
{
// Weird MSFT detection of 404... seriously... just give us the f(*&#$@ status code as a number ffs!!!
// "Numbers for HTTP status codes? Nah.... let's create our own set of enums/structs to abstract that stuff away."
// FACEPALM
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(ConnectionError, null, null, 404)));
}
else
{
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(ConnectionError)));
}
}
catch (Exception)
{
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(FileNotFoundError)));
}
}
/// <summary>
/// Read file from Isolated Storage and sends it to server
/// </summary>
/// <param name="asynchronousResult"></param>
private void WriteCallback(IAsyncResult asynchronousResult)
{
try
{
HttpWebRequest webRequest = (HttpWebRequest)asynchronousResult.AsyncState;
using (Stream requestStream = (webRequest.EndGetRequestStream(asynchronousResult)))
{
string lineStart = "--";
string lineEnd = Environment.NewLine;
byte[] boundaryBytes = System.Text.Encoding.UTF8.GetBytes(lineStart + Boundary + lineEnd);
string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"" + lineEnd + lineEnd + "{1}" + lineEnd;
if (uploadOptions.Params != null)
{
string[] arrParams = uploadOptions.Params.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string param in arrParams)
{
string[] split = param.Split('=');
string key = split[0];
string val = split[1];
requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
string formItem = string.Format(formdataTemplate, key, val);
byte[] formItemBytes = System.Text.Encoding.UTF8.GetBytes(formItem);
requestStream.Write(formItemBytes, 0, formItemBytes.Length);
}
requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
}
using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
{
if (!isoFile.FileExists(uploadOptions.FilePath))
{
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(FileNotFoundError, uploadOptions.Server, uploadOptions.FilePath, 0)));
return;
}
using (FileStream fileStream = new IsolatedStorageFileStream(uploadOptions.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, uploadOptions.FileKey, uploadOptions.FileName, uploadOptions.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;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
{
requestStream.Write(buffer, 0, bytesRead);
bytesSent += bytesRead;
}
}
byte[] endRequest = System.Text.Encoding.UTF8.GetBytes(lineEnd + lineStart + Boundary + lineStart + lineEnd);
requestStream.Write(endRequest, 0, endRequest.Length);
}
}
webRequest.BeginGetResponse(ReadCallback, webRequest);
}
catch (Exception)
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(ConnectionError)));
});
}
}
/// <summary>
/// Reads response into FileUploadResult
/// </summary>
/// <param name="asynchronousResult"></param>
private void ReadCallback(IAsyncResult asynchronousResult)
{
try
{
HttpWebRequest webRequest = (HttpWebRequest)asynchronousResult.AsyncState;
using (HttpWebResponse response = (HttpWebResponse)webRequest.EndGetResponse(asynchronousResult))
{
using (Stream streamResponse = response.GetResponseStream())
{
using (StreamReader streamReader = new StreamReader(streamResponse))
{
string responseString = streamReader.ReadToEnd();
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileUploadResult(bytesSent, (long)response.StatusCode, responseString)));
});
}
}
}
}
catch (Exception)
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
FileTransferError transferError = new FileTransferError(ConnectionError, uploadOptions.Server, uploadOptions.FilePath, 403);
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, transferError));
});
}
}
}
}