mirror of
https://github.com/winscp/winscp.git
synced 2026-10-24 02:28:55 +03:00
265 lines
8.7 KiB
C#
265 lines
8.7 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Xml;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
namespace WinSCP
|
|
{
|
|
internal class SessionLogReader : CustomLogReader
|
|
{
|
|
public SessionLogReader(Session session) :
|
|
base(session)
|
|
{
|
|
_position = 0;
|
|
}
|
|
|
|
public void SetTimeouted()
|
|
{
|
|
_timeouted = true;
|
|
}
|
|
|
|
public override void Dispose()
|
|
{
|
|
using (Session.Logger.CreateCallstack())
|
|
{
|
|
LogContents();
|
|
Cleanup();
|
|
}
|
|
base.Dispose();
|
|
}
|
|
|
|
private void Cleanup()
|
|
{
|
|
if (_stream != null)
|
|
{
|
|
Session.Logger.WriteLine("Closing log");
|
|
_stream.Dispose();
|
|
_stream = null;
|
|
}
|
|
|
|
if (_reader != null)
|
|
{
|
|
((IDisposable)_reader).Dispose();
|
|
_reader = null;
|
|
}
|
|
}
|
|
|
|
public override bool Read(LogReadFlags flags)
|
|
{
|
|
using (Session.Logger.CreateCallstack())
|
|
{
|
|
bool result;
|
|
if (_timeouted)
|
|
{
|
|
Session.Logger.WriteLine("Not reading, session has timed out");
|
|
result = false;
|
|
}
|
|
else
|
|
{
|
|
bool retry;
|
|
|
|
do
|
|
{
|
|
result = DoRead();
|
|
|
|
retry = false;
|
|
|
|
if (result &&
|
|
IsNonEmptyElement("failure"))
|
|
{
|
|
SessionRemoteException e = SessionRemoteException.ReadFailure(this);
|
|
|
|
Session.RaiseFailed(e);
|
|
|
|
if ((flags & LogReadFlags.ThrowFailures) == 0)
|
|
{
|
|
retry = true;
|
|
}
|
|
else
|
|
{
|
|
throw Session.Logger.WriteException(e);
|
|
}
|
|
}
|
|
}
|
|
while (retry);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
private bool DoRead()
|
|
{
|
|
int interval = 50;
|
|
bool result;
|
|
|
|
do
|
|
{
|
|
if (_reader == null)
|
|
{
|
|
OpenLog();
|
|
}
|
|
|
|
Debug.Assert(_reader != null);
|
|
|
|
try
|
|
{
|
|
result = _reader.Read();
|
|
if (result)
|
|
{
|
|
++_position;
|
|
Session.Logger.WriteLine("Read node {0}: {1} {2}{3}{4}",
|
|
_position, _reader.NodeType, _reader.Name,
|
|
(_reader.HasValue && !string.IsNullOrEmpty(_reader.Name) && !string.IsNullOrEmpty(_reader.Value) ? "=" : string.Empty),
|
|
_reader.Value);
|
|
Session.GotOutput();
|
|
}
|
|
else
|
|
{
|
|
Session.Logger.WriteLine("Cannot read");
|
|
|
|
if (!_closed)
|
|
{
|
|
// this should not happen as when the log is not closed,
|
|
// we should get XmlException on reaching the end
|
|
_closed = true;
|
|
Cleanup();
|
|
}
|
|
|
|
Session.CheckForTimeout();
|
|
}
|
|
}
|
|
catch (XmlException e)
|
|
{
|
|
Cleanup();
|
|
|
|
// check if the the root cause was session abort
|
|
Session.CheckForTimeout();
|
|
LogContents();
|
|
string message = "Error parsing session log file";
|
|
// This is possibly a race condition, as we may not have processed the event with the error yet
|
|
// The ExeSessionProcess loops every 100ms
|
|
Thread.Sleep(200);
|
|
string s = Session.GetErrorOutputMessage();
|
|
if (!string.IsNullOrEmpty(s))
|
|
{
|
|
message += " - " + s;
|
|
}
|
|
throw Session.Logger.WriteException(new SessionLocalException(Session, message, e));
|
|
}
|
|
|
|
if (!result && !_closed)
|
|
{
|
|
Session.Logger.WriteLine("Waiting for log update and dispatching events for {0}", interval);
|
|
Session.DispatchEvents(interval);
|
|
if (interval < 500)
|
|
{
|
|
interval *= 2;
|
|
}
|
|
}
|
|
}
|
|
while (!result && !_closed);
|
|
|
|
if (result)
|
|
{
|
|
LogContents();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private void LogContents()
|
|
{
|
|
if (Session.Logger.Logging)
|
|
{
|
|
try
|
|
{
|
|
// alterative to File.ReadAllText with write-sharing
|
|
// (note that the StreamReader disposes the Stream)
|
|
using (StreamReader reader = new StreamReader(new FileStream(Session.XmlLogPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Encoding.UTF8))
|
|
{
|
|
string contents = reader.ReadToEnd();
|
|
if ((_logged == null) || (_logged != contents))
|
|
{
|
|
Session.Logger.WriteLine("Log contents:\n{0}", contents);
|
|
_logged = contents;
|
|
}
|
|
else
|
|
{
|
|
Session.Logger.WriteLine("Log contents has not changed");
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Session.Logger.WriteLine("Error logging log contents [{0}]", e.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OpenLog()
|
|
{
|
|
if (_closed)
|
|
{
|
|
throw Session.Logger.WriteException(new InvalidOperationException("Log was closed already"));
|
|
}
|
|
|
|
try
|
|
{
|
|
Session.Logger.WriteLine("Opening log without write sharing");
|
|
// First try to open file without write sharing.
|
|
// This fails, if WinSCP is still writing to the log file.
|
|
// This is done only as a way to detect that log file is not complete yet.
|
|
_stream = new PatientFileStream(Session, Session.XmlLogPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
|
_closed = true;
|
|
LogContents();
|
|
}
|
|
catch (IOException)
|
|
{
|
|
Session.Logger.WriteLine("Opening log with write sharing");
|
|
// If log file is still being written to, open it with write sharing
|
|
_stream = new PatientFileStream(Session, Session.XmlLogPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
|
_closed = false;
|
|
}
|
|
|
|
Session.Logger.WriteLine("Log opened");
|
|
|
|
// Allow control characters in log
|
|
var settings = new XmlReaderSettings() { CheckCharacters = false };
|
|
_reader = XmlReader.Create(_stream, settings);
|
|
|
|
int skip = _position;
|
|
Session.Logger.WriteLine("Skipping {0} nodes", skip);
|
|
while (skip > 0)
|
|
{
|
|
if (!_reader.Read())
|
|
{
|
|
throw Session.Logger.WriteException(new SessionLocalException(Session, "Read less nodes than in previous log parsing"));
|
|
}
|
|
--skip;
|
|
}
|
|
}
|
|
|
|
internal override XmlReader Reader
|
|
{
|
|
get
|
|
{
|
|
if (_reader == null)
|
|
{
|
|
throw Session.Logger.WriteException(new SessionLocalException(Session, "Reading has not commenced yet"));
|
|
}
|
|
return _reader;
|
|
}
|
|
}
|
|
|
|
private int _position;
|
|
private XmlReader _reader;
|
|
private PatientFileStream _stream;
|
|
private bool _closed;
|
|
private string _logged;
|
|
private bool _timeouted;
|
|
}
|
|
}
|