Exemple #1
0
        public FsChange ReadFsChange()
        {
            var changeType = (FsChangeType)ReadByte();

            if (changeType == FsChangeType.EmptyMarker)
            {
                return(FsChange.Empty);
            }

            var fsChange = new FsChange(changeType, ReadString());

            switch (fsChange.ChangeType)
            {
            case FsChangeType.Change:
                fsChange.Length        = ReadLong();
                fsChange.IsDirectory   = fsChange.Length == -1;
                fsChange.LastWriteTime = ReadDateTime();
                break;

            case FsChangeType.Rename:
                fsChange.OldPath = ReadString();
                break;
            }
            return(fsChange);
        }
Exemple #2
0
        // read and apply changes
        public IEnumerable <FsChangeResult> ReadAndApplyChanges(FileMaskList excludeList)
        {
            while (true)
            {
                var fsChange = Reader.ReadFsChange();
                if (fsChange.IsEmpty)
                {
                    break;
                }

                var fsChangeResult = ApplyFsChange(fsChange, excludeList);
                if (fsChangeResult.ResultCode != FsChangeResultCode.Ok && fsChange.ChangeType == FsChangeType.Change)
                {
                    var path = Path.Combine(BasePath, fsChange.Path);
                    if (fsChange.IsDirectory && File.Exists(path) ||
                        !fsChange.IsDirectory && Directory.Exists(path))
                    {
                        // delete and retry
                        var fsRemoveChangeResult = ApplyFsChange(
                            FsChange.CreateRemove(fsChange.Path),
                            excludeList);
                        if (fsRemoveChangeResult.ResultCode == FsChangeResultCode.Ok)
                        {
                            fsChangeResult = ApplyFsChange(fsChange, excludeList);
                        }
                    }
                }

                // TODO: skip ok codes (sender do not use them at the moment)
                if (fsChangeResult.ResultCode != FsChangeResultCode.Ok)
                {
                    yield return(fsChangeResult);
                }
            }
        }
Exemple #3
0
 private void AddDirectoryContents(string path)
 {
     try
     {
         var scanDirectory = new ScanDirectory(_sender._logger, _sender._excludeList,
                                               false, _cancellationTokenSource.Token);
         foreach (var srcEntry in scanDirectory.ScanPath(_sender._srcPath, path))
         {
             _sender.AddChange(FsChange.CreateChange(srcEntry.Path));
         }
     }
     catch (OperationCanceledException)
     {
     }
 }
Exemple #4
0
 public void WriteFsChange(FsChange fsChange)
 {
     WriteByte((byte)fsChange.ChangeType);
     if (fsChange.IsEmpty)
     {
         return;
     }
     WriteString(fsChange.Path);
     if (fsChange.IsChange)
     {
         WriteLong(fsChange.IsDirectory ? -1 : fsChange.Length);
         WriteDateTime(fsChange.LastWriteTime);
     }
     if (fsChange.IsRename)
     {
         WriteString(fsChange.OldPath);
     }
 }
Exemple #5
0
        private void OnWatcherRenamed(object source, RenamedEventArgs e)
        {
            // ignore event for srcPath (don't know why it occurs rarely)
            if (e.FullPath == _srcPath || e.OldFullPath == _srcPath)
            {
                return;
            }

            var path    = GetPath(e.FullPath);
            var oldPath = GetPath(e.OldFullPath);

            if (_gitIsBusy && oldPath == GIT_INDEX_LOCK_FILENAME)
            {
                SetGitIsBusy(false);
            }

            // is new file excluded?
            if (_excludeList.IsMatch(path))
            {
                // old file is not excluded -> delete it
                if (!_excludeList.IsMatch(oldPath))
                {
                    AddChange(FsChange.CreateRemove(oldPath));
                }

                // both files are excluded -> do nothing
            }
            else // new file is not excluded
            {
                // old file is excluded -> send change with withSubdirectories
                if (_excludeList.IsMatch(oldPath))
                {
                    AddChange(FsChange.CreateChange(path), withSubdirectories: true);
                }
                else
                {
                    // both files are not excluded -> send rename
                    AddChange(FsChange.CreateRename(path, oldPath));
                }
            }
        }
Exemple #6
0
        private void OnWatcherChanged(object source, FileSystemEventArgs e)
        {
            // ignore event for srcPath (don't know why it occurs rarely)
            if (e.FullPath == _srcPath)
            {
                return;
            }

            var path = GetPath(e.FullPath);

            if (!_gitIsBusy && e.ChangeType == WatcherChangeTypes.Created && path == GIT_INDEX_LOCK_FILENAME)
            {
                SetGitIsBusy(true);
            }

            if (_excludeList.IsMatch(path))
            {
                return;
            }
            AddChange(FsChange.CreateChange(path), withSubdirectories: e.ChangeType == WatcherChangeTypes.Created);
        }
Exemple #7
0
        private void AddChange(FsChange fsChange, bool notifyHasWork = true, bool withSubdirectories = false)
        {
            lock (_changes)
            {
                if (_changes.TryGetValue(fsChange.Path, out var oldFsChange))
                {
                    oldFsChange.Expired = true;
                }
                _changes[fsChange.Path] = fsChange;
            }

            if (withSubdirectories)
            {
                _pathScanner.Add(fsChange.Path);
            }

            if (notifyHasWork)
            {
                UpdateHasWork();
            }
        }
Exemple #8
0
        private void OnWatcherDeleted(object source, FileSystemEventArgs e)
        {
            // ignore event for srcPath (don't know why it occurs rarely)
            if (e.FullPath == _srcPath)
            {
                return;
            }

            var path = GetPath(e.FullPath);

            if (_gitIsBusy && path == GIT_INDEX_LOCK_FILENAME)
            {
                SetGitIsBusy(false);
            }

            if (_excludeList.IsMatch(path))
            {
                return;
            }

            AddChange(FsChange.CreateRemove(path));
        }
Exemple #9
0
        public bool ReadFsChangeBody(string path, FsChange fsChange)
        {
            var shebangPosition = 0;
            var makeExecutable  = false;

            string     tempPath        = null;
            FileStream fs              = null;
            bool       bodyReadSuccess = false;

            try
            {
                long written = 0;
                int  chunkSize;
                do
                {
                    chunkSize = ReadInt();
                    if (chunkSize < 0)
                    {
                        // error occurred in sender
                        return(false);
                    }

                    var remain = chunkSize;
                    while (remain > 0)
                    {
                        var read = BinaryReader.Read(_buffer, 0, Math.Min(BUFFER_LENGTH, remain));
                        if (read <= 0)
                        {
                            throw new EndOfStreamException($"Premature end of stream {remain}, {chunkSize}, {read})");
                        }

                        if (written == 0)
                        {
                            var directoryName = Path.GetDirectoryName(path);
                            Directory.CreateDirectory(directoryName);
                            tempPath = Path.Combine(directoryName,
                                                    "." + Path.GetFileName(path) + "." + Path.GetRandomFileName());
                            fs = new FileStream(tempPath, FileMode.CreateNew, FileAccess.Write, FileShare.Read);
                        }

                        if (PlatformHasChmod && shebangPosition < ShebangLength)
                        {
                            for (var i = 0; i < read;)
                            {
                                if (_buffer[i++] != ShebangBytes[shebangPosition++])
                                {
                                    // no shebang
                                    shebangPosition = int.MaxValue;
                                    break;
                                }
                                if (shebangPosition == ShebangLength)
                                {
                                    makeExecutable = true;
                                    break;
                                }
                            }
                        }

                        fs?.Write(_buffer, 0, read);
                        written += read;
                        remain  -= read;
                    }
                } while (chunkSize > 0);

                bodyReadSuccess = written == fsChange.Length;
                if (bodyReadSuccess && makeExecutable)
                {
                    // 0755, rwxr-xr-x
                    fs.ChangeMode(0b111_101_101);
                }

                // create empty file
                if (bodyReadSuccess && fsChange.Length == 0)
                {
                    var directoryName = Path.GetDirectoryName(path);
                    Directory.CreateDirectory(directoryName);
                    using (new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
                    {
                    }
                    File.SetLastWriteTime(path, fsChange.LastWriteTime);
                }

                return(bodyReadSuccess);
            }
            catch (Exception)
            {
                SkipFsChangeBody();
                throw;
            }
            finally
            {
                if (fs != null)
                {
                    fs.Dispose();

                    if (bodyReadSuccess)
                    {
                        try
                        {
                            File.SetLastWriteTime(tempPath, fsChange.LastWriteTime);
                            File.Move(tempPath, path, true);
                        }
                        catch (Exception)
                        {
                            FsHelper.TryDeleteFile(tempPath);
                            throw;
                        }
                    }
                    else
                    {
                        FsHelper.TryDeleteFile(tempPath);
                    }
                }
            }
        }
Exemple #10
0
        public bool WriteFsChangeBody(string filename, FsChange fsChange)
        {
            long written = 0;

            FileStream fs = null;

            try
            {
                try
                {
                    fs = new FileStream(filename, FileMode.Open, FileAccess.Read,
                                        FileShare.Delete | FileShare.ReadWrite);
                }
                catch (Exception ex)
                {
                    if (ex is FileNotFoundException || ex is DirectoryNotFoundException)
                    {
                        // file vanished -> ignore it
                        fsChange.Expired = true;
                    }
                    else
                    {
                        // something else
                        _logger.Log(ex.Message, LogLevel.Error);
                    }

                    // cannot read file (sender error)
                    WriteFsChange(fsChange);
                    WriteInt(-1);
                    return(false);
                }

                // length resolved
                fsChange.Length = fs.Length;
                WriteFsChange(fsChange);

                int read;
                do
                {
                    if (fsChange.Expired)
                    {
                        // file change is expired -> stop
                        WriteInt(-1);
                        return(false);
                    }

                    try
                    {
                        read = fs.Read(_buffer, 0, BUFFER_LENGTH);
                        if (read <= 0)
                        {
                            break;
                        }
                    }
                    catch (Exception ex)
                    {
                        // file read error (sender error)
                        _logger.Log(ex.Message, LogLevel.Error);
                        WriteInt(-1);
                        return(false);
                    }

                    WriteInt(read);
                    BinaryWriter.Write(_buffer, 0, read);
                    written += read;
                } while (read == BUFFER_LENGTH);

                // check written
                if (written != fsChange.Length)
                {
                    // file length mismatch
                    WriteInt(-1);
                    return(false);
                }

                WriteInt(0);
                return(true);
            }
            finally
            {
                fs?.Dispose();
            }
        }
Exemple #11
0
        private FsChangeResult ApplyFsChange(FsChange fsChange, FileMaskList excludeList)
        {
            var path = Path.Combine(BasePath, fsChange.Path);
            FsChangeResultCode resultCode = FsChangeResultCode.None;
            string             error      = null;

            // Change file | Remove |  Rename + Change directory
            if (fsChange.IsChange && !fsChange.IsDirectory)
            {
                try
                {
                    if (fsChange.HasBody && Reader.ReadFsChangeBody(path, fsChange))
                    {
                        resultCode = FsChangeResultCode.Ok;
                    }
                    else
                    {
                        // error occurred in sender
                        error      = "Sender error";
                        resultCode = FsChangeResultCode.SenderError;
                    }
                }
                catch (EndOfStreamException)
                {
                    // end of data
                    throw;
                }
                catch (Exception ex)
                {
                    resultCode = FsChangeResultCode.Error;
                    error      = ex.Message;
                }
            }
            else if (fsChange.IsRemove)
            {
                // directory
                if (Directory.Exists(path))
                {
                    var       scanDirectory = new ScanDirectory(Logger, excludeList, false);
                    Exception exception     = null;
                    foreach (var fsEntry in scanDirectory.ScanPath(BasePath, fsChange.Path))
                    {
                        var fsEntryPath = Path.Combine(BasePath, fsEntry.Path);
                        try
                        {
                            if (fsEntry.IsDirectory)
                            {
                                Directory.Delete(fsEntryPath, false);
                            }
                            else
                            {
                                File.Delete(fsEntryPath);
                            }
                        }
                        catch (Exception ex)
                        {
                            exception ??= ex;
                            Logger.Log($"Error deleting {fsEntryPath}: {ex.Message}", LogLevel.Warning);
                        }
                    }

                    try
                    {
                        Directory.Delete(path, false);
                    }
                    catch (Exception ex)
                    {
                        exception ??= ex;
                        Logger.Log($"Error deleting {path}: {ex.Message}", LogLevel.Warning);
                    }

                    if (exception == null)
                    {
                        resultCode = FsChangeResultCode.Ok;
                    }
                    else
                    {
                        // scan directory see any file -> error (handle excludes)
                        if (scanDirectory.ScanPath(BasePath, fsChange.Path).Any(x => !x.IsDirectory))
                        {
                            resultCode = FsChangeResultCode.Error;
                            error      = exception.Message;
                        }
                        else
                        {
                            resultCode = FsChangeResultCode.Ok;
                        }
                    }
                }
                else if (File.Exists(path))
                {
                    try
                    {
                        File.Delete(path);
                        resultCode = FsChangeResultCode.Ok;
                    }
                    catch (Exception ex)
                    {
                        resultCode = FsChangeResultCode.Error;
                        error      = ex.Message;
                    }
                }
                else
                {
                    resultCode = FsChangeResultCode.Ok;
                }
            }
            else
            {
                if (fsChange.IsChange && fsChange.IsDirectory)
                {
                    try
                    {
                        var directoryPath = fsChange.IsRename ? Path.Combine(BasePath, fsChange.OldPath) : path;
                        Directory.CreateDirectory(directoryPath);
                        Directory.SetLastWriteTime(directoryPath, fsChange.LastWriteTime);
                        resultCode = FsChangeResultCode.Ok;
                    }
                    catch (Exception ex)
                    {
                        error      = ex.Message;
                        resultCode = FsChangeResultCode.Error;
                    }
                }

                if (resultCode != FsChangeResultCode.Error && fsChange.IsRename)
                {
                    try
                    {
                        var oldPath = Path.Combine(BasePath, fsChange.OldPath);
                        if (Directory.Exists(oldPath))
                        {
                            Directory.Move(oldPath, path);
                        }
                        else
                        {
                            File.Move(oldPath, path, true);
                        }

                        resultCode = FsChangeResultCode.Ok;
                    }
                    catch (Exception ex)
                    {
                        error      = ex.Message;
                        resultCode = FsChangeResultCode.Error;
                    }
                }
            }

            if (resultCode == FsChangeResultCode.None)
            {
                resultCode = FsChangeResultCode.Error;
                error      = "Unknown change type";
            }

            return(new FsChangeResult
            {
                ChangeType = fsChange.ChangeType,
                Path = fsChange.Path,
                ResultCode = resultCode,
                ErrorMessage = error
            });
        }
Exemple #12
0
        private bool SendChanges()
        {
            // fetch changes
            lock (_changes)
            {
                if (_changes.Count == 0)
                {
                    return(true);
                }
                _applyRequest.SetChanges(_changes.Values);
            }

            if (!_isSending)
            {
                _logger.Log("Sending");
                _isSending = true;
            }

            var sw             = Stopwatch.StartNew();
            var response       = _agentStarter.SendCommand <ApplyResponse>(_applyRequest);
            var responseResult = response.Result.ToDictionary(x => x.Key, y => y);

            bool hasErrors = false;

            // process sent changes
            lock (_changes)
            {
                foreach (var fsChange in _applyRequest.SentChanges)
                {
                    if (!fsChange.Expired)
                    {
                        _changes.Remove(fsChange.Path);
                        if (responseResult.TryGetValue(fsChange.Path, out var fsChangeResult))
                        {
                            if (fsChangeResult.ResultCode != FsChangeResultCode.Ok)
                            {
                                var withSubdirectories = false;
                                // ignore sender errors: just resend
                                if (fsChangeResult.ResultCode != FsChangeResultCode.SenderError)
                                {
                                    // if rename failed -> send change with withSubdirectories
                                    if (fsChange.ChangeType == FsChangeType.Rename)
                                    {
                                        withSubdirectories = true;
                                    }
                                    else
                                    {
                                        hasErrors = true;
                                        _logger.Log(
                                            $"Change apply error {fsChange.ChangeType} {fsChange.Path}: {fsChangeResult.ErrorMessage ?? "-"}", LogLevel.Error);
                                    }
                                }
                                AddChange(FsChange.CreateChange(fsChange.Path), false, withSubdirectories);
                            }
                        }
                    }
                    else
                    {
                        // remove expired
                        if (_changes.TryGetValue(fsChange.Path, out var oldFsChange) && oldFsChange.Expired)
                        {
                            _changes.Remove(fsChange.Path);
                        }
                    }
                }
            }

            _sentReporter.Report(_applyRequest.SentChanges, _applyRequest.SentChangesSize, sw.Elapsed);
            _applyRequest.ClearChanges();
            UpdateHasWork();
            return(!hasErrors);
        }
Exemple #13
0
        private void Scan()
        {
            var            sw      = Stopwatch.StartNew();
            List <FsEntry> srcList = null;
            Dictionary <string, FsEntry> destList;

            /*
             * Start agent before scan source
             *
             * Old timeline: [Main thread]       Start ... Initialize ... Scan destination ... Finish
             *               [Secondary thread]  Scan source ................................. Finish
             *
             * New timeline: [Main thread]       Start ... Initialize ... Scan destination ... Finish
             *               [Secondary thread]            Scan source ....................... Finish
             *
             * A failed start could cause unnecessary scanning source in old timeline.
             * No need to scan source before start in most cases because it is about as fast as the scan destination.
             */
            _agentStarter.Start();

            using (var tokenSource = new CancellationTokenSource())
            {
                var cancellationToken = tokenSource.Token;

                // scan source
                var task = Task.Run(() =>
                {
                    try
                    {
                        var swScanSource  = Stopwatch.StartNew();
                        var scanDirectory =
                            new ScanDirectory(_logger, _excludeList, cancellationToken: cancellationToken);
                        srcList = scanDirectory.ScanPath(_srcPath).ToList();
                        cancellationToken.ThrowIfCancellationRequested();
                        _logger.Log($"Scanned source {srcList.Count} items in {swScanSource.ElapsedMilliseconds} ms");
                    }
                    catch (OperationCanceledException)
                    {
                        srcList = null;
                    }
                }, cancellationToken);

                try
                {
                    var swScanDestination = Stopwatch.StartNew();
                    // scan destination
                    var response = _agentStarter.SendCommand <ScanResponse>(new ScanRequest(_logger));
                    destList = response.FileList.ToDictionary(x => x.Path, y => y);
                    _logger.Log($"Scanned destination {destList.Count} items in {swScanDestination.ElapsedMilliseconds} ms");
                    task.Wait(cancellationToken);
                }
                catch (Exception)
                {
                    tokenSource.Cancel();
                    throw;
                }
            }

            // During scan, changes could come from file system events or from PathScanner, we should not overwrite them.
            var  itemsCount  = 0;
            long changesSize = 0;

            lock (_changes)
            {
                foreach (var srcEntry in srcList)
                {
                    if (!destList.TryGetValue(srcEntry.Path, out var destEntry))
                    {
                        destEntry = FsEntry.Empty;
                    }

                    // Skip changed srcEntry
                    if (!_changes.ContainsKey(srcEntry.Path))
                    {
                        // add to changes (no replace)
                        if (!srcEntry.Compare(destEntry))
                        {
                            itemsCount++;
                            if (!srcEntry.IsDirectory)
                            {
                                changesSize += srcEntry.Length;
                            }
                            AddChange(FsChange.CreateChange(srcEntry), false);
                        }
                    }

                    if (!destEntry.IsEmpty)
                    {
                        destList.Remove(destEntry.Path);
                    }
                }

                // add deletes
                foreach (var destEntry in destList.Values)
                {
                    // Skip changed destEntry
                    if (!_changes.ContainsKey(destEntry.Path))
                    {
                        itemsCount++;
                        AddChange(FsChange.CreateRemove(destEntry.Path), false);
                    }
                }
            }

            _needToScan = false;
            _logger.Log(
                $"Scanned in {sw.ElapsedMilliseconds} ms, {itemsCount} items, {PrettySize(changesSize)} to send");
            UpdateHasWork();
        }