Switch from using the title as identifier to the bookId (and remove the title from character book save)

Orionik [05-27-22 - 15:15]
Switch from using the title as identifier to the bookId (and remove the title from character book save)
Filename
CHANGELOG
Librarian.lua
Librarian.xml
LibrarianSettings.lua
lang/en.lua
lang/fr.lua
diff --git a/CHANGELOG b/CHANGELOG
index 65ae1d2..9ec5e8b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,10 +9,10 @@ Librarian v3.0 2022-05-13
 - Remove Import from before patch option which was meant for save created back in 2015
 - Remove some deprecation code for save before 2015
 - Add unreadPerCollection in save in order to know which collection should have an icon in lore library
-- Add deprecation to add these unreadPerCollection flag and the bookId
+- Deprecate saves to use bookId as identifiers instead of the title (several books had the same name), characterBooks now reference the bookId instead of the title and fill at the same time the unreadPerCollection list
 - Optimize ImportFromLoreLibrary by removing the Refresh every frame (without it, past 1000 books, the game slow down a lot and at 4000 you end up with less than 1fps)
 - Activate back auto importation from init now that it is quick enough
-- Add full localization and support french language
+- Add full localization and support for french language
 - Add a small timer before refreshing the list when doing a research as we could trigger a crash if we were typing a search too fast when we have too many books
 - Improve settings (better integration with LibAddonMenu and add a few options)

diff --git a/Librarian.lua b/Librarian.lua
index faaa1a5..f2ed37d 100644
--- a/Librarian.lua
+++ b/Librarian.lua
@@ -38,16 +38,23 @@ function Librarian:Initialize(...)
     self.localSavedVars = ZO_SavedVars:New("Librarian_SavedVariables", 1, nil, self.defaults, nil)
     self.globalSavedVars = ZO_SavedVars:NewAccountWide("Librarian_SavedVariables", 1, nil, self.defaults, nil)

-    if not self.globalSavedVars.settings then self.globalSavedVars.settings = {} end
+    if not self.globalSavedVars.settings then
+        self.globalSavedVars.settings = {}
+        self.globalSavedVars.saveVersion = 1
+    end
     self.settings = self.globalSavedVars.settings

     if not self.globalSavedVars.books then self.globalSavedVars.books = {} end
     self.books = self.globalSavedVars.books

-    if not self.localSavedVars.characterBooks then self.localSavedVars.characterBooks = {} end
+    if not self.localSavedVars.characterBooks then
+        self.localSavedVars.characterBooks = {}
+        self.localSavedVars.saveVersion = 1
+    end
     self.characterBooks = self.localSavedVars.characterBooks
     self.characterBooksCache = {}

+    if not self.localSavedVars.unreadPerCollections then self.localSavedVars.unreadPerCollections = {} end
     self.unreadPerCollections = self.localSavedVars.unreadPerCollections

     self.searchBox = GetControl(LibrarianFrame, "SearchBox")
@@ -85,9 +92,9 @@ end

 function Librarian:AddLoreReaderUnreadToggle()
     local readerKeybinds
-    if LORE_READER.keybindStripDescriptor then
+    if LORE_READER.keybindStripDescriptor then
         readerKeybinds = LORE_READER.keybindStripDescriptor
-    else
+    else
         readerKeybinds = LORE_READER.PCKeybindStripDescriptor
     end

@@ -95,24 +102,24 @@ function Librarian:AddLoreReaderUnreadToggle()
         local toggleKeybind =
         {
             alignment = KEYBIND_STRIP_ALIGN_RIGHT,
-            name = function()
-                local book = self:FindBook(LORE_READER.titleText)
-                if not book or book.unread then
-                    if self.settings.showUnreadIndicatorInReader then
-                        self.loreReaderUnreadIndicator:SetHidden(false)
-                    else
-                        self.loreReaderUnreadIndicator:SetHidden(true)
+            name = function()
+                local book = self:FindBook(self.lastShownBookId)
+                if not book or (book.unread and book.title == LORE_READER.titleText) then
+                    if self.settings.showUnreadIndicatorInReader then
+                        self.loreReaderUnreadIndicator:SetHidden(false)
+                    else
+                        self.loreReaderUnreadIndicator:SetHidden(true)
                     end
                     return GetString(LIBRARIAN_MARK_READ)
-                else
+                else
                     self.loreReaderUnreadIndicator:SetHidden(true)
                     return GetString(LIBRARIAN_MARK_UNREAD)
                 end
             end,
             keybind = "UI_SHORTCUT_SECONDARY",
             callback = function()
-                local book = self:FindBook(LORE_READER.titleText)
-                if not book then return end
+                local book = self:FindBook(self.lastShownBookId)
+                if not book or book.title ~= LORE_READER.titleText then return end
                 self:ToggleReadBook(book)
                 KEYBIND_STRIP:UpdateKeybindButtonGroup(readerKeybinds)
                 self:RefreshAllData()
@@ -134,7 +141,7 @@ function Librarian:AddLoreLibraryIcons()
         return
     end

-    -- ADD UNREAD ICON ON EACH ENTRY UNREAD
+    -- ADD UNREAD ICON ON EACH UNREAD ENTRY
     local BOOK_DATA_TYPE = 1
     local scrollList = LORE_LIBRARY.list:GetListControl()
     local initalDataType = scrollList.dataTypes[BOOK_DATA_TYPE]
@@ -143,11 +150,12 @@ function Librarian:AddLoreLibraryIcons()
     local function SetUpBookEntry(control, data)
         initalDataType.setupCallback(control, data)

-        local title, _, known = GetLoreBookInfo(data.categoryIndex, data.collectionIndex, data.bookIndex)
+        local _, _, known, bookId = GetLoreBookInfo(data.categoryIndex, data.collectionIndex, data.bookIndex)
+        control.bookId = bookId
         local shouldUnreadIconBeHidden = true
         if known then
-            local book = self:FindBook(title)
-            if not book or book.unread then
+            local book = self:FindBook(bookId)
+            if not book or book.unread then
                 shouldUnreadIconBeHidden = false
             end
         end
@@ -156,17 +164,24 @@ function Librarian:AddLoreLibraryIcons()

     ZO_ScrollList_AddDataType(scrollList, BOOK_DATA_TYPE, "Librarian_LoreLibrary_BookEntry", initalDataType.height, SetUpBookEntry)

+    -- WHEN READING BOOK FROM LORE LIBRARY, UPDATE LASTSHOWNBOOKID
+    local initial_ZO_LoreLibrary_ReadBook = ZO_LoreLibrary_ReadBook
+    ZO_LoreLibrary_ReadBook = function(categoryIndex, collectionIndex, bookIndex)
+        self.lastShownBookId = select(4, GetLoreBookInfo(categoryIndex, collectionIndex, bookIndex))
+        initial_ZO_LoreLibrary_ReadBook(categoryIndex, collectionIndex, bookIndex)
+    end
+
     -- ORDER BOOK LIST TO MAKE UNREAD BOOK FIRST
     local function BookEntryComparator(leftScrollData, rightScrollData)
         local leftData = leftScrollData.data
         local rightData = rightScrollData.data
-        local leftTitle, _, leftKnown = GetLoreBookInfo(leftData.categoryIndex, leftData.collectionIndex, leftData.bookIndex)
-        local rightTitle, _, rightKnown = GetLoreBookInfo(rightData.categoryIndex, rightData.collectionIndex, rightData.bookIndex)
+        local leftTitle, _, leftKnown, leftBookId = GetLoreBookInfo(leftData.categoryIndex, leftData.collectionIndex, leftData.bookIndex)
+        local rightTitle, _, rightKnown, rightBookId = GetLoreBookInfo(rightData.categoryIndex, rightData.collectionIndex, rightData.bookIndex)

         if leftKnown == rightKnown then
             if leftKnown then
-                local leftBook = self:FindBook(leftTitle)
-                local rightBook = self:FindBook(rightTitle)
+                local leftBook = self:FindBook(leftBookId)
+                local rightBook = self:FindBook(rightBookId)
                 if (leftBook and not leftBook.unread) == (rightBook and not rightBook.unread) then
                     return leftTitle < rightTitle
                 end
@@ -198,7 +213,7 @@ function Librarian:AddLoreLibraryIcons()
         name = function()
             local selectedRow = LORE_LIBRARY.list:GetMouseOverRow()
             if selectedRow and selectedRow.known then
-                local book = self:FindBook(selectedRow.text:GetText())
+                local book = self:FindBook(selectedRow.bookId)
                 if not book or book.unread then
                     return GetString(LIBRARIAN_MARK_READ)
                 else
@@ -214,7 +229,7 @@ function Librarian:AddLoreLibraryIcons()
         callback = function()
             local selectedRow = LORE_LIBRARY.list:GetMouseOverRow()
             if selectedRow then
-                local book = self:FindBook(selectedRow.text:GetText())
+                local book = self:FindBook(selectedRow.bookId)
                 if not book then
                     local bookId = select(4, GetLoreBookInfo(selectedRow.categoryIndex, selectedRow.collectionIndex, selectedRow.bookIndex))
                     local body, medium, showTitle = ReadLoreBook(selectedRow.categoryIndex, selectedRow.collectionIndex, selectedRow.bookIndex)
@@ -232,7 +247,7 @@ function Librarian:AddLoreLibraryIcons()
     }
     table.insert(loreLibraryKeybinds, toggleKeybind)

-    -- ADD UNREAD ICON ON COLLECTION IF IT CONTAINS AN UNREAD BOOK
+    -- ADD UNREAD ICON ON COLLECTION IF IT CONTAINS AN UNREAD BOOK OR ADD COMPLETED MARK IF ALL ARE KNOWN AND READ
     local navigationEntryTemplateInfo = LORE_LIBRARY.navigationTree.templateInfo["ZO_LoreLibraryNavigationEntry"]
     local previousSetupFunction = navigationEntryTemplateInfo.setupFunction

@@ -263,45 +278,133 @@ function Librarian:AddLoreLibraryIcons()
     LORE_LIBRARY.navigationTree.templateInfo["ZO_LabelHeader"].equalityFunction = navigationEntryTemplateInfo.equalityFunction
 end

-function Librarian:UpdateSavedVariables()
-    -- before version 3.0, unreadPerCollections didn't exist and bookId wasn't set
-    if not self.unreadPerCollections then
-        self.localSavedVars.unreadPerCollections = {}
-        self.unreadPerCollections = self.localSavedVars.unreadPerCollections
+local function GetNextBookIndex(bookIndexes)
+    if bookIndexes.bookIndex >= bookIndexes.totalBooks then
+        if bookIndexes.collectionIndex >= bookIndexes.numCollections then
+            if bookIndexes.categoryIndex >= GetNumLoreCategories() then
+                return false
+            else
+                bookIndexes.categoryIndex = bookIndexes.categoryIndex + 1
+                bookIndexes.numCollections = select(2, GetLoreCategoryInfo(bookIndexes.categoryIndex))
+                bookIndexes.collectionIndex = 0
+            end
+        end
+        bookIndexes.collectionIndex = bookIndexes.collectionIndex + 1
+        bookIndexes.totalBooks = select(4, GetLoreCollectionInfo(bookIndexes.categoryIndex, bookIndexes.collectionIndex))
+        bookIndexes.bookIndex = 0
+    end
+    bookIndexes.bookIndex = bookIndexes.bookIndex + 1
+    return true
+end

-        -- if this character doesn't know any book yet, no need to go further
-        if next(self.characterBooks) ~= nil then
+function Librarian:UpdateSavedVariables()
+    -- before version 3.0 there was no saveVersion
+    -- In this version, the bookId is now used as identifier instead of the title (because several book have the same title)
+    -- Also unreadPerCollections was added
+    if not self.globalSavedVars.saveVersion then
+        self.globalSavedVars.saveVersion = 1
+
+        -- if this player doesn't know any book yet, no need to go further
+        if next(self.books) ~= nil then
             local GetLoreBookInfo, ReadLoreBook = GetLoreBookInfo, ReadLoreBook
-            local categoryIndex, collectionIndex, bookIndex = 0, 0, 0
-            local numCollections, totalBooks = 0, 0
+            local bookIndexes = {
+                categoryIndex = 0,
+                collectionIndex = 0,
+                bookIndex = 0,
+                numCollections = 0,
+                totalBooks = 0,
+            }
             while true do
-                if bookIndex >= totalBooks then
-                    if collectionIndex >= numCollections then
-                        if categoryIndex >= GetNumLoreCategories() then
-                            break
+                if not GetNextBookIndex(bookIndexes) then
+                    break
+                end
+
+                local title, _, _, bookId = GetLoreBookInfo(bookIndexes.categoryIndex, bookIndexes.collectionIndex, bookIndexes.bookIndex)
+                local book = nil
+                for _,bookIt in pairs(self.books) do
+                    if bookIt.title == title then
+                        book = bookIt
+                        break
+                    end
+                end
+                if book then
+                    if not book.bookId then
+                        book.bookId = bookId
+                    else -- there is a collision (2 books with same name)
+                        local body, medium, showTitle = ReadLoreBook(bookIndexes.categoryIndex, bookIndexes.collectionIndex, bookIndexes.bookIndex)
+
+                        local bookBody = table.concat(book.body)
+                        if bookBody == body then
+                            -- the current book is the one corresponding so we will update the bookId and add a book for the previous book which had the same title
+                            local previousCategoryIndex, previousCollectionIndex, previousBookIndex = GetLoreBookIndicesFromBookId(book.bookId)
+                            local previousBody, previousMedium, previousShowTitle = ReadLoreBook(previousCategoryIndex, previousCollectionIndex, previousBookIndex)
+                            local newBook = { title = title, body = previousBody, medium = previousMedium, showTitle = previousShowTitle, bookId = book.bookId }
+                            self:AddBookToGlobalSave(newBook, false)
+
+                            book.bookId = bookId
                         else
-                            categoryIndex = categoryIndex + 1
-                            local loreCategoryName
-                            numCollections = select(2, GetLoreCategoryInfo(categoryIndex))
-                            collectionIndex = 0
+                            -- the current book has a different body, so let's create an entry for it
+                            local newBook = { title = title, body = body, medium = medium, showTitle = showTitle, bookId = bookId }
+                            self:AddBookToGlobalSave(newBook, false)
                         end
                     end
-                    collectionIndex = collectionIndex + 1
-                    totalBooks = select(4, GetLoreCollectionInfo(categoryIndex, collectionIndex))
-                    bookIndex = 0
                 end
-                bookIndex = bookIndex + 1
-                local title, icon, known, bookId = GetLoreBookInfo(categoryIndex, collectionIndex, bookIndex)
-                book = self:FindBook(title)
-                if book then
-                    book.bookId = bookId
-                    if known and book.unread and self:FindCharacterBook(title, bookId) then
-                        self:AddUnreadBookInCollection(categoryIndex, collectionIndex)
+            end
+        end
+    end
+
+    if not self.localSavedVars.saveVersion then
+        self.localSavedVars.saveVersion = 1
+        self.localSavedVars.unreadPerCollections = {} -- erase whatever is already inside (just in case)
+        self.unreadPerCollections = self.localSavedVars.unreadPerCollections
+
+        local GetLoreBookInfo, ReadLoreBook = GetLoreBookInfo, ReadLoreBook
+        local bookIndexes = {
+            categoryIndex = 0,
+            collectionIndex = 0,
+            bookIndex = 0,
+            numCollections = 0,
+            totalBooks = 0,
+        }
+        while true do
+            if not GetNextBookIndex(bookIndexes) then
+                break
+            end
+
+            local title, _, known, bookId = GetLoreBookInfo(bookIndexes.categoryIndex, bookIndexes.collectionIndex, bookIndexes.bookIndex)
+
+            if known then
+                local characterBook = nil
+                for _,characterBookIt in ipairs(self.characterBooks) do
+                    if characterBookIt.title == title then
+                        characterBook = characterBookIt
+                        break
+                    end
+                end
+
+                if characterBook then
+                    if not characterBook.bookId then
+                        characterBook.bookId = bookId
+                    else
+                        local newCharacterBook = { title = title, timeStamp = characterBook.timeStamp, bookId = bookId }
+                        table.insert(self.characterBooks, newCharacterBook)
+                    end
+
+                    local book = self:FindBook(bookId)
+                    if book and book.unread then
+                        self:AddUnreadBookInCollection(bookIndexes.categoryIndex, bookIndexes.collectionIndex)
                     end
                 end
             end
         end
+
+        -- now we can remove the title from the characterBooks and rely only on the bookId (it saves space)
+        for _,characterBook in ipairs(self.characterBooks) do
+            characterBook.title = nil
+        end
     end
+
+
 end

 function Librarian:InitializeKeybindStripDescriptors()
@@ -311,30 +414,30 @@ function Librarian:InitializeKeybindStripDescriptors()
             alignment = KEYBIND_STRIP_ALIGN_RIGHT,
             name = GetString(SI_LORE_LIBRARY_READ),
             keybind = "UI_SHORTCUT_PRIMARY",
-            visible = function()
-                return self.mouseOverRow
+            visible = function()
+                return self.mouseOverRow
             end,
             callback = function()
-                self:ReadBook(self.mouseOverRow.data.title)
+                self:ReadBook(self.mouseOverRow.data)
             end,
         },
         {
             alignment = KEYBIND_STRIP_ALIGN_RIGHT,
-            name = function()
+            name = function()
                 if not self.mouseOverRow then return nil end
-                local book = self:FindBook(self.mouseOverRow.data.title)
-                if book.unread then
+                local book = self:FindBook(self.mouseOverRow.data.bookId)
+                if book.unread then
                     return GetString(LIBRARIAN_MARK_READ)
-                else
+                else
                     return GetString(LIBRARIAN_MARK_UNREAD)
                 end
             end,
             keybind = "UI_SHORTCUT_SECONDARY",
-            visible = function()
-                return self.mouseOverRow
+            visible = function()
+                return self.mouseOverRow
             end,
             callback = function()
-                local book = self:FindBook(self.mouseOverRow.data.title)
+                local book = self:FindBook(self.mouseOverRow.data.bookId)
                 self:ToggleReadBook(book)
                 self:RefreshAllData()
             end,
@@ -356,11 +459,11 @@ function Librarian:InitializeScene()
         LIBRARIAN_SCENE:AddFragment(LIBRARIAN_TITLE_FRAGMENT)
         LIBRARIAN_SCENE:AddFragment(CODEX_WINDOW_SOUNDS)

-        LIBRARIAN_SCENE:RegisterCallback("StateChange",
+        LIBRARIAN_SCENE:RegisterCallback("StateChange",
             function(oldState, newState)
-                if(newState == SCENE_SHOWING) then
+                if(newState == SCENE_SHOWING) then
                     KEYBIND_STRIP:AddKeybindButtonGroup(self.keybindStripDescriptor)
-                elseif(newState == SCENE_HIDDEN) then
+                elseif(newState == SCENE_HIDDEN) then
                     KEYBIND_STRIP:RemoveKeybindButtonGroup(self.keybindStripDescriptor)
                 end
             end)
@@ -408,7 +511,7 @@ function Librarian:ImportFromLoreLibrary()
         bookIndex = bookIndex + 1
         local title, icon, known, bookId = GetLoreBookInfo(categoryIndex, collectionIndex, bookIndex)
         if known then
-            if not self:FindCharacterBook(title, bookId) then
+            if not self:FindCharacterBook(bookId) then
                 local body, medium, showTitle = ReadLoreBook(categoryIndex, collectionIndex, bookIndex)
                 local book = { title = title, body = body, medium = medium, showTitle = showTitle, bookId = bookId }
                 self:AddBook(book, false)
@@ -430,25 +533,40 @@ function Librarian:ImportFromLoreLibrary()
 end

 function Librarian:BuildMasterList()
+    local GetLoreBookIndicesFromBookId, GetLoreCollectionInfo = GetLoreBookIndicesFromBookId, GetLoreCollectionInfo
+
+    local function ShouldDisplayBook(book)
+        if not self.settings.showHiddenBook then
+            local category, collection = GetLoreBookIndicesFromBookId(book.bookId)
+            local hiddenCollection = select(5, GetLoreCollectionInfo(category, collection))
+            if hiddenCollection then
+                return false
+            end
+        end
+        return true
+    end
+
     for i, book in ipairs(self.books) do
-        local data = {}
-        for k,v in pairs(book) do
-            if k == "body" then
-                data[k] = table.concat(book.body)
+        if ShouldDisplayBook(book) then
+            local data = {}
+            for k,v in pairs(book) do
+                if k == "body" then
+                    data[k] = table.concat(book.body)
+                else
+                    data[k] = v
+                end
+            end
+            data.type = LIBRARIAN_SEARCH
+            local characterBook = self:FindCharacterBook(book.bookId)
+            if characterBook then
+                data.seenByCurrentCharacter = true
+                data.timeStamp = characterBook.timeStamp
             else
-                data[k] = v
+                data.seenByCurrentCharacter = false
+                data.timeStamp = book.timeStamp
             end
+            self.masterList[i] = data
         end
-        data.type = LIBRARIAN_SEARCH
-        local characterBook = self:FindCharacterBook(book.title, book.bookId)
-        if characterBook then
-            data.seenByCurrentCharacter = true
-            data.timeStamp = characterBook.timeStamp
-        else
-            data.seenByCurrentCharacter = false
-            data.timeStamp = book.timeStamp
-        end
-        self.masterList[i] = data
     end
 end

@@ -516,20 +634,18 @@ function Librarian:ProcessBookEntry(stringSearch, data, searchTerm, cache)
     return false
 end

-function Librarian:FindCharacterBook(title, bookId)
+function Librarian:FindCharacterBook(bookId)
     if not self.characterBooks then
         return nil
     end

-    if bookId and self.characterBooksCache[bookId] then
+    if self.characterBooksCache[bookId] then
         return self.characterBooksCache[bookId]
     end

-    for _,book in pairs(self.characterBooks) do
-        if book.title == title then
-            if bookId then
-                self.characterBooksCache[bookId] = book
-            end
+    for _,book in ipairs(self.characterBooks) do
+        if book.bookId == bookId then
+            self.characterBooksCache[bookId] = book
             return book
         end
     end
@@ -537,33 +653,39 @@ function Librarian:FindCharacterBook(title, bookId)
     return nil
 end

-function Librarian:FindBook(title)
-    for _,book in pairs(self.books) do
-        if book.title == title then return book end
+function Librarian:FindBook(bookId)
+    for _,book in ipairs(self.books) do
+        if book.bookId == bookId then
+            return book
+        end
     end
 end

-function Librarian:AddBook(book, refreshDataRightAway)
-    if not self:FindCharacterBook(book.title, book.bookId) then
-        if not self:FindBook(book.title) then
-            book.timeStamp = GetTimeStamp()
-            book.unread = true
-            local wordCount = 0
-            for w in book.body:gmatch("%S+") do wordCount = wordCount + 1 end
-            book.wordCount = wordCount
-
-            local newBody = book.body
-            book.body = {}
-            while string.len(newBody) > 1024 do
-                table.insert(book.body, string.sub(newBody, 0, 1024))
-                newBody = string.sub(newBody, 1025)
-            end
-            table.insert(book.body, newBody)
+function Librarian:AddBookToGlobalSave(book)
+    book.timeStamp = GetTimeStamp()
+    book.unread = true
+    local wordCount = 0
+    for w in book.body:gmatch("%S+") do wordCount = wordCount + 1 end
+    book.wordCount = wordCount
+
+    local newBody = book.body
+    book.body = {}
+    while string.len(newBody) > 1024 do
+        table.insert(book.body, string.sub(newBody, 0, 1024))
+        newBody = string.sub(newBody, 1025)
+    end
+    table.insert(book.body, newBody)

-            table.insert(self.books, book)
+    table.insert(self.books, book)
+end
+
+function Librarian:AddBook(book, refreshDataRightAway)
+    if not self:FindCharacterBook(book.bookId) then
+        if not self:FindBook(book.bookId) then
+            self:AddBookToGlobalSave(book)
         end

-        local characterBook = { title = book.title, timeStamp = GetTimeStamp() }
+        local characterBook = { bookId = book.bookId, timeStamp = GetTimeStamp() }
         table.insert(self.characterBooks, characterBook)

         if book.unread then
@@ -619,13 +741,14 @@ function Librarian:Toggle()
         SCENE_MANAGER:Show("librarian")
     else
         SCENE_MANAGER:Hide("librarian")
-    end
-end
+    end
+end
+
+function Librarian:ReadBook(data)
+    self.lastShownBookId = data.bookId

-function Librarian:ReadBook(title)
-    local book = self:FindBook(title)
-    LORE_READER:SetupBook(book.title, book.body and table.concat(book.body), book.medium, book.showTitle)
-    SCENE_MANAGER:Push("loreReaderInteraction")
+    LORE_READER:SetupBook(data.title, data.body, data.medium, data.showTitle)
+    SCENE_MANAGER:Push("loreReaderInteraction")

     -- PlaySound(LORE_READER.OpenSound)
 end
@@ -677,6 +800,8 @@ end
 function Librarian.OnShowBook(eventCode, title, body, medium, showTitle, bookId)
     local book = { title = title, body = body, medium = medium, showTitle = showTitle, bookId = bookId }
     LIBRARIAN:AddBook(book, true)
+
+    LIBRARIAN.lastShownBookId = bookId
 end

 function Librarian.OnMouseEnterRow(control)
@@ -688,7 +813,7 @@ function Librarian.OnMouseExitRow(control)
 end

 function Librarian.OnMouseUpRow(control, button, upInside)
-    LIBRARIAN:ReadBook(control.data.title)
+    LIBRARIAN:ReadBook(control.data)
 end

 function Librarian.OnAddonLoaded(event, addon)
diff --git a/Librarian.xml b/Librarian.xml
index d7e6a83..e24c405 100644
--- a/Librarian.xml
+++ b/Librarian.xml
@@ -28,7 +28,7 @@
                 </Label>
             </Controls>
         </Button>
-
+
         <TopLevelControl name="LibrarianFrame" inherits="ZO_RightPanelFootPrint" hidden="true">
             <Controls>
                 <Button name="$(parent)Options" mouseOverBlendMode="ADD" inherits="ZO_ButtonBehaviorClickSound">
@@ -41,7 +41,7 @@
                     />
                     <OnClicked>
                         SLASH_COMMANDS["/librarianOptions"]()
-                    </OnClicked>
+                    </OnClicked>
                 </Button>
                 <Label name="$(parent)BookCount" font="ZoFontHeader3" color="INTERFACE_COLOR_TYPE_TEXT_COLORS:INTERFACE_TEXT_COLOR_NORMAL">
                     <Anchor point="TOPLEFT" offsetY="10" />
@@ -134,7 +134,7 @@
                 </Texture>
             </Controls>
         </Control>
-
+

         <Label name="Librarian_LoreLibraryNavigationEntry" inherits="ZO_LoreLibraryNavigationEntry" virtual="true">
             <Controls>
diff --git a/LibrarianSettings.lua b/LibrarianSettings.lua
index 28415cd..046ef68 100644
--- a/LibrarianSettings.lua
+++ b/LibrarianSettings.lua
@@ -1,19 +1,19 @@
 LibrarianSettings = ZO_Object:Subclass()

 local timeFormats = {
-	{ name = GetString(LIBRARIAN_SETTINGS_TIME_12), value = TIME_FORMAT_PRECISION_TWELVE_HOUR },
+	{ name = GetString(LIBRARIAN_SETTINGS_TIME_12), value = TIME_FORMAT_PRECISION_TWELVE_HOUR },
 	{ name = GetString(LIBRARIAN_SETTINGS_TIME_24), value = TIME_FORMAT_PRECISION_TWENTY_FOUR_HOUR }
 }

 local alertStyles = {
-	{ name = GetString(LIBRARIAN_SETTINGS_ALERT_NONE), value = "None", chat = false, alert = false },
+	{ name = GetString(LIBRARIAN_SETTINGS_ALERT_NONE), value = "None", chat = false, alert = false },
 	{ name = GetString(LIBRARIAN_SETTINGS_ALERT_CHAT), value = "Chat", chat = true, alert = false },
 	{ name = GetString(LIBRARIAN_SETTINGS_ALERT_NOTIFICATION), value = "Alert", chat = false, alert = true },
 	{ name = GetString(LIBRARIAN_SETTINGS_ALERT_BOTH), value = "Both", chat = true, alert = true },
 }

 local reloadReminders = {
-	{ name = GetString(LIBRARIAN_SETTINGS_RELOAD_REMNDER_NEVER), value = 0 },
+	{ name = GetString(LIBRARIAN_SETTINGS_RELOAD_REMNDER_NEVER), value = 0 },
 	{ name = GetString(LIBRARIAN_SETTINGS_RELOAD_REMNDER_1), value = 1 },
 	{ name = GetString(LIBRARIAN_SETTINGS_RELOAD_REMNDER_5), value = 5 },
 	{ name = GetString(LIBRARIAN_SETTINGS_RELOAD_REMNDER_10), value = 10 }
@@ -62,6 +62,10 @@ function LibrarianSettings:Initialize(settings, menuSettings)
 		self.settings.alertEnabled = true
 	end

+    if self.settings.showHiddenBook == nil then
+        self.settings.showHiddenBook = true
+    end
+
 	if self.settings.showUnreadIndicatorInReader == nil then
 		self.settings.showUnreadIndicatorInReader = true
 	end
@@ -77,11 +81,11 @@ function LibrarianSettings:Initialize(settings, menuSettings)
   if self.settings.enableCharacterSpin == nil then
     self.settings.enableCharacterSpin = true
   end
-
+
   if self.settings.unreadIndicatorTransparency == nil then
     self.settings.unreadIndicatorTransparency = 1
   end
-
+
   local panelData = {
     type = "panel",
     name = GetString(LIBRARIAN_WINDOW_TITLE_LIBRARIAN),
@@ -92,49 +96,59 @@ function LibrarianSettings:Initialize(settings, menuSettings)
   }

   local optionsTable = {
-    [1] = {
+    {
       type = "dropdown",
       name = LIBRARIAN_SETTINGS_TIME,
       tooltip = LIBRARIAN_SETTINGS_TIME_TOOLTIP,
       choices = map(timeFormats, function(item) return item.name end),
       getFunc = function() return getSettingByValue(timeFormats, self.settings.timeFormat).name end,
-      setFunc = function(name)
+      setFunc = function(name)
         self.settings.timeFormat = getSettingByName(timeFormats, name).value
         LIBRARIAN:CommitScrollList()
       end
     },
-    [2] = {
+    {
       type = "dropdown",
       name = LIBRARIAN_SETTINGS_ALERT,
       tooltip = LIBRARIAN_SETTINGS_ALERT_TOOLTIP,
       choices = map(alertStyles, function(item) return item.name end),
       getFunc = function() return getSettingByValue(alertStyles, self.settings.alertStyle).name end,
-      setFunc = function(name)
+      setFunc = function(name)
         local setting = getSettingByName(alertStyles, name)
         self.settings.alertStyle = setting.value
         self.settings.chatEnabled = setting.chat
         self.settings.alertEnabled = setting.alert
       end
     },
-    [3] = {
+    {
       type = "dropdown",
       name = LIBRARIAN_SETTINGS_RELOADUI_REMINDER,
       tooltip = LIBRARIAN_SETTINGS_RELOADUI_REMINDER_TOOLTIP,
       choices = map(reloadReminders, function(item) return item.name end),
       getFunc = function() return getSettingByValue(reloadReminders, self.settings.reloadReminderBookCount).name end,
-      setFunc = function(name)
+      setFunc = function(name)
         local setting = getSettingByName(reloadReminders, name)
         self.settings.reloadReminderBookCount = setting.value
       end
     },
-    [4] = {
+    {
+      type = "checkbox",
+      name = LIBRARIAN_SETTINGS_SHOW_HIDDEN_BOOK,
+      tooltip = LIBRARIAN_SETTINGS_SHOW_HIDDEN_BOOK_TOOLTIP,
+      getFunc = function() return self.settings.showHiddenBook end,
+      setFunc = function(value)
+        self.settings.showHiddenBook = value
+        LIBRARIAN:RefreshData()
+      end
+    },
+    {
       type = "checkbox",
       name = LIBRARIAN_SETTINGS_UNREAD_INDICATOR_READER,
       tooltip = LIBRARIAN_SETTINGS_UNREAD_INDICATOR_READER_TOOLTIP,
       getFunc = function() return self.settings.showUnreadIndicatorInReader end,
       setFunc = function(value) self.settings.showUnreadIndicatorInReader = value end
     },
-    [5] = {
+    {
       type = "slider",
       name = LIBRARIAN_SETTINGS_ICON_TRANSPARENCY,
       tooltip = LIBRARIAN_SETTINGS_ICON_TRANSPARENCY_TOOLTIP,
@@ -147,7 +161,7 @@ function LibrarianSettings:Initialize(settings, menuSettings)
       max = 100,
       step = 1
     },
-    [6] = {
+    {
       type = "checkbox",
       name = LIBRARIAN_SETTINGS_UNREAD_INDICATOR_LIBRARY,
       tooltip = LIBRARIAN_SETTINGS_UNREAD_INDICATOR_LIBRARY_TOOLTIP,
@@ -155,7 +169,7 @@ function LibrarianSettings:Initialize(settings, menuSettings)
       setFunc = function(value) self.settings.showUnreadIndicatorInLoreLibrary = value end,
       requiresReload = true
     },
-    [7] = {
+    {
       type = "checkbox",
       name = LIBRARIAN_SETTINGS_CHARACTER_SPIN,
       tooltip = LIBRARIAN_SETTINGS_CHARACTER_SPIN_TOOLTIP,
@@ -163,14 +177,14 @@ function LibrarianSettings:Initialize(settings, menuSettings)
       setFunc = function(value) self.settings.enableCharacterSpin = value end,
       requiresReload = true
     },
-    [8] = {
+    {
       type = "button",
       name = GetString(LIBRARIAN_SETTINGS_IMPORT),
       tooltip = GetString(LIBRARIAN_SETTINGS_IMPORT_TOOLTIP),
       func = function() LIBRARIAN:ImportFromLoreLibrary() end
     }
   }
-
+
   local LAM = LibAddonMenu2
   LAM:RegisterAddonPanel(menuSettings.name, panelData)
   LAM:RegisterOptionControls(menuSettings.name, optionsTable)
diff --git a/lang/en.lua b/lang/en.lua
index 3245be9..e168aa7 100644
--- a/lang/en.lua
+++ b/lang/en.lua
@@ -29,6 +29,8 @@ local strings = {
     LIBRARIAN_SETTINGS_ALERT_TOOLTIP = "Select a style of alert",
     LIBRARIAN_SETTINGS_RELOADUI_REMINDER = "ReloadUI reminder after",
     LIBRARIAN_SETTINGS_RELOADUI_REMINDER_TOOLTIP = "Reminder to type /reloadui after this number of new books are discovered.",
+    LIBRARIAN_SETTINGS_SHOW_HIDDEN_BOOK = "Show Hidden Book",
+    LIBRARIAN_SETTINGS_SHOW_HIDDEN_BOOK_TOOLTIP = "ESO has some hidden book collection and hide some book from you in the lore library. For example the full motif book containing all 14 pages are part of these hidden books. So, if you want to have your book count matching between the lore library and Librarian, you need to uncheck this",
     LIBRARIAN_SETTINGS_UNREAD_INDICATOR_READER = "Unread Indicator (Reader)",
     LIBRARIAN_SETTINGS_UNREAD_INDICATOR_READER_TOOLTIP = "Show an unread indicator next to the title of a book when reading it.",
     LIBRARIAN_SETTINGS_ICON_TRANSPARENCY = "Indicator Transparency",
diff --git a/lang/fr.lua b/lang/fr.lua
index 8daab2b..aa8c72b 100644
--- a/lang/fr.lua
+++ b/lang/fr.lua
@@ -25,6 +25,8 @@ SafeAddString(LIBRARIAN_SETTINGS_ALERT,								"Options d'alert", 1)
 SafeAddString(LIBRARIAN_SETTINGS_ALERT_TOOLTIP,						"Comment souhaitez-vous être alerté ?", 1)
 SafeAddString(LIBRARIAN_SETTINGS_RELOADUI_REMINDER,					"Rappel 'ReloadUI' après", 1)
 SafeAddString(LIBRARIAN_SETTINGS_RELOADUI_REMINDER_TOOLTIP,			"Rappel pour lancer la commande /reloadui après que ce nombre de livre ait été découverts.", 1)
+SafeAddString(LIBRARIAN_SETTINGS_SHOW_HIDDEN_BOOK, 					"Montrer les livres cachés", 1)
+SafeAddString(LIBRARIAN_SETTINGS_SHOW_HIDDEN_BOOK_TOOLTIP, 			"TESO cache certaines collections de livres dans la bibliothèque. Par example les livres contenant le motif complet (au lieu des 14 pages) font parti de ces livres cachés. Donc, si vous souhaitez que le nombre de livre affiché dans Librarian corresponde à celui de la Bibliothèque, vous devez décocher cette case.", 1)
 SafeAddString(LIBRARIAN_SETTINGS_UNREAD_INDICATOR_READER,			"Icone 'Non-lu' (Liseuse)", 1)
 SafeAddString(LIBRARIAN_SETTINGS_UNREAD_INDICATOR_READER_TOOLTIP,	"Affiche une icone 'Non-lu' à côté du titre lors de la lecture d'un livre.", 1)
 SafeAddString(LIBRARIAN_SETTINGS_ICON_TRANSPARENCY,					"Transparence de l'icone", 1)