Skip to content

Commit 07c7e4e

Browse files
Ensure "Title Missing" shows for appropriate books (BL-16027)
1 parent 7968827 commit 07c7e4e

9 files changed

Lines changed: 218 additions & 58 deletions

File tree

src/BloomBrowserUI/collectionsTab/BooksOfCollection.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Grid from "@mui/material/Grid";
44
import * as React from "react";
55
import { useState, useEffect } from "react";
66
import "./BooksOfCollection.less";
7-
import { useApiData, useWatchApiData } from "../utils/bloomApi";
7+
import { post, useApiData, useWatchApiData } from "../utils/bloomApi";
88
import {
99
BookButton,
1010
bookButtonHeight,
@@ -86,6 +86,17 @@ export const BooksOfCollection: React.FunctionComponent<{
8686
setReloadParameter("");
8787
}, [unfilteredBooks, props.filter]);
8888

89+
// Notify the backend that the collection pane is ready to receive book label updates.
90+
// Using a ref callback so this happens after the DOM is rendered, without needing useEffect.
91+
const collectionPaneRef = React.useCallback(
92+
(node: HTMLDivElement | null) => {
93+
if (node && props.isEditableCollection && books.length > 0) {
94+
post("collections/collectionPaneReady");
95+
}
96+
},
97+
[props.isEditableCollection, books.length],
98+
);
99+
89100
//const selectedBookInfo = useMonitorBookSelection();
90101
const collection: ICollection = useApiData(
91102
`collections/collectionProps?${collectionQuery}`,
@@ -123,6 +134,7 @@ export const BooksOfCollection: React.FunctionComponent<{
123134
key={"BookCollection-" + props.collectionId}
124135
className="bookButtonPane"
125136
style={{ cursor: "context-menu" }}
137+
ref={collectionPaneRef}
126138
>
127139
{books.length > 0 && (
128140
<Grid

src/BloomExe/Book/Book.cs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -328,15 +328,7 @@ public virtual string TitleBestForUserDisplay
328328
/// </remarks>
329329
public virtual string NameBestForUserDisplay
330330
{
331-
get
332-
{
333-
if (BookInfo.FileNameLocked)
334-
{
335-
// The user has explicitly chosen a name to use for the book, distinct from its titles.
336-
return Path.GetFileName(FolderPath);
337-
}
338-
return TitleBestForUserDisplay;
339-
}
331+
get { return BookInfo.GetBestDisplayTitle(null, this); }
340332
}
341333

342334
/// <summary>

src/BloomExe/Book/BookInfo.cs

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Threading;
1010
using System.Windows.Forms;
1111
using Bloom.Api;
12+
using Bloom.Collection;
1213
using Bloom.Edit;
1314
using Bloom.Utils;
1415
using L10NSharp;
@@ -341,6 +342,8 @@ public string Copyright
341342
/// </summary>
342343
public string QuickTitleUserDisplay => FolderName;
343344

345+
public string ThumbnailLabel;
346+
344347
public bool TryGetPremadeThumbnail(out Image image)
345348
{
346349
string path = Path.Combine(FolderPath, "thumbnail.png");
@@ -851,9 +854,66 @@ internal string GetBestTitleForUserDisplay(
851854
List<string> langCodes,
852855
bool ignoreFolderName = false
853856
)
857+
{
858+
return GetBestDisplayTitle(null, null, langCodes, ignoreFolderName);
859+
}
860+
861+
internal string GetBestDisplayTitle(
862+
CollectionSettings settings, // may be null
863+
Book book, // may be null, but if we have it, we can use it to get the title without having to read the html again
864+
List<string> langCodes = null,
865+
bool ignoreFolderName = false
866+
)
854867
{
855868
if (!ignoreFolderName && FileNameLocked)
869+
{
870+
// The user has explicitly chosen a name to use for the book, distinct from its titles.
856871
return FolderName;
872+
}
873+
if (IsInEditableCollection)
874+
{
875+
// If we have the book, we can use that for getting the title.
876+
// If we have the settings but not the book, we can use the settings to get the title from the html.
877+
if (!string.IsNullOrEmpty(FolderPath) && (book != null || settings != null))
878+
{
879+
// Use the loaded book if we have it to get the best title.
880+
if (FolderPath == book?.FolderPath && book.BookInfo.SaveContext == SaveContext)
881+
{
882+
if (langCodes == null)
883+
langCodes = book.BookData.GetBasicBookLanguageCodes().ToList();
884+
return Book.GetBestTitleForDisplay(
885+
book.BookData.GetMultiTextVariableOrEmpty("bookTitle"),
886+
langCodes,
887+
IsInEditableCollection
888+
);
889+
}
890+
else
891+
{
892+
// If we can create a BookData, we can get the title from there.
893+
var htmlPath = BookStorage.FindBookHtmlInFolder(FolderPath);
894+
if (
895+
!string.IsNullOrEmpty(htmlPath)
896+
&& RobustFile.Exists(htmlPath)
897+
&& settings != null
898+
)
899+
{
900+
var dom = HtmlDom.CreateFromHtmlFile(htmlPath);
901+
var bookData = new BookData(dom, settings, null);
902+
if (langCodes == null)
903+
langCodes = bookData.GetBasicBookLanguageCodes().ToList();
904+
return Book.GetBestTitleForDisplay(
905+
bookData.GetMultiTextVariableOrEmpty("bookTitle"),
906+
langCodes,
907+
IsInEditableCollection
908+
);
909+
}
910+
}
911+
}
912+
}
913+
// If we still don't have a title, try to get one from the metadata.
914+
// This code is used for all books that are not in the editable collection, and for any books
915+
// in the editable collection that don't seem to have any title in the HTML. (The latter case
916+
// may be hopeless, but this check isn't very expensive.)
857917
try
858918
{
859919
// JSON parsing requires newlines to be double quoted with backslashes inside string values.
@@ -867,13 +927,20 @@ internal string GetBestTitleForUserDisplay(
867927
// behave as expected.
868928
foreach (var lang in langs.Where((l) => l != "item"))
869929
multiText[lang] = titles[lang].Trim();
870-
return Book.GetBestTitleForDisplay(multiText, langCodes, IsInEditableCollection);
930+
if (langCodes == null)
931+
langCodes = langs.Where((l) => l != "item").ToList();
932+
if (langCodes.Count > 0)
933+
return Book.GetBestTitleForDisplay(
934+
multiText,
935+
langCodes,
936+
IsInEditableCollection
937+
);
871938
}
872939
catch (Exception e)
873940
{
874941
Console.WriteLine(e);
875942
}
876-
return Title;
943+
return Title; // Title may be empty, but we have no better option at this point.
877944
}
878945

879946
private static void SafelyAddToIdSet(

src/BloomExe/Book/BookStorage.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1844,9 +1844,9 @@ public void SetBookName(string name)
18441844
Debug.Fail("(debug mode only): could not rename the folder");
18451845
}
18461846

1847-
RaiseBookRenamedEvent(fromToPair);
1848-
18491847
OnFolderPathChanged();
1848+
1849+
RaiseBookRenamedEvent(fromToPair);
18501850
}
18511851

18521852
// Move a file, possibly only changing the case of the name.
@@ -1914,9 +1914,8 @@ public void RestoreBookName(string restoredName)
19141914
string restoredPath = Path.Combine(Path.GetDirectoryName(FolderPath), restoredName);
19151915
var fromToPair = new KeyValuePair<string, string>(FolderPath, restoredPath);
19161916
FolderPath = restoredPath;
1917-
RaiseBookRenamedEvent(fromToPair);
1918-
19191917
OnFolderPathChanged();
1918+
RaiseBookRenamedEvent(fromToPair);
19201919
}
19211920

19221921
private void RaiseBookRenamedEvent(KeyValuePair<string, string> fromToPair)

src/BloomExe/Collection/BookCollection.cs

Lines changed: 60 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ public enum CollectionType
2424
SourceCollection,
2525
}
2626

27-
public delegate BookCollection Factory(string path, CollectionType collectionType); //autofac uses this
27+
public delegate BookCollection Factory(
28+
string path,
29+
CollectionType collectionType,
30+
CollectionSettings collectionSettings = null
31+
); //autofac uses this
2832

2933
public EventHandler CollectionChanged;
3034

@@ -37,6 +41,8 @@ public enum CollectionType
3741
private static HashSet<string> _changingFolders = new HashSet<string>();
3842
private BloomWebSocketServer _webSocketServer;
3943

44+
private CollectionSettings _collectionSettings;
45+
4046
public static event EventHandler CollectionCreated;
4147

4248
//for moq only
@@ -53,13 +59,15 @@ public BookCollection(
5359
CollectionType collectionType,
5460
BookSelection bookSelection,
5561
TeamCollectionManager tcm = null,
62+
CollectionSettings collectionSettings = null,
5663
BloomWebSocketServer webSocketServer = null
5764
)
5865
{
5966
_path = path;
6067
_bookSelection = bookSelection;
6168
_tcManager = tcm;
6269
_webSocketServer = webSocketServer;
70+
_collectionSettings = collectionSettings;
6371

6472
Type = collectionType;
6573

@@ -239,11 +247,13 @@ public void DeleteBook(Book.BookInfo bookInfo)
239247
/// <param name="bookInfo"></param>
240248
public void HandleBookDeletedFromCollection(string folderPath)
241249
{
242-
var infoToDelete = _bookInfos.FirstOrDefault(b => b.FolderPath == folderPath);
243-
//Debug.Assert(_bookInfos.Contains(bookInfo)); this will occur if we delete a book from the BloomLibrary section
244-
if (infoToDelete != null) // for paranoia. We shouldn't be trying to delete a book that isn't there.
245-
_bookInfos.Remove(infoToDelete);
246-
250+
lock (_bookInfoLock)
251+
{
252+
var infoToDelete = _bookInfos.FirstOrDefault(b => b.FolderPath == folderPath);
253+
//Debug.Assert(_bookInfos.Contains(bookInfo)); this will occur if we delete a book from the BloomLibrary section
254+
if (infoToDelete != null) // for paranoia. We shouldn't be trying to delete a book that isn't there.
255+
_bookInfos.Remove(infoToDelete);
256+
}
247257
if (CollectionChanged != null)
248258
CollectionChanged.Invoke(this, null);
249259
}
@@ -325,28 +335,34 @@ public string PathToDirectory
325335

326336
public void UpdateBookInfo(BookInfo info)
327337
{
328-
var oldIndex = _bookInfos.FindIndex(i => i.Id == info.Id);
329-
IComparer<string> comp = new NaturalSortComparer<string>();
330-
var newKey = Path.GetFileName(info.FolderPath);
331-
if (oldIndex >= 0)
338+
lock (_bookInfoLock)
332339
{
333-
// optimize: very often the new one will belong at the same index,
334-
// if that's the case we could just replace.
335-
_bookInfos.RemoveAt(oldIndex);
336-
}
340+
var oldIndex = _bookInfos.FindIndex(i => i.Id == info.Id);
341+
IComparer<string> comp = new NaturalSortComparer<string>();
342+
var newKey = Path.GetFileName(info.FolderPath);
343+
if (oldIndex >= 0)
344+
{
345+
// optimize: very often the new one will belong at the same index,
346+
// if that's the case we could just replace.
347+
_bookInfos.RemoveAt(oldIndex);
348+
}
337349

338-
int newIndex = _bookInfos.FindIndex(x =>
339-
comp.Compare(newKey, Path.GetFileName(x.FolderPath)) <= 0
340-
);
341-
if (newIndex < 0)
342-
newIndex = _bookInfos.Count;
343-
_bookInfos.Insert(newIndex, info);
350+
int newIndex = _bookInfos.FindIndex(x =>
351+
comp.Compare(newKey, Path.GetFileName(x.FolderPath)) <= 0
352+
);
353+
if (newIndex < 0)
354+
newIndex = _bookInfos.Count;
355+
_bookInfos.Insert(newIndex, info);
356+
}
344357
NotifyCollectionChanged();
345358
}
346359

347360
public void AddBookInfo(BookInfo bookInfo)
348361
{
349-
_bookInfos.Add(bookInfo);
362+
lock (_bookInfoLock)
363+
{
364+
_bookInfos.Add(bookInfo);
365+
}
350366
NotifyCollectionChanged();
351367
}
352368

@@ -356,22 +372,25 @@ public void AddBookInfo(BookInfo bookInfo)
356372
/// <param name="bookInfo"></param>
357373
public void InsertBookInfo(BookInfo bookInfo)
358374
{
359-
IComparer<string> comparer = new NaturalSortComparer<string>();
360-
for (int i = 0; i < _bookInfos.Count; i++)
375+
lock (_bookInfoLock)
361376
{
362-
var compare = comparer.Compare(_bookInfos[i].FolderPath, bookInfo.FolderPath);
363-
if (compare == 0)
364-
{
365-
_bookInfos[i] = bookInfo; // Replace
366-
return;
367-
}
368-
if (compare > 0)
377+
IComparer<string> comparer = new NaturalSortComparer<string>();
378+
for (int i = 0; i < _bookInfos.Count; i++)
369379
{
370-
_bookInfos.Insert(i, bookInfo);
371-
return;
380+
var compare = comparer.Compare(_bookInfos[i].FolderPath, bookInfo.FolderPath);
381+
if (compare == 0)
382+
{
383+
_bookInfos[i] = bookInfo; // Replace
384+
return;
385+
}
386+
if (compare > 0)
387+
{
388+
_bookInfos.Insert(i, bookInfo);
389+
return;
390+
}
372391
}
392+
_bookInfos.Add(bookInfo);
373393
}
374-
_bookInfos.Add(bookInfo);
375394
}
376395

377396
private bool BackupFileExists(string folderPath)
@@ -412,7 +431,10 @@ private void AddBookInfo(string folderPath)
412431
)
413432
? _bookSelection.CurrentSelection.BookInfo
414433
: new BookInfo(folderPath, editable, sc);
415-
434+
bookInfo.ThumbnailLabel = bookInfo.GetBestDisplayTitle(
435+
_collectionSettings,
436+
_bookSelection.CurrentSelection
437+
);
416438
_bookInfos.Add(bookInfo);
417439
}
418440
catch (Exception e)
@@ -500,7 +522,10 @@ private void WatcherOnChange(object sender, FileSystemEventArgs fileSystemEventA
500522
{
501523
if (_watcherIsDisabled)
502524
return;
503-
_bookInfos = null; // Possibly obsolete; next request will update it.
525+
lock (_bookInfoLock)
526+
{
527+
_bookInfos = null; // Possibly obsolete; next request will update it.
528+
}
504529
DebounceFolderChanged(fileSystemEventArgs.FullPath);
505530
}
506531

src/BloomExe/CollectionTab/CollectionModel.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,8 @@ private IEnumerable<BookCollection> GetBookCollectionsOnce()
360360
{
361361
editableCollection = _bookCollectionFactory(
362362
_pathToCollection,
363-
BookCollection.CollectionType.TheOneEditableCollection
363+
BookCollection.CollectionType.TheOneEditableCollection,
364+
_collectionSettings
364365
);
365366
if (_bookCollectionHolder != null)
366367
_bookCollectionHolder.TheOneEditableCollection = editableCollection;

0 commit comments

Comments
 (0)