Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store color palettes apart from other collection settings (BL-14129) #6779

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 56 additions & 14 deletions src/BloomExe/Collection/CollectionSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -324,13 +324,23 @@ public void Save()
xml.Add(new XElement("DefaultBookTags", "bookshelf:" + DefaultBookshelf));
}
xml.Add(BulkPublishBloomPubSettings.ToXElement());
RobustIO.SaveXElement(xml, SettingsFilePath);

// Color palette settings are stored in a separate Json file
SaveColorPalettesToJsonFile();
}

internal void SaveColorPalettesToJsonFile()
{
// If we're in a Team Collection, the file checker will try to sync up with any changes
// that have occurred remotely.
var jsonString = new StringBuilder();
jsonString.AppendLine("{");
foreach (var key in ColorPalettes.Keys)
{
var palette = new XElement("Palette", ColorPalettes[key]);
palette.Add(new XAttribute("id", key));
xml.Add(palette);
}
SIL.IO.RobustIO.SaveXElement(xml, SettingsFilePath);
jsonString.AppendLine($"\"{key}\":\"{ColorPalettes[key]}\",");
jsonString.AppendLine("}");
var jsonFilePath = Path.Combine(FolderPath, "colorPalettes.json");
RobustFile.WriteAllText(jsonFilePath, jsonString.ToString());
}

public string GetCollectionStylesCss(bool omitDirection)
Expand Down Expand Up @@ -739,19 +749,49 @@ internal static string ReadString(XElement document, string id, string defaultVa
}
}

internal static void LoadDictionary(
XElement document,
string tag,
Dictionary<string, string> dict
)
internal void LoadDictionary(XElement document, string tag, Dictionary<string, string> dict)
{
dict.Clear();
var elements = document.Descendants(tag);
if (elements != null)
if (elements != null && elements.Count() > 0)
{
foreach (XElement element in elements)
dict[element.Attribute("id").Value] = element.Value;
}
else
{
// The color palettes are now stored in a separate JSON file, so if they aren't found in the XML,
// we need to try to load them from the JSON file.
if (tag == "Palette")
{
var path = Path.Combine(FolderPath, "colorPalettes.json");
LoadColorPalettesFromJsonFile(dict, path);
}
}
}

internal static void LoadColorPalettesFromJsonFile(
Dictionary<string, string> dict,
string path
)
{
if (RobustFile.Exists(path))
{
var jsonString = RobustFile.ReadAllText(path);
try
{
var json = JObject.Parse(jsonString);
if (json != null)
{
foreach (var property in json.Properties())
dict[property.Name] = property.Value.ToString();
}
}
catch (Exception ex)
{
Logger.WriteEvent($"Error loading color palettes from {path}: " + ex.Message);
}
}
}

public virtual string CollectionName { get; protected set; }
Expand Down Expand Up @@ -1026,7 +1066,7 @@ public string CharactersForDigitsForPageNumbers
public bool HaveEnterpriseSubscription =>
HaveEnterpriseFeatures && BrandingProjectKey != "Local-Community";

private readonly Dictionary<string, string> ColorPalettes =
internal readonly Dictionary<string, string> ColorPalettes =
new Dictionary<string, string>();

private string _brandingProjectKey;
Expand All @@ -1046,6 +1086,8 @@ public string GetColorPaletteAsJson(string paletteTag)
var pieces = savedColor.Split('/');
if (pieces.Length > 1)
opacity = double.Parse(pieces[1], NumberFormatInfo.InvariantInfo);
if (opacity > 1)
opacity = opacity / 100; // some old values stored range of 0-100 instead of 0-1
var colors = pieces[0].Split('-');
// Opacity needs to be formatted in an invariant manner to keep the file format stable.
var colorElement =
Expand Down Expand Up @@ -1092,7 +1134,7 @@ public void AddColorToPalette(string paletteTag, string colorString)
return;
savedPalette = savedPalette + " " + colorToSave;
ColorPalettes[paletteTag] = savedPalette.Trim();
Save();
SaveColorPalettesToJsonFile();
}
catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException)
{
Expand Down
139 changes: 126 additions & 13 deletions src/BloomExe/TeamCollection/TeamCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -445,13 +445,12 @@ private void OnChanged(object sender, FileSystemEventArgs e)
// are in a consistent state, as we may get multiple write notifications during the process of
// writing a file. It may also help to ensure that repo writing doesn't interfere somehow with
// whatever is changing things.
// (Form.ActiveForm should not be null when Bloom is running normally. However, it can be when we're displaying
// a page in a browser, or when we're reloading Bloom after saving collection settings.)
if (Form.ActiveForm != null)
var form = Shell.GetShellOrOtherOpenForm(); // Form.ActiveForm is null when a browser is active
if (form != null)
{
SafeInvoke.InvokeIfPossible(
"Add SyncCollectionFilesToRepoOnIdle",
Form.ActiveForm,
form,
false,
(Action)(
() =>
Expand Down Expand Up @@ -793,9 +792,10 @@ public void SyncLocalAndRepoCollectionFiles(bool atStartup = true)
}
else
{
if (LocalCollectionFilesUpdated())
var updateType = LocalCollectionFilesUpdated();
if (updateType != CollectionSettingsChange.None)
{
if (repoModTime > savedSyncTime)
if (repoModTime > savedSyncTime && updateType == CollectionSettingsChange.Other)
{
// We have a conflict we should warn the user about...if we haven't already.
if (!_haveShownRemoteSettingsChangeWarning)
Expand Down Expand Up @@ -886,7 +886,9 @@ private string MakeChecksumOnFilesInternal(IEnumerable<string> files)
private void RecordCollectionFilesSyncData()
{
var files = FilesToMonitorForCollection();
var checksum = MakeChecksumOnFiles(files);
var checksum = MakeChecksumOnFiles(
files.FindAll(f => Path.GetFileName(f) != "colorPalettes.json")
);
RecordCollectionFilesSyncDataInternal(checksum);
}

Expand All @@ -907,6 +909,13 @@ private void RecordCollectionFilesSyncDataInternal(string checksum)
RobustFile.WriteAllText(path, nowString + @";" + checksum);
}

internal enum CollectionSettingsChange
{
None,
ColorPalette,
Other
}

/// <summary>
/// Return true if local collection-level files have changed and need to be copied
/// to the repo. Usually we can determine this by a quick check of modify times.
Expand All @@ -915,14 +924,33 @@ private void RecordCollectionFilesSyncDataInternal(string checksum)
/// we update the time record to make the next check faster.)
/// </summary>
/// <returns></returns>
internal bool LocalCollectionFilesUpdated()
internal CollectionSettingsChange LocalCollectionFilesUpdated()
{
var files = FilesToMonitorForCollection();
var localModTime = files.Select(f => new FileInfo(f).LastWriteTime).Max();

var localModTime = files
.FindAll(f => Path.GetFileName(f) != "colorPalettes.json")
.Select(f => new FileInfo(f).LastWriteTime)
.Max();
var savedModTime = LocalCollectionFilesRecordedSyncTime();
var colorPaletteChanged = false;
var colorPaletteFile = files.Find(f => Path.GetFileName(f) == "colorPalettes.json");
if (colorPaletteFile != null && RobustFile.Exists(colorPaletteFile))
{
var colorPaletteTime = new FileInfo(colorPaletteFile).LastWriteTime;
if (colorPaletteTime > savedModTime)
colorPaletteChanged = SyncColorPaletteFileFromRepo();
}
if (localModTime <= savedModTime)
return false;
var currentChecksum = MakeChecksumOnFiles(files);
{
return colorPaletteChanged
? CollectionSettingsChange.ColorPalette
: CollectionSettingsChange.None;
}

var currentChecksum = MakeChecksumOnFiles(
files.FindAll(f => Path.GetFileName(f) != "colorPalettes.json")
);
var localFilesReallyUpdated = currentChecksum != LocalCollectionFilesSavedChecksum();
if (!localFilesReallyUpdated && savedModTime >= LastRepoCollectionFileModifyTime)
{
Expand All @@ -935,7 +963,85 @@ internal bool LocalCollectionFilesUpdated()
// collection level anyway.
RecordCollectionFilesSyncDataInternal(currentChecksum);
}
return localFilesReallyUpdated;
if (localFilesReallyUpdated)
return CollectionSettingsChange.Other;
else if (colorPaletteChanged)
return CollectionSettingsChange.ColorPalette;
else
return CollectionSettingsChange.None;
}

object _syncRepo = new object();

private bool SyncColorPaletteFileFromRepo()
{
lock (_syncRepo)
{
// We need to copy the colorPalettes.json file from the repo so we can load it and
// merge into our local colorPalettes.json file.
var tempFolder = Path.Combine(Path.GetTempPath(), "BloomColorPalette");
if (!Directory.Exists(tempFolder))
Directory.CreateDirectory(tempFolder);
try
{
var repoColorPaletteFile = Path.Combine(tempFolder, "colorPalettes.json");
CopyRepoCollectionFilesToLocalImpl(tempFolder);
var repoColorPalettes = new Dictionary<string, string>();
if (RobustFile.Exists(repoColorPaletteFile))

CollectionSettings.LoadColorPalettesFromJsonFile(
repoColorPalettes,
repoColorPaletteFile
);
var dirty = false;
foreach (var key in repoColorPalettes.Keys)
{
var mergedValues = MergeColorPaletteValues(
repoColorPalettes[key],
_tcManager.Settings.ColorPalettes[key]
);
if (mergedValues != _tcManager.Settings.ColorPalettes[key])
{
_tcManager.Settings.ColorPalettes[key] = mergedValues;
dirty = true;
}
}
if (dirty)
_tcManager.Settings.SaveColorPalettesToJsonFile();
return dirty;
}
finally
{
try
{
RobustIO.DeleteDirectory(tempFolder, true);
}
catch (Exception e)
{
Logger.WriteEvent(
$"Error deleting temporary folder {tempFolder}: " + e.Message
);
}
}
}
}

private string MergeColorPaletteValues(string repoValues, string localValues)
{
if (repoValues == localValues)
return localValues;
if (String.IsNullOrEmpty(repoValues))
return localValues;
if (String.IsNullOrEmpty(localValues))
return repoValues;
var repoArray = repoValues.Split(new char[] { ' ' });
var localList = new List<string>(localValues.Split(new char[] { ' ' }));
foreach (var value in repoArray)
{
if (!string.IsNullOrEmpty(value) && !localList.Contains(value))
localList.Add(value);
}
return string.Join(" ", localList);
}

internal DateTime LocalCollectionFilesRecordedSyncTime()
Expand Down Expand Up @@ -1013,7 +1119,14 @@ public static List<string> RootLevelCollectionFilesIn(string folder)
{
var files = new List<string>();
files.Add(Path.GetFileName(CollectionPath(folder)));
foreach (var file in new[] { "customCollectionStyles.css", "configuration.txt" })
foreach (
var file in new[]
{
"customCollectionStyles.css",
"configuration.txt",
"colorPalettes.json"
}
)
{
if (RobustFile.Exists(Path.Combine(folder, file)))
files.Add(file);
Expand Down