4
* Copyright 2009, Moxiecode Systems AB
5
* Released under GPL License.
7
* License: http://www.plupload.com/license
8
* Contributing: http://www.plupload.com/contributing
13
using System.Threading;
14
using System.Windows.Threading;
16
using System.Text.RegularExpressions;
17
using System.Windows.Browser;
18
using System.Windows.Media.Imaging;
19
using System.Collections.Generic;
20
using FluxJpeg.Core.Encoder;
22
using Plupload.PngEncoder;
24
namespace Moxiecode.Plupload {
31
/// Description of File.
33
public class FileReference {
34
#region private fields
35
private string name, uploadUrl, id, targetName, mimeType;
36
private FileInfo info;
37
private SynchronizationContext syncContext;
38
private int chunks, chunkSize;
39
private bool multipart, chunking;
40
private long size, chunk;
41
private string fileDataName;
42
private Dictionary<string, object> multipartParams;
43
private Dictionary<string, object> headers;
44
private Stream fileStream;
45
private Stream imageStream;
46
private HttpWebRequest req;
49
/// <summary>Upload complete delegate.</summary>
50
public delegate void UploadCompleteHandler(object sender, UploadEventArgs e);
52
/// <summary>Upload chunk compleate delegate.</summary>
53
public delegate void UploadChunkCompleteHandler(object sender, UploadEventArgs e);
55
/// <summary>Upload error delegate.</summary>
56
public delegate void ErrorHandler(object sender, ErrorEventArgs e);
58
/// <summary>Upload progress delegate.</summary>
59
public delegate void ProgressHandler(object sender, ProgressEventArgs e);
61
/// <summary>Upload complete event</summary>
62
public event UploadCompleteHandler UploadComplete;
64
/// <summary>Upload chunk complete event</summary>
65
public event UploadChunkCompleteHandler UploadChunkComplete;
67
/// <summary>Error event</summary>
68
public event ErrorHandler Error;
70
/// <summary>Progress event</summary>
71
public event ProgressHandler Progress;
74
/// Main constructor for the file reference.
76
/// <param name="id">Unique file id for item.</param>
77
/// <param name="info">FileInfo that got returned from a file selection.</param>
78
public FileReference(string id, FileInfo info) {
80
this.name = info.Name;
82
this.size = info.Length;
85
/// <summary>Unique id for the file reference.</summary>
90
/// <summary>File name to use with upload.</summary>
96
/// <summary>File size for the selected file.</summary>
98
get { return this.size; }
102
/// Uploads the file to the specific url and using the specified chunk_size.
104
/// <param name="upload_url">URL to upload to.</param>
105
/// <param name="chunk_size">Chunk size to use.</param>
106
/// <param name="image_width">Image width to scale to.</param>
107
/// <param name="image_height">Image height to scale to.</param>
108
/// <param name="image_quality">Image quality to store as.</param>
109
public void Upload(string upload_url, string json_settings) {
110
int chunkSize = 0, imageWidth = 0, imageHeight = 0, imageQuality = 90;
112
Dictionary<string, object> settings = (Dictionary<string, object>) Moxiecode.Plupload.Utils.JsonReader.ParseJson(json_settings);
114
chunkSize = Convert.ToInt32(settings["chunk_size"]);
115
imageWidth = Convert.ToInt32(settings["image_width"]);
116
imageHeight = Convert.ToInt32(settings["image_height"]);
117
imageQuality = Convert.ToInt32(settings["image_quality"]);
118
this.fileDataName = (string)settings["file_data_name"];
119
this.multipart = Convert.ToBoolean(settings["multipart"]);
120
this.multipartParams = (Dictionary<string, object>)settings["multipart_params"];
121
this.headers = (Dictionary<string, object>)settings["headers"];
122
this.targetName = (string) settings["name"];
123
this.mimeType = (string) settings["mime"];
126
this.chunking = chunkSize > 0;
128
this.uploadUrl = upload_url;
131
// Is jpeg and image size is defined
132
if (Regex.IsMatch(this.name, @"\.(jpeg|jpg|png)$", RegexOptions.IgnoreCase) && (imageWidth != 0 || imageHeight != 0)) {
133
if (Regex.IsMatch(this.name, @"\.png$"))
134
this.imageStream = this.ResizeImage(this.info.OpenRead(), imageWidth, imageHeight, imageQuality, ImageType.Png);
136
this.imageStream = this.ResizeImage(this.info.OpenRead(), imageWidth, imageHeight, imageQuality, ImageType.Jpeg);
138
this.imageStream.Seek(0, SeekOrigin.Begin);
139
this.size = this.imageStream.Length;
141
} catch (Exception ex) {
142
syncContext.Send(delegate {
143
this.OnIOError(new ErrorEventArgs(ex.Message, 0, this.chunks));
148
this.chunkSize = chunkSize;
149
this.chunks = (int) Math.Ceiling((double) this.Size / (double) chunkSize);
151
this.chunkSize = (int) this.Size;
155
this.UploadNextChunk();
158
private int ReadByteRange(byte[] buffer, long position, int offset, int count) {
161
// Read from image memory stream if it's defined
162
if (this.imageStream != null) {
163
this.imageStream.Seek(position, SeekOrigin.Begin);
164
return this.imageStream.Read(buffer, offset, count);
167
// Open the file and read the specified part of it
168
if (this.fileStream == null) {
169
this.fileStream = this.info.OpenRead();
172
bytes = this.fileStream.Read(buffer, offset, count);
178
/// Uploads the next chunk if there are more in queue.
180
/// <returns>True/false if there are more chunks to be uploaded.</returns>
181
public bool UploadNextChunk() {
182
string url = this.uploadUrl;
184
// Is there more chunks
185
if (this.chunk >= this.chunks)
188
this.syncContext = SynchronizationContext.Current;
190
// Add name, chunk and chunks to query string when we don't use multipart
191
if (!this.multipart) {
192
if (url.IndexOf('?') == -1) {
196
url += "name=" + Uri.EscapeDataString(this.targetName);
199
url += "&chunk=" + this.chunk;
200
url += "&chunks=" + this.chunks;
204
this.req = WebRequest.Create(new Uri(HtmlPage.Document.DocumentUri, url)) as HttpWebRequest;
205
this.req.Method = "POST";
207
// Add custom headers
208
if (this.headers != null) {
209
foreach (string key in this.headers.Keys) {
210
if (this.headers[key] == null)
213
switch (key.ToLower())
215
// in silverlight 3, these are set by the web browser that hosts the Silverlight application.
216
// http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest%28v=vs.95%29.aspx
218
case "content-length":
220
case "if-modified-since":
222
case "transfer-encoding":
226
// in silverlight this isn't supported, can not find reference to why not
230
// in .NET Framework 3.5 and below, these are set by the system.
231
// http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest%28v=VS.90%29.aspx
237
this.req.Accept = (string)this.headers[key];
241
this.req.ContentType = (string)this.headers[key];
244
this.req.Headers[key] = (string)this.headers[key];
250
IAsyncResult asyncResult = this.req.BeginGetRequestStream(new AsyncCallback(RequestStreamCallback), this.req);
256
/// Cancels uploading the current file.
258
public void CancelUpload() {
259
if (this.req != null) {
266
#region protected methods
268
protected virtual void OnUploadComplete(UploadEventArgs e) {
271
if (UploadComplete != null)
272
UploadComplete(this, e);
275
protected virtual void OnUploadChunkComplete(UploadEventArgs e) {
276
if (UploadChunkComplete != null)
277
UploadChunkComplete(this, e);
280
protected virtual void OnIOError(ErrorEventArgs e) {
287
protected virtual void OnProgress(ProgressEventArgs e) {
288
if (Progress != null)
294
#region private methods
296
private void RequestStreamCallback(IAsyncResult ar) {
297
HttpWebRequest request = (HttpWebRequest) ar.AsyncState;
298
string boundary = "----pluploadboundary" + DateTime.Now.Ticks, dashdash = "--", crlf = "\r\n";
299
Stream requestStream = null;
300
byte[] buffer = new byte[1048576], strBuff;
302
long loaded = 0, end = 0;
303
int percent, lastPercent = 0;
306
requestStream = request.EndGetRequestStream(ar);
308
if (this.multipart) {
309
request.ContentType = "multipart/form-data; boundary=" + boundary;
311
// Add name to multipart array
312
this.multipartParams["name"] = this.targetName;
314
// Add chunking when needed
316
this.multipartParams["chunk"] = this.chunk;
317
this.multipartParams["chunks"] = this.chunks;
320
// Append mutlipart parameters
321
foreach (KeyValuePair<string, object> pair in this.multipartParams) {
322
strBuff = this.StrToByteArray(dashdash + boundary + crlf +
323
"Content-Disposition: form-data; name=\"" + pair.Key + '"' + crlf + crlf +
327
requestStream.Write(strBuff, 0, strBuff.Length);
330
// Append multipart file header
331
strBuff = this.StrToByteArray(
332
dashdash + boundary + crlf +
333
"Content-Disposition: form-data; name=\"" + this.fileDataName + "\"; filename=\"" + this.name + '"' +
334
crlf + "Content-Type: " + this.mimeType + crlf + crlf
337
requestStream.Write(strBuff, 0, strBuff.Length);
339
request.ContentType = "application/octet-stream";
343
loaded = this.chunk * this.chunkSize;
346
end = (this.chunk + 1) * this.chunkSize;
350
while (loaded < end && (bytes = ReadByteRange(buffer, loaded, 0, (int)(end - loaded < buffer.Length ? end - loaded : buffer.Length))) != 0) {
352
percent = (int) Math.Round((double) loaded / (double) this.Size * 100.0);
354
if (percent > lastPercent) {
355
syncContext.Post(delegate {
356
if (percent > lastPercent) {
357
this.OnProgress(new ProgressEventArgs(loaded, this.Size));
358
lastPercent = percent;
363
requestStream.Write(buffer, 0, bytes);
364
requestStream.Flush();
367
// Append multipart file footer
368
if (this.multipart) {
369
strBuff = this.StrToByteArray(crlf + dashdash + boundary + dashdash + crlf);
370
requestStream.Write(strBuff, 0, strBuff.Length);
372
} catch (Exception ex) {
373
syncContext.Send(delegate {
374
this.OnIOError(new ErrorEventArgs(ex.Message, this.chunk, this.chunks));
378
if (requestStream != null) {
379
requestStream.Close();
380
requestStream.Dispose();
381
requestStream = null;
383
} catch (Exception ex) {
384
syncContext.Send(delegate {
385
this.OnIOError(new ErrorEventArgs(ex.Message, this.chunk, this.chunks));
391
request.BeginGetResponse(new AsyncCallback(ResponseCallback), request);
393
catch (WebException ex)
395
if (ex.Status != WebExceptionStatus.RequestCanceled) {
396
syncContext.Send(delegate {
397
this.OnIOError(new ErrorEventArgs(ex.Message, this.chunk, this.chunks));
401
catch (Exception ex) {
402
syncContext.Send(delegate {
403
this.OnIOError(new ErrorEventArgs(ex.Message, this.chunk, this.chunks));
408
private void ResponseCallback(IAsyncResult ar) {
411
HttpWebRequest request = ar.AsyncState as HttpWebRequest;
413
WebResponse response = request.EndGetResponse(ar);
415
syncContext.Post(ExtractResponse, response);
417
catch (WebException ex) {
418
if (ex.Status != WebExceptionStatus.RequestCanceled) {
419
syncContext.Send(delegate {
420
this.OnIOError(new ErrorEventArgs(ex.Message, this.chunk, this.chunks));
424
catch (Exception ex) {
425
syncContext.Send(delegate {
426
this.OnIOError(new ErrorEventArgs(ex.Message, this.chunk, this.chunks));
431
private void ExtractResponse(object state) {
432
HttpWebResponse response = state as HttpWebResponse;
433
StreamReader respReader = null;
434
Stream respStream = null;
438
respStream = response.GetResponseStream();
440
if (response.StatusCode == HttpStatusCode.OK) {
441
respReader = new StreamReader(respStream);
443
if (respStream != null) {
444
content = respReader.ReadToEnd();
446
throw new Exception("Error could not open response stream.");
448
throw new Exception("Error server returned status: " + ((int) response.StatusCode) + " " + response.StatusDescription);
452
syncContext.Send(delegate {
453
this.OnUploadChunkComplete(new UploadEventArgs(content, this.chunk - 1, this.chunks));
455
} catch (Exception ex) {
456
syncContext.Send(delegate {
457
this.OnIOError(new ErrorEventArgs(ex.Message, chunk, chunks));
460
if (respStream != null)
463
if (respReader != null)
470
private void DisposeStreams() {
471
if (fileStream != null) {
472
fileStream.Dispose();
476
if (imageStream != null) {
477
imageStream.Dispose();
482
private void Debug(string msg) {
483
((ScriptObject) HtmlPage.Window.Eval("console")).Invoke("log", new string[] {msg});
486
private Stream ResizeImage(Stream image_stream, int width, int height, int quality, ImageType type) {
488
// Load the image as a writeablebitmap
489
WriteableBitmap writableBitmap;
490
BitmapImage bitmapImage = new BitmapImage();
491
bitmapImage.SetSource(image_stream);
492
writableBitmap = new WriteableBitmap(bitmapImage);
494
double scale = Math.Min((double) width / writableBitmap.PixelWidth, (double) height / writableBitmap.PixelHeight);
500
// Setup shorter names and pixelbuffers
501
int w = writableBitmap.PixelWidth;
502
int h = writableBitmap.PixelHeight;
503
int[] p = writableBitmap.Pixels;
504
byte[][,] imageRaster = new byte[3][,]; // RGB colors
505
imageRaster[0] = new byte[w, h];
506
imageRaster[1] = new byte[w, h];
507
imageRaster[2] = new byte[w, h];
509
// Copy WriteableBitmap data into buffer for FluxJpeg
511
for (int y = 0; y < h; y++) {
512
for (int x = 0; x < w; x++) {
515
imageRaster[0][x, y] = (byte) (color >> 16); // R
516
imageRaster[1][x, y] = (byte) (color >> 8); // G
517
imageRaster[2][x, y] = (byte) (color); // B
521
// Create new FluxJpeg image based on pixel data
522
FluxJpeg.Core.Image jpegImage = new FluxJpeg.Core.Image(new ColorModel {
523
colorspace = ColorSpace.RGB
526
// Calc new proportional size
527
width = (int) Math.Round(writableBitmap.PixelWidth * scale);
528
height = (int) Math.Round(writableBitmap.PixelHeight * scale);
531
ImageResizer resizer = new ImageResizer(jpegImage);
532
Image resizedImage = resizer.Resize(width, height, FluxJpeg.Core.Filtering.ResamplingFilters.LowpassAntiAlias);
533
Stream imageStream = new MemoryStream();
535
if (type == ImageType.Jpeg) {
536
// Encode the resized image as Jpeg
537
JpegEncoder jpegEncoder = new JpegEncoder(resizedImage, quality, imageStream);
538
jpegEncoder.Encode();
540
int[] pixelBuffer = new int[resizedImage.Height * resizedImage.Width];
541
byte[][,] resizedRaster = resizedImage.Raster;
543
// Convert FJCore raster to PixelBuffer
544
for (int y = 0; y < resizedImage.Height; y++) {
545
for (int x = 0; x < resizedImage.Width; x++) {
548
color = color | resizedRaster[0][x, y] << 16; // R
549
color = color | resizedRaster[1][x, y] << 8; // G
550
color = color | resizedRaster[2][x, y]; // B
552
pixelBuffer[(y * resizedImage.Width) + x] = color;
556
// Encode the resized image as Png
557
PngEncoder pngEncoder = new PngEncoder(pixelBuffer, resizedImage.Width, resizedImage.Height, false, PngEncoder.FILTER_NONE, Deflater.BEST_COMPRESSION);
558
byte[] pngBuffer = pngEncoder.pngEncode();
559
imageStream.Write(pngBuffer, 0, pngBuffer.Length);
564
// Ignore the error and let the server resize the image
570
private byte[] StrToByteArray(string str) {
571
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
573
return encoding.GetBytes(str);
580
/// Upload event arguments class.
582
public class UploadEventArgs : EventArgs {
583
#region private fields
584
private string response;
590
/// Main constructor for the upload event.
592
/// <param name="response">Response contents as a string.</param>
593
public UploadEventArgs(string response) : this(response, 0, 0) {
597
/// Main constructor for the upload event.
599
/// <param name="response">Response contents as a string.</param>
600
/// <param name="chunk">Current chunk number.</param>
601
/// <param name="chunks">Total chunks.</param>
602
public UploadEventArgs(string response, long chunk, int chunks) {
603
this.response = response;
605
this.chunks = chunks;
608
/// <summary>Response from upload request.</summary>
609
public string Response {
610
get { return response; }
613
/// <summary>Chunk number.</summary>
615
get { return chunk; }
618
/// <summary>Total number of chunks.</summary>
620
get { return chunks; }
625
/// Error event arguments class.
627
public class ErrorEventArgs : EventArgs {
628
#region private fields
629
private string message;
635
/// Main constructor for the error event.
637
/// <param name="message">Error message.</param>
638
public ErrorEventArgs(string message) : this(message, 0, 0) {
639
this.message = message;
643
/// Main constructor for the error event.
645
/// <param name="message">Error message.</param>
646
/// <param name="chunk">Current chunk number.</param>
647
/// <param name="chunks">Total chunks.</param>
648
public ErrorEventArgs(string message, long chunk, int chunks) {
649
this.message = message;
651
this.chunks = chunks;
654
/// <summary>Chunk number.</summary>
656
get { return chunk; }
659
/// <summary>Total number of chunks.</summary>
661
get { return chunks; }
664
/// <summary>Error message.</summary>
665
public string Message {
666
get { return message; }
671
/// Progress event arguments class.
673
public class ProgressEventArgs : EventArgs {
674
#region private fields
675
private long loaded, total;
679
/// Main constructor for the progress events args.
681
/// <param name="loaded">Number of bytes uploaded.</param>
682
/// <param name="total">Total bytes to upload.</param>
683
public ProgressEventArgs(long loaded, long total) {
684
this.loaded = loaded;
688
/// <summary>Total bytes to upload.</summary>
690
get { return total; }
693
/// <summary>Number of bytes upload so far.</summary>
695
get { return loaded; }