/*
* Copyright (C) 2004-2005 Jonathan Bindel
* Copyright (C) 2006-2007 Eskil Bylund
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Timers;
using ICSharpCode.SharpZipLib.Zip.Compression;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using DCSharp.Backend.Managers;
using DCSharp.Backend.Objects;
using DCSharp.Backend.Protocols;
using DCSharp.Extras;
using DCSharp.Logging;
namespace DCSharp.Backend.Connections{
public enum TransferDirection
{
Unknown,
Up,
Down
}
public enum TransferError
{
Unknown,
Aborted,
NoSlots,
NotAvailable
}
public class TransferErrorEventArgs : TransferEventArgs
{
public TransferErrorEventArgs(TransferFileInfo transfer,
TransferError error, string message) : base(transfer)
{
this.error = error;
this.message = message;
}
public TransferError Error
{
get { return error; }
}
private TransferError error;
public string Message
{
get { return message; }
}
private string message;
}
public class UserConnection : Connection
{
public event EventHandler RemoteIdentityChanged;
// FIXME: The state of the TransferInfo should be reset before emitting
// any of these events.
public event EventHandler DirectionChanged;
public event EventHandler<TransferEventArgs> TransferStarted;
public event EventHandler<TransferEventArgs> TransferCompleted;
public event EventHandler<TransferErrorEventArgs> TransferAborted;
private static Logger log = LogManager.GetLogger("UserConnection");
private object context;
private FileStream fileStream;
private Timer activityTimer;
private int lastActivity;
private int lastUpdate;
private bool disposed;
#region Constructors
public UserConnection(IPEndPoint endPoint, LocalIdentity localIdentity,
IUserProtocol protocol) : this(localIdentity, protocol)
{
if (endPoint == null)
{
throw new ArgumentNullException("endPoint");
}
context = endPoint.ToString();
EndPoint = endPoint;
}
public UserConnection(Socket client, LocalIdentity localIdentity,
IUserProtocol protocol) : this(localIdentity, protocol)
{
if (client == null)
{
throw new ArgumentNullException("client");
}
Client = client;
}
private UserConnection(LocalIdentity localIdentity, IUserProtocol protocol) :
base(protocol)
{
if (localIdentity == null)
{
throw new ArgumentNullException("localIdentity");
}
monitor = new ConnectionMonitor();
activityTimer = new Timer();
activityTimer.Elapsed += new ElapsedEventHandler(OnTimerElapsed);
activityTimer.Interval = 2000;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the remote user identity.
/// </summary>
/// <value>The remote user identity.</value>
public Identity RemoteIdentity
{
get { return remoteIdentity; }
set
{
remoteIdentity = value;
OnRemoteIdentityChanged();
}
}
private Identity remoteIdentity;
/// <summary>
/// Gets or sets the local user identity.
/// </summary>
/// <value>The local user identity.</value>
public Identity LocalIdentity
{
get { return localIdentity; }
set { localIdentity = value; }
}
private Identity localIdentity;
/// <summary>
/// Gets or sets the hub this connection was established on.
/// </summary>
/// <value>The hub this connection was established on.</value>
public HubConnection Hub
{
get { return hub; }
set { hub = value; }
}
private HubConnection hub;
#region Transfer
public TransferDirection Direction
{
get { return direction; }
internal set
{
direction = value;
OnDirectionChanged();
}
}
private TransferDirection direction;
public UploadFileInfo Upload
{
get { return upload; }
}
private UploadFileInfo upload;
public bool Uploading
{
get { return upload != null; }
}
public DownloadFileInfo Download
{
get { return download; }
}
private DownloadFileInfo download;
public SourceInfo Source
{
get { return source; }
}
private SourceInfo source;
public bool Downloading
{
get { return download != null; }
}
public double Bps
{
get
{
if (Downloading)
{
return monitor.DownloadSpeed;
}
else if (Uploading)
{
return monitor.UploadSpeed;
}
return 0;
}
}
public ConnectionMonitor Monitor
{
get { return monitor; }
}
private ConnectionMonitor monitor;
#endregion
protected IUserProtocol UserProtocol
{
get { return (IUserProtocol)Protocol; }
}
#endregion
#region Methods
#region Connect
private void OnTimerElapsed(object sender, ElapsedEventArgs args)
{
uint elapsed = (uint)(Environment.TickCount - lastActivity);
if (elapsed >= 10000)
{
OnError(ConnectionError.Timeout, null);
Disconnect();
}
}
#endregion
#region Send
internal override void Send(string message, System.Text.Encoding encoding)
{
lastActivity = Environment.TickCount;
base.Send(message, encoding);
log.Debug("Sent: " + message, null, context);
}
internal override void ConnectionSend(string message)
{
lastActivity = Environment.TickCount;
base.ConnectionSend(message);
log.Debug("Sent: " + message, null, context);
}
#endregion
#region Receive
protected override void HandleMessage(string message)
{
log.Debug("Received: " + message, null, context);
lastActivity = Environment.TickCount;
base.HandleMessage(message);
}
#endregion
#region Upload
internal void UploadFile(UploadFileInfo upload)
{
UploadFile(upload, false);
}
internal void UploadFile(UploadFileInfo upload, bool compress)
{
if (upload == null)
{
throw new ArgumentNullException("upload");
}
if (Uploading || Downloading)
{
throw new InvalidOperationException("File transfer in progress.");
}
log.Info(String.Format("Uploading {0}, {1} - {2}", upload.Name,
upload.Position, upload.RequestedBytes), null, context);
Stream outputStream = null;
try
{
// Open the file stream
fileStream = new FileStream(upload.Name, FileMode.Open,
FileAccess.Read, FileShare.Read);
long endPos = upload.Position + upload.RequestedBytes;
if (endPos > fileStream.Length)
{
throw new ArgumentOutOfRangeException("To many bytes requested.");
}
outputStream = new NetworkStream(Client, false);
if (compress)
{
outputStream = new DeflaterOutputStream(outputStream,
new Deflater(Deflater.BEST_SPEED));
}
// Update objects
this.upload = upload;
upload.UserConnection = this;
OnTransferStarted(upload);
// Send
byte[] bytes = new byte[BufferSize];
fileStream.Position = upload.Position;
while (fileStream.Position < endPos)
{
int length = fileStream.Read(bytes, 0, bytes.Length);
if (length > 0)
{
outputStream.Write(bytes, 0, length);
upload.Position += length;
monitor.BytesSent(length);
UpdateMonitor();
}
else
{
log.Error("Got end of stream", null, context);
break;
}
lastActivity = Environment.TickCount;
}
log.Info("Upload finished", null, context);
OnTransferCompleted(upload);
}
catch (Exception e)
{
log.Error("Upload failed", e, context);
OnTransferAborted(upload, TransferError.Unknown, null);
Disconnect();
}
finally
{
if (outputStream != null)
{
outputStream.Close();
}
DisposeFileStream();
ResetTransfer();
}
}
#endregion
#region Download
public void DownloadFile(DownloadFileInfo download, SourceInfo source)
{
if (download == null)
{
throw new ArgumentNullException("download");
}
if (source == null)
{
throw new ArgumentNullException("source");
}
if (Uploading || Downloading)
{
throw new InvalidOperationException("File transfer in progress.");
}
log.Info(String.Format("Downloading {0}", download.Name), null, context);
OpenFileStream(download);
// Update objects
source.Active = true;
download.UserConnection = this;
this.download = download;
this.source = source;
// Request download
this.UserProtocol.DownloadFile(download, source);
OnTransferStarted(download);
}
public void AbortTransfer()
{
//if (!(Downloading || Uploading))
//{
// throw new InvalidOperationException("No file transfer in progress.");
//}
Disconnect();
}
protected override int HandleData(byte[] buffer, int offset, int count)
{
if (!Downloading || fileStream == null)
{
// Download aborted. Discard the data.
return count;
}
if (fileStream.Position + count > download.Size)
{
count = (int)(download.Size - fileStream.Position);
}
fileStream.Write(buffer, offset, count);
download.Position = fileStream.Position;
monitor.BytesReceived(count);
UpdateMonitor();
lastActivity = Environment.TickCount;
if (fileStream.Position >= download.Size)
{
FinalizeDownload();
}
return count;
}
internal void BeginReceive()
{
log.Debug("Receiving " + download.Name, null, context);
if (Downloading)
{
Mode = ConnectionMode.Data;
}
else
{
log.Error("Inconsistent state");
Disconnect();
}
}
internal void DownloadError(TransferError error, string reason)
{
log.Error("Download error: " + error + " - " + reason, null, context);
if (error != TransferError.Aborted && source != null)
{
if (error == TransferError.NotAvailable)
{
source.Available = false;
}
}
DisposeFileStream();
OnTransferAborted(download, error, reason);
ResetTransfer();
}
private void FinalizeDownload()
{
log.Info("Download complete", null, context);
Mode = ConnectionMode.Message;
if (download.Target == null)
{
download.Target = Runtime.Settings.DownloadDirectory;
}
string tempFile = fileStream.Name;
string file = Path.Combine(download.Target, download.Name);
DisposeFileStream();
try
{
Util.MoveFile(tempFile, file, true);
}
catch
{
// We don't have access to Target. Fallback to TempTarget
// TODO: Ask the user where to save the file
try
{
file = Path.Combine(download.TempTarget, download.Name);
Util.MoveFile(tempFile, file, true);
}
catch
{
file = tempFile;
}
}
// Complete
DownloadFileInfo completed = download;
ResetTransfer();
OnTransferCompleted(completed);
}
private void ResetTransfer()
{
if (download != null)
{
download.UserConnection = null;
download = null;
}
if (source != null)
{
source.Active = false;
source = null;
}
if (upload != null)
{
upload.UserConnection = null;
upload = null;
}
}
private bool OpenFileStream(DownloadFileInfo download)
{
if (download.TempTarget == null)
{
download.TempTarget = Runtime.Settings.TempDownloadDirectory;
}
// TODO: Check if the directory exists
Directory.CreateDirectory(download.TempTarget);
string tempFile = Path.Combine(download.TempTarget,
download.Name + ".dctmp");
try
{
fileStream = new FileStream(tempFile, FileMode.OpenOrCreate,
FileAccess.Write, FileShare.Read);
fileStream.Position = download.Position;
}
catch (IOException e)
{
log.Error("Opening file failed", e, context);
DisposeFileStream();
return false;
}
return true;
}
private void UpdateMonitor()
{
uint elapsed = (uint)(Environment.TickCount - lastUpdate);
if (elapsed >= 1000)
{
monitor.TimePeriodPassed();
lastUpdate = Environment.TickCount;
}
}
#endregion
protected override void OnError(ConnectionError error, Exception e)
{
log.Error(error.ToString(), e, context);
base.OnError(error, e);
}
protected override void OnStateChanged()
{
if (State == ConnectionState.Connected)
{
context = Client.RemoteEndPoint.ToString();
activityTimer.Start();
}
log.Info(State.ToString(), null, context);
base.OnStateChanged();
}
protected virtual void OnRemoteIdentityChanged()
{
if (RemoteIdentityChanged != null)
{
RemoteIdentityChanged(this, EventArgs.Empty);
}
}
protected virtual void OnDirectionChanged()
{
if (DirectionChanged != null)
{
DirectionChanged(this, EventArgs.Empty);
}
}
protected virtual void OnTransferStarted(TransferFileInfo transfer)
{
if (TransferStarted != null)
{
TransferStarted(this, new TransferEventArgs(transfer));
}
}
protected virtual void OnTransferCompleted(TransferFileInfo transfer)
{
if (TransferCompleted != null)
{
TransferCompleted(this, new TransferEventArgs(transfer));
}
}
protected virtual void OnTransferAborted(TransferFileInfo transfer,
TransferError error, string message)
{
if (TransferAborted != null)
{
TransferAborted(this, new TransferErrorEventArgs(transfer,
error, message));
}
}
private void DisposeFileStream()
{
if (fileStream != null)
{
fileStream.Flush();
fileStream.Close();
fileStream = null;
}
}
#region IDisposable Members
protected override void Dispose(bool disposing)
{
if (disposed)
{
return;
}
disposed = true;
if (disposing)
{
if (Downloading)
{
log.Info("Download aborted", null, context);
OnTransferAborted(download, TransferError.Aborted, null);
}
ResetTransfer();
DisposeFileStream();
activityTimer.Dispose();
}
base.Dispose(disposing);
}
#endregion
#endregion
}
}
|