Reactive Extensions is ideally suited to the task of monitoring a directory for changes. While the events from FileSystemWatcher are ok, it isn’t efficient. The goal is to send as little data as possible to the server. If a file is moved or copied, there’s no need to upload the file again. Instead, you should recognize the event on the client and simply send a Moved or Copied message pointing to the original file on the server.
Here’s a simple prototype. I left out all the code to track the files with hashes. It gets a little trickier trying to track directories. If a directory is deleted, the entire subtree is deleted. You can use SQLite with the closure.c extension to track hierarchical data.
Anyway, this proof-of-concept is easy. Cut and paste lost some formatting. Stupid tabs.
public class DropboxClient { private readonly FileSystemWatcher watcher; public IObservable<DropboxEventArg> FileSystemObservable { get; private set; } public DropboxClient(string home) { watcher = new FileSystemWatcher { Path = home, EnableRaisingEvents = true, IncludeSubdirectories = true }; SetupRx(); } private void SetupRx() { var changed = Observable.FromEventPattern<FileSystemEventArgs>(watcher, "Changed"); var created = Observable.FromEventPattern<FileSystemEventArgs>(watcher, "Created"); var deleted = Observable.FromEventPattern<FileSystemEventArgs>(watcher, "Deleted"); var renamed = Observable.FromEventPattern<FileSystemEventArgs>(watcher, "Renamed"); // Often it repeats a change event for every little change (timestamp, file size, etc). var dbchanged = changed .DistinctUntilChanged() //.Do(UpdateRecord) .Select(fe => new DropboxEventArg(fe.EventArgs)); // This seems to work fine, I think var dbrenamed = renamed.Select(fe => new DropboxEventArg(fe.EventArgs)); // Deleted is ok, too var dbdeleted = deleted //.Do(DeleteRecord) .Select(fe => new DropboxEventArg(fe.EventArgs)); // If file already exists, then a created file is a copy of another file var dbcreated = created .Select(fe => { if (FileExists(fe.EventArgs.FullPath)) return new DropboxEventArg(fe.EventArgs, DropboxChangeTypes.Copied); else { //CreateRecord(fe); return new DropboxEventArg(fe.EventArgs); } }); FileSystemObservable = dbchanged.Merge(dbrenamed).Merge(dbdeleted).Merge(dbcreated); } private void CreateRecord(EventPattern<FileSystemEventArgs> fe) { // Create row in repo throw new NotImplementedException(); } private void UpdateRecord(EventPattern<FileSystemEventArgs> obj) { // If file size is different, rehash and update // If dir, maybe do nothing. Not sure. throw new NotImplementedException(); } private void DeleteRecord(EventPattern<FileSystemEventArgs> obj) { // Delete file from repository // If directory, delete entire subtree from repo throw new NotImplementedException(); } private bool FileExists(string fpath) { // If file, hash and lookup // If dir, maybe do nothing return false; } } public enum DropboxChangeTypes { Changed, Created, Deleted, Renamed, Moved, Copied } public class DropboxEventArg { public DropboxChangeTypes ChangeType; public string FullPath; public string Name; public DropboxEventArg() { } public DropboxEventArg(FileSystemEventArgs fe) { FullPath = fe.FullPath; Name = fe.Name; switch (fe.ChangeType) { case WatcherChangeTypes.Changed: ChangeType = DropboxChangeTypes.Changed; break; case WatcherChangeTypes.Created: ChangeType = DropboxChangeTypes.Created; break; case WatcherChangeTypes.Deleted: ChangeType = DropboxChangeTypes.Deleted; break; case WatcherChangeTypes.Renamed: ChangeType = DropboxChangeTypes.Renamed; break; } } public DropboxEventArg(FileSystemEventArgs fe, DropboxChangeTypes ct) { FullPath = fe.FullPath; Name = fe.Name; ChangeType = ct; } }
Image may be NSFW.
Clik here to view.
Clik here to view.
