📄 bufferupload.cs
字号:
using System;
using System.Net;
using System.IO;
using System.Security;
using System.Web.Services;
using System.Web.Services.Protocols;
using Microsoft.Web.Services2;
using Microsoft.Web.Services2.Dime;
namespace BufferedUploadWin
{
#region enums + eventargs class
public enum UploadState
{
Waiting,
Running,
Completed,
Failed,
Aborting
}
public delegate void ProgressChangedEventHandler(object sender, ProgressChangedEventArgs e);
public class ProgressChangedEventArgs : System.EventArgs
{
private int percentComplete = 0;
public int PercentComplete { get { return this.percentComplete; }}
public ProgressChangedEventArgs(int value)
{
this.percentComplete = value;
}
}
#endregion
/// <summary>
/// Uploads a file to the webservice.
/// </summary>
public sealed class BufferedUpload : UploadWin.BufferedUploadServer.UploadWse
{
private int bufferSize = 100000;
private long sBytes = 0;
private long filesize = 0;
private long sentBytes
{
set
{
this.sBytes = value;
try
{
if(this.sBytes > 0)
ProgressChanged(this, new ProgressChangedEventArgs((int)(((double)this.sBytes / (double)this.filesize)*100)));
}
catch {}
}
get { return this.sBytes; }
}
private bool overwrite = true;
private bool useIESettings = true;
private string instanceId = "";
private string filename = "";
private Exception lastError;
private System.Net.CookieContainer cookies = new System.Net.CookieContainer();
private UploadState state = UploadState.Waiting;
public event ProgressChangedEventHandler ProgressChanged;
public event System.EventHandler StateChanged;
public int ChunkSize { get { return this.bufferSize; } set { this.bufferSize = value; } }
public long SentBytes { get { return this.sentBytes; } }
public long Filesize { get { return this.filesize; } }
public bool UseIESettings { get { return this.useIESettings; } set { this.useIESettings = value; } }
public bool Overwrite { get { return this.overwrite; } set { this.overwrite = value; } }
public string InstanceId { get { return this.instanceId; } }
public string ServiceURL { get { return this.Url; } set { this.Url = value; Constructor(this.filename, this.Url); } }
public Exception LastError { get { return this.lastError; } set { this.lastError = value; } }
public UploadState State
{
get { return this.state; }
set
{
this.state = value;
try
{
StateChanged(this, new System.EventArgs());
}
catch {}
}
}
public BufferedUpload() { Constructor("", this.Url); }
public BufferedUpload(string Filename) { Constructor(Filename, this.Url); }
public BufferedUpload(string Filename, string url) { Constructor(Filename, url); }
private void Constructor(string Filename, string url)
{
this.Url = url;
this.filename = Filename.Trim();
if (this.useIESettings)
this.Credentials = System.Net.CredentialCache.DefaultCredentials;
}
/// <summary>
/// This method initializes a chunked file upload on a new thread, (non-blocking).
/// </summary>
/// <param name="Filename">The path and name of the file to upload.</param>
/// <param name="startPoint"/>The offset at which to begin </param>
public void BeginUploadChunks() { BeginUploadChunks(this.filename); }
public void BeginUploadChunks(string Filename) { BeginUploadChunks(Filename, 0); } // if a new filename is specified, begin at 0 offset
private void BeginUploadChunks(string Filename, long startPoint)
{
this.filename = Filename;
this.sentBytes = startPoint;
new UploadFileChunksDelegate(this.UploadFileChunks).BeginInvoke(null, null); // delegate based approach doesn't leave dangling threads if the app is closed unexpectedly
}
/// <summary>
/// This method initializes a chunked file upload on the current thread (so you'll have
/// to wait for it to complete), to start the upload on a new thread use BeginDownload() instead.
/// </summary>
/// <param name="Filename">The path and name of the file to upload.</param>
public delegate void UploadFileChunksDelegate();
public void UploadFileChunks() { UploadFileChunks(this.filename, this.sentBytes); }
public void UploadFileChunks(string Filename) { UploadFileChunks(Filename, 0); } // default to a new upload starting at 0 offset. retry can pass in the last good offset
private void UploadFileChunks(string Filename, long startPoint)
{
this.filename = Filename;
this.sentBytes = startPoint;
if (System.IO.File.Exists(Filename))
{
this.State = UploadState.Running;
FileInfo File = new FileInfo(Filename);
FileStream fs = new FileStream(Filename, FileMode.Open, FileAccess.Read);
fs.Seek(this.sentBytes, SeekOrigin.Begin); // this is relevent during a retry. otherwise, it just seeks to the start
this.CookieContainer = this.cookies; // required to maintain session state for this web service
this.filesize = (long) File.Length; // this can be used by the user interface to show "10 Mb / 400 Mb" uploaded type of messages.
int bytesRead = 0; // used to store the number of bytes written each time the buffer is read. will always be the chunksize, except for the last buffer, which will probably be smaller than the chunksize.
byte[] buffer = new byte[bufferSize];
if (this.sentBytes == 0)
{
// initialise an upload Instance to manage the file I/O for this upload
try
{
this.instanceId = this.Initialize(Path.GetFileName(Filename), this.overwrite);
}
catch(Exception ex)
{
// file probably already exists. but can't continue in any case so just quit.
fs.Close(); // for all exceptions
this.lastError = ex;
this.State = UploadState.Failed;
return;
}
}
bytesRead = fs.Read(buffer, 0, bufferSize); // read the first chunk in the buffer (which is re-used for every chunk)
// send the chunks to the web service one by one, until the Read() returns 0, meaning the entire file has been read.
while (bytesRead > 0 && this.state != UploadState.Aborting) // because this method runs on a thread started with BeginInvoke, there is no handle on it from the main thread. Hence the only way to tell this thread to quit is to change a shared variable, i.e. the 'State'
{
// copy the byte buffer into a memory stream to send in a dime attachment
MemoryStream ms = new MemoryStream(buffer, 0, buffer.Length);
DimeAttachment dimeAttach = new DimeAttachment("image/gif", TypeFormat.MediaType, ms);
this.RequestSoapContext.Attachments.Add(dimeAttach);
try
{
// send this chunk to the server
this.AppendChunk(this.instanceId, sentBytes, bytesRead);
// sentBytes is only updated AFTER a successful send of the bytes. so we can retry from the current sentBytes position if AppendChunk fails.
this.sentBytes += bytesRead;
}
catch(Exception ex)
{
fs.Close(); // for all exceptions, so we don't lock the file
if(this.state == UploadState.Aborting)
return; // AppendChunks() can cause exceptions if the web service object is disposed mid-transfer, ignore these errors in the case where the state was changed to aborting.
else
{
// error occurred, transfer corrupted. cancel the upload. the application can attempt to pick up from where the error occured, by calling Retry(), this is left up to the application to invoke ie.
this.lastError = ex.GetBaseException();
this.State = UploadState.Failed;
return;
}
}
bytesRead = fs.Read(buffer, 0, bufferSize); // read the next chunk (if it exists) into the buffer. the while loop will terminate if there is nothing left to read
}
// clean up
fs.Close();
this.RemoveInstance(this.instanceId); // remove the instance from the server now that we are finished with it
this.State = UploadState.Completed; // raise the StateChanged event so the user interface can update
}
else
throw new Exception("File not found!");
}
/// <summary>
/// Aborts the upload, by setting the state to 'aborting', which will terminate
/// the while loop running on the thread executing UploadChunks()
/// It also removes the instance from the web server session state
/// </summary>
public void Cancel()
{
this.state = UploadState.Aborting;
this.RemoveInstance(this.InstanceId);
}
/// <summary>
/// Attempt to continue a download that had an error mid-transfer.
/// The 'sentBytes' variable stores the number of bytes that have been successfully sent,
/// so we can (reasonably) safely assume it's ok to start from this position.
/// </summary>
public void Retry()
{
this.BeginUploadChunks(this.filename, this.sentBytes);
}
/// <summary>
/// Returns a description of a number of bytes, in appropriate units.
/// e.g.
/// passing in 1024 will return a string "1 Kb"
/// passing in 1230000 will return "1.23 Mb"
/// Megabytes and Gigabytes are formatted to 2 decimal places.
/// Kilobytes are rounded to whole numbers.
/// If the rounding results in 0, "1 Kb" is returned, because Windows behaves like this also.
/// </summary>
public static string GetFileSize(long numBytes)
{
string fileSize = "";
if(numBytes > 1000000000) fileSize = Math.Round((double)numBytes/1000000000, 2) + " Gb";
else if(numBytes > 1000000) fileSize = Math.Round((double)numBytes/1000000, 2) + " Mb";
else fileSize = Math.Round((double)numBytes/1000, 0) + " Kb";
if(fileSize == "0 Kb") fileSize = "1 Kb"; // min.
return fileSize;
}
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -