FileSystemNotifier upload event

+1 vote
asked Dec 21, 2020 by skay (130 points)

I am trying to use FileSystemNotifier to implement logging when a file is uploaded, including the file size. I'm using the built-in LocalFileSystemProvider at the moment.

So far I've run into the following issues trying to get the file size:

  • CreateCompleted: The e.ResultNode.Length property is always 0 (or, if the uploaded file is overwriting an existing one, then this property has the size of the existing file rather than the new one).
  • GetContentCompleted: I'm testing for e.ContentParameters.AccessType == NodeContentAccess.Write, which works, but the e.ResultContent.Length and e.Node.Length properties are always 0.
  • SetContentCompleted: Not fired when a file is uploaded.
  • SaveContentCompleted: Not fired when a file is uploaded.

It seems like the first two events do fire when the file is created but still has a size of 0, so I understand why the Length property is 0 in that case. But with the second two events not seeming to trigger at all,what can I do to get the file size of the new file after an uploaded is completed?

Applies to: File Server

1 Answer

0 votes
answered Dec 21, 2020 by renestein (3,010 points)
edited Dec 22, 2020 by renestein

Hello Skay,
Welcome to the Rebex forum and thanks for your question.

LocalFileSystemProvider uses internally .NET FileStream.
In our VFS lingo FileStream is ImmediateWriteStream https://api.rebex.net/?_ga=2.179352420.1735321122.1608551725-600862379.1463497785##RebexTotalPack.chm/Html/M_Rebex_IO_FileSystem_NodeContent_CreateImmediateWriteContent_1_bca7c9fe.htm
As documented.

"SaveContent method in ReadWriteFileSystemProvider is NEVER called. All data are written immediately using the suitable stream write methods."

Associated SaveContent events are not called as well. We are considering a small change - SaveContentCompleted (or similar event) would be called both for immediate and delayed streams.
You may use the following workaround for now.

1) Handle GetContentCompletedEvent.

  localFileSystemProvider.GetFileSystemNotifier().GetContentCompleted += localFileSystemProvider_GetContentCompleted;

2) In GetContentCompleted event wrap returned stream to the special EventStream (see below) and handle EventStream Closed event.

 private static void localFileSystemProvider_GetContentCompleted(object sender, GetContentEventArgs e)
{
  if (e.ContentParameters != NodeContentParameters.WriteAccess)
  {
    return;
  }

  var eventStream = new EventStream(e.ResultContent.GetStream(), e.Node);

  eventStream.Closed += EventStreamClosed;

  e.ResultContent = NodeContent.CreateImmediateWriteContent(eventStream);

}

private static void EventStreamClosed(object o, EventArgs args)
{
  var stream = (EventStream)o;
  stream.Closed -= EventStreamClosed;
  Console.WriteLine($"File { stream.Node.Path} has length {stream.Length}.");
}

3) EventStream is a special stream decorator that publishes Closed event and (re)reads current file node length.

using System;
using System.IO;
using Rebex.IO.FileSystem;

namespace SpecialEventStream
{
  public class EventStream : Stream
  {

    private readonly Stream _stream;
    public event EventHandler<EventArgs> Closed;
    public EventStream(Stream stream, NodeBase node)
    {
      Node = node ?? throw new ArgumentNullException(nameof(node));
      _stream = stream ?? throw new ArgumentNullException(nameof(stream));
    }

    public NodeBase Node
    {
      get;
    }


    public override void Flush()
    {
      _stream.Flush();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
      return _stream.Read(buffer, offset, count);
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
      return _stream.Seek(offset, origin);
    }

    public override void SetLength(long value)
    {
      _stream.SetLength(value);
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
      _stream.Write(buffer, offset, count);
    }

    public override bool CanRead => _stream.CanRead;

    public override bool CanSeek => _stream.CanSeek;

    public override bool CanWrite => _stream.CanWrite;

    public override long Length => Node.Length;

    public override long Position
    {
      get => _stream.Position;
      set => _stream.Position = value;
    }

    protected override void Dispose(bool disposing)
    {
      if (disposing)
      {
        _stream.Dispose();
        OnClosed();
      }

    }

    protected virtual void OnClosed()
    {
      Closed?.Invoke(this, EventArgs.Empty);
    }
  }

}

See also FileServer.FileUploaded event that may be better for your scenario.
https://api.rebex.net/?_ga=2.179352420.1735321122.1608551725-600862379.1463497785##RebexTotalPack.chm/Html/E_Rebex_Net_Servers_FileServer_FileUploaded.htm

Hope this helps.
Rene

commented Dec 21, 2020 by skay (130 points)
Thanks for all the info. I think using the `FileUploaded` event will work best.
...