Skip to content

Commit

Permalink
Don't ignore failed folders: treat them appropriately
Browse files Browse the repository at this point in the history
Folders can unfail at any point. Don't fetch ignores for failed folders,
but do fetch ignores when they unfail. Don't watch failed folders.

Fixes #187
  • Loading branch information
canton7 committed Jan 12, 2016
1 parent 7f6f0c1 commit 26dde80
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 55 deletions.
14 changes: 11 additions & 3 deletions src/SyncTrayzor/Services/WatchedFolderMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public WatchedFolderMonitor(ISyncThingManager syncThingManager, IDirectoryWatche
this.directoryWatcherFactory = directoryWatcherFactory;

this.syncThingManager.Folders.FoldersChanged += this.FoldersChanged;
this.syncThingManager.Folders.SyncStateChanged += this.FolderSyncStateChanged;
this.syncThingManager.StateChanged += this.StateChanged;
}

Expand All @@ -55,6 +56,12 @@ private void FoldersChanged(object sender, EventArgs e)
this.Reset();
}

private void FolderSyncStateChanged(object sender, FolderSyncStateChangedEventArgs e)
{
// Don't monitor failed folders
this.Reset();
}

private void StateChanged(object sender, SyncThingStateChangedEventArgs e)
{
this.Reset();
Expand All @@ -81,7 +88,7 @@ private void Reset()

foreach (var folder in folders)
{
if (!this._watchedFolders.Contains(folder.FolderId))
if (!this._watchedFolders.Contains(folder.FolderId) || folder.SyncState == FolderSyncState.Error)
continue;

var watcher = this.directoryWatcherFactory.Create(folder.Path, this.BackoffInterval, this.FolderExistenceCheckingInterval);
Expand Down Expand Up @@ -136,8 +143,9 @@ private void WatcherDirectoryChanged(Folder folder, string subPath)

public void Dispose()
{
this.syncThingManager.Folders.FoldersChanged += this.FoldersChanged;
this.syncThingManager.StateChanged += this.StateChanged;
this.syncThingManager.Folders.FoldersChanged -= this.FoldersChanged;
this.syncThingManager.Folders.SyncStateChanged -= this.FolderSyncStateChanged;
this.syncThingManager.StateChanged -= this.StateChanged;
}
}
}
2 changes: 1 addition & 1 deletion src/SyncTrayzor/SyncThing/ApiClient/ISyncThingApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public interface ISyncThingApi
Task<SyncthingVersion> FetchVersionAsync();

[Get("/rest/db/ignores")]
Task<Ignores> FetchIgnoresAsync(string folder);
Task<Ignores> FetchIgnoresAsync(string folder, CancellationToken cancellationToken);

[Post("/rest/system/restart")]
Task RestartAsync();
Expand Down
2 changes: 1 addition & 1 deletion src/SyncTrayzor/SyncThing/ApiClient/ISyncThingApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public interface ISyncThingApiClient
Task<SystemInfo> FetchSystemInfoAsync();
Task<Connections> FetchConnectionsAsync();
Task<SyncthingVersion> FetchVersionAsync();
Task<Ignores> FetchIgnoresAsync(string folderId);
Task<Ignores> FetchIgnoresAsync(string folderId, CancellationToken cancellationToken);
Task RestartAsync();
Task<FolderStatus> FetchFolderStatusAsync(string folderId, CancellationToken cancellationToken);
Task<DebugFacilitiesSettings> FetchDebugFacilitiesAsync();
Expand Down
4 changes: 2 additions & 2 deletions src/SyncTrayzor/SyncThing/ApiClient/SyncThingApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ public async Task<SyncthingVersion> FetchVersionAsync()
return version;
}

public async Task<Ignores> FetchIgnoresAsync(string folderId)
public async Task<Ignores> FetchIgnoresAsync(string folderId, CancellationToken cancellationToken)
{
var ignores = await this.api.FetchIgnoresAsync(folderId);
var ignores = await this.api.FetchIgnoresAsync(folderId, cancellationToken);
logger.Debug("Fetched ignores for folderid {0}: {1}", folderId, ignores);
return ignores;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ public void Accept(LocalIndexUpdatedEvent evt)

public void Accept(StateChangedEvent evt)
{
var oldState = evt.Data.From == "syncing" ? FolderSyncState.Syncing : FolderSyncState.Idle;
var state = evt.Data.To == "syncing" ? FolderSyncState.Syncing : FolderSyncState.Idle;
var oldState = FolderStateTransformer.SyncStateFromStatus(evt.Data.From);
var state = FolderStateTransformer.SyncStateFromStatus(evt.Data.To);
this.OnSyncStateChanged(evt.Data.Folder, oldState, state);
}

Expand Down
13 changes: 13 additions & 0 deletions src/SyncTrayzor/SyncThing/Folder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public enum FolderSyncState
{
Syncing,
Idle,
Error,
}

public class FolderIgnores
Expand All @@ -16,6 +17,13 @@ public class FolderIgnores
public IReadOnlyList<Regex> IncludeRegex { get; }
public IReadOnlyList<Regex> ExcludeRegex { get; }

public FolderIgnores()
{
this.IgnorePatterns = EmptyList<string>.Instance;
this.IncludeRegex = EmptyList<Regex>.Instance;
this.ExcludeRegex = EmptyList<Regex>.Instance;
}

public FolderIgnores(List<string> ignores, List<string> patterns)
{
this.IgnorePatterns = ignores;
Expand All @@ -33,6 +41,11 @@ public FolderIgnores(List<string> ignores, List<string> patterns)
this.IncludeRegex = includeRegex.AsReadOnly();
this.ExcludeRegex = excludeRegex.AsReadOnly();
}

private static class EmptyList<T>
{
public static IReadOnlyList<T> Instance = new List<T>().AsReadOnly();
}
}

public class FolderError
Expand Down
30 changes: 30 additions & 0 deletions src/SyncTrayzor/SyncThing/FolderStateTransformer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Collections.Generic;
using NLog;
using SyncTrayzor.SyncThing.ApiClient;

namespace SyncTrayzor.SyncThing
{
public static class FolderStateTransformer
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();

private static readonly Dictionary<string, FolderSyncState> folderSyncStateLookup = new Dictionary<string, FolderSyncState>()
{
{ "syncing", FolderSyncState.Syncing },
{ "idle", FolderSyncState.Idle },
{ "error", FolderSyncState.Error },
};

public static FolderSyncState SyncStateFromStatus(string state)
{
FolderSyncState syncState;
if (folderSyncStateLookup.TryGetValue(state, out syncState))
return syncState;

logger.Warn($"Unknown folder sync state {state}. Defaulting to Idle");

// Default
return FolderSyncState.Idle;
}
}
}
76 changes: 30 additions & 46 deletions src/SyncTrayzor/SyncThing/SyncThingFolderManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public async Task ReloadIgnoresAsync(string folderId)
if (!this.folders.TryGetValue(folderId, out folder))
return;

var ignores = await this.apiClient.Value.FetchIgnoresAsync(folderId);
var ignores = await this.apiClient.Value.FetchIgnoresAsync(folderId, CancellationToken.None);
folder.Ignores = new FolderIgnores(ignores.IgnorePatterns, ignores.RegexPatterns);
}

Expand Down Expand Up @@ -163,71 +163,46 @@ private async Task<IEnumerable<Folder>> FetchFoldersAsync(Config config, string
.DistinctBy(x => x.ID)
.Select(async folder =>
{
var ignores = await this.FetchFolderIgnoresAsync(folder.ID, cancellationToken);
var status = await this.FetchFolderStatusAsync(folder.ID, cancellationToken);
var syncState = FolderStateTransformer.SyncStateFromStatus(status.State);

Ignores ignores = null;
if (syncState != FolderSyncState.Error)
ignores = await this.FetchFolderIgnoresAsync(folder.ID, cancellationToken);

var path = folder.Path;
// Strip off UNC prefix, if they're put it on
if (path.StartsWith(uncPrefix))
path = path.Substring(uncPrefix.Length);
if (path.StartsWith("~"))
path = Path.Combine(tilde, path.Substring(1).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));

return new Folder(folder.ID, path, SyncStateFromStatus(status), new FolderIgnores(ignores.IgnorePatterns, ignores.RegexPatterns), status);
var folderIgnores = (ignores == null) ? new FolderIgnores() : new FolderIgnores(ignores.IgnorePatterns, ignores.RegexPatterns);

return new Folder(folder.ID, path, syncState, folderIgnores, status);
});

cancellationToken.ThrowIfCancellationRequested();

var folders = await Task.WhenAll(folderConstructionTasks);
return folders;
}

private static FolderSyncState SyncStateFromStatus(FolderStatus status)
{
return status.State == "syncing" ? FolderSyncState.Syncing : FolderSyncState.Idle;
var actualFolders = folders.Where(x => x != null);
return actualFolders;
}

private async Task<Ignores> FetchFolderIgnoresAsync(string folderId, CancellationToken cancellationToken)
{
// Until startup is complete, these can return a 500.
// There's no sensible way to determine when startup *is* complete, so we just have to keep trying...

// Again, there's the possibility that we've just abort the API...
ISyncThingApiClient apiClient;
lock (this.apiClient.LockObject)
// This will 500 if there's an error. Return null if that's the case
try
{
cancellationToken.ThrowIfCancellationRequested();
apiClient = this.apiClient.UnsynchronizedValue;
if (apiClient == null)
throw new InvalidOperationException("ApiClient must not be null");
var ignores = await this.apiClient.Value.FetchIgnoresAsync(folderId, cancellationToken);
return ignores;
}

Ignores ignores = null;
// We used to time out after an absolute time here. However, there's the possiblity of going to sleep
// halfway through polling, which throws things off. Therefore use a number of iterations
var numRetries = this.ignoresFetchTimeout.TotalSeconds; // Each iteration is a second
for (var retriesCount = 0; retriesCount < numRetries; retriesCount++)
catch (ApiException e)
{
try
{
ignores = await apiClient.FetchIgnoresAsync(folderId);
// No need to log: ApiClient did that for us
break;
}
catch (ApiException e)
{
logger.Debug("Attempting to fetch folder {0}, but received status {1}", folderId, e.StatusCode);
if (e.StatusCode != HttpStatusCode.InternalServerError)
throw;
}

await Task.Delay(1000, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
if (e.StatusCode == HttpStatusCode.InternalServerError)
return null;
throw;
}

if (ignores == null)
throw new SyncThingDidNotStartCorrectlyException($"Unable to fetch ignores for folder {folderId}. Syncthing returned 500 after {this.ignoresFetchTimeout}");

return ignores;
}

private async Task<FolderStatus> FetchFolderStatusAsync(string folderId, CancellationToken cancellationToken)
Expand Down Expand Up @@ -265,7 +240,7 @@ private void FolderErrorsChangedEvt(string folderId, List<FolderErrorData> error
this.OnFolderErrorsChanged(folder, folderErrors);
}

private void FolderSyncStateChanged(SyncStateChangedEventArgs e)
private async void FolderSyncStateChanged(SyncStateChangedEventArgs e)
{
Folder folder;
if (!this.folders.TryGetValue(e.FolderId, out folder))
Expand All @@ -278,6 +253,15 @@ private void FolderSyncStateChanged(SyncStateChangedEventArgs e)
folder.ClearFolderErrors();
this.OnFolderErrorsChanged(folder, new List<FolderError>());
}
else if (e.SyncState == FolderSyncState.Error)
{
folder.Ignores = new FolderIgnores();
}
else if (e.PrevSyncState == FolderSyncState.Error)
{
var ignores = await this.FetchFolderIgnoresAsync(e.FolderId, CancellationToken.None);
folder.Ignores = new FolderIgnores(ignores.IgnorePatterns, ignores.RegexPatterns);
}

this.OnSyncStateChanged(folder, e.PrevSyncState, e.SyncState);
}
Expand Down
1 change: 1 addition & 0 deletions src/SyncTrayzor/SyncTrayzor.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@
<Compile Include="SyncThing\EventWatcher\ItemFinishedEventArgs.cs" />
<Compile Include="SyncThing\EventWatcher\ItemStartedEventArgs.cs" />
<Compile Include="SyncThing\FolderErrorsChangedEventArgs.cs" />
<Compile Include="SyncThing\FolderStateTransformer.cs" />
<Compile Include="SyncThing\FreePortFinder.cs" />
<Compile Include="SyncThing\SyncStateChangedEventArgs.cs" />
<Compile Include="SyncThing\SyncThingHttpClientHandler.cs" />
Expand Down

0 comments on commit 26dde80

Please sign in to comment.