From c230e525e4b1241093a7138ac96ec624c7d46ca3 Mon Sep 17 00:00:00 2001 From: natlibfi-ptalosel <117168919+natlibfi-ptalosel@users.noreply.github.com> Date: Thu, 29 Oct 2026 15:21:16 +0200 Subject: [PATCH 01/18] EKIRJASTO-987 Remove TenPrintCover header file reference --- Palace/AppInfrastructure/Palace-Bridging-Header.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Palace/AppInfrastructure/Palace-Bridging-Header.h b/Palace/AppInfrastructure/Palace-Bridging-Header.h index 4e37b7b3..9a03059d 100644 --- a/Palace/AppInfrastructure/Palace-Bridging-Header.h +++ b/Palace/AppInfrastructure/Palace-Bridging-Header.h @@ -35,7 +35,6 @@ #import "UIFont+TPPSystemFontOverride.h" #import "UIView+TPPViewAdditions.h" #import "TPPEncryptedPDFDataProvider.h" -#import "NYPLTenPrintCoverView.h" #import "TPPBookCellDelegate.h" #import "TPPOpenSearchDescription.h" #import "TPPCatalogSearchViewController.h" From 1bf7da2150c2db5b18055e4b65a516d89064bdd4 Mon Sep 17 00:00:00 2001 From: natlibfi-ptalosel <117168919+natlibfi-ptalosel@users.noreply.github.com> Date: Thu, 29 Oct 2026 15:22:10 +0200 Subject: [PATCH 02/18] EKIRJASTO-987 Remove TenPrintCover header search path --- Palace.xcconfig | 2 +- Palace.xcodeproj/project.pbxproj | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/Palace.xcconfig b/Palace.xcconfig index c0d27400..c070d3b2 100644 --- a/Palace.xcconfig +++ b/Palace.xcconfig @@ -1,4 +1,4 @@ -HEADER_SEARCH_PATHS = $(inherited) "$(SDKROOT)/usr/include/libxml2" "$(SRCROOT)/readium-sdk/Platform/Apple/RDServices" "$(SRCROOT)/readium-sdk/Platform/Apple/include" "$(SRCROOT)/ios-tenprintcover/TenPrintCover" "$(SRCROOT)/ios-audiobook-overdrive/OverdriveProcessor" "$(SRCROOT)/adept-ios" "$(SRCROOT)/adobe-content-filter/public" "$(SRCROOT)/adobe-rmsdk/dp/public" "$(SRCROOT)/adobe-rmsdk/xml/uft/public" +HEADER_SEARCH_PATHS = $(inherited) "$(SDKROOT)/usr/include/libxml2" "$(SRCROOT)/readium-sdk/Platform/Apple/RDServices" "$(SRCROOT)/readium-sdk/Platform/Apple/include" "$(SRCROOT)/ios-audiobook-overdrive/OverdriveProcessor" "$(SRCROOT)/adept-ios" "$(SRCROOT)/adobe-content-filter/public" "$(SRCROOT)/adobe-rmsdk/dp/public" "$(SRCROOT)/adobe-rmsdk/xml/uft/public" WARNING_CFLAGS = -Wall -Wextra -Weverything -Wno-objc-missing-property-synthesis -Wno-c11-extensions -Wno-documentation -Wno-c++98-compat-pedantic -Wno-c++98-compat diff --git a/Palace.xcodeproj/project.pbxproj b/Palace.xcodeproj/project.pbxproj index 4069c8f0..0e76521a 100644 --- a/Palace.xcodeproj/project.pbxproj +++ b/Palace.xcodeproj/project.pbxproj @@ -6142,7 +6142,6 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "\"$(SDKROOT)/usr/include/libxml2\"", - "\"$(SRCROOT)/ios-tenprintcover/TenPrintCover\"", "\"$(SRCROOT)/ios-audiobook-overdrive/OverdriveProcessor\"", "\"$(SRCROOT)/adept-ios\"", "\"$(SRCROOT)/adobe-content-filter/public\"", @@ -6454,7 +6453,6 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "\"$(SDKROOT)/usr/include/libxml2\"", - "\"$(SRCROOT)/ios-tenprintcover/TenPrintCover\"", "\"$(SRCROOT)/ios-audiobook-overdrive/OverdriveProcessor\"", "\"$(SRCROOT)/adept-ios\"", "\"$(SRCROOT)/adobe-content-filter/public\"", @@ -6766,7 +6764,6 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "\"$(SDKROOT)/usr/include/libxml2\"", - "\"$(SRCROOT)/ios-tenprintcover/TenPrintCover\"", "\"$(SRCROOT)/ios-audiobook-overdrive/OverdriveProcessor\"", "\"$(SRCROOT)/adept-ios\"", "\"$(SRCROOT)/adobe-content-filter/public\"", @@ -7078,7 +7075,6 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "\"$(SDKROOT)/usr/include/libxml2\"", - "\"$(SRCROOT)/ios-tenprintcover/TenPrintCover\"", "\"$(SRCROOT)/ios-audiobook-overdrive/OverdriveProcessor\"", "\"$(SRCROOT)/adept-ios\"", "\"$(SRCROOT)/adobe-content-filter/public\"", @@ -7380,7 +7376,6 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "\"$(SDKROOT)/usr/include/libxml2\"", - "\"$(SRCROOT)/ios-tenprintcover/TenPrintCover\"", "\"$(SRCROOT)/ios-audiobook-overdrive/OverdriveProcessor\"", "\"$(SRCROOT)/adept-ios\"", "\"$(SRCROOT)/adobe-content-filter/public\"", @@ -7681,7 +7676,6 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "\"$(SDKROOT)/usr/include/libxml2\"", - "\"$(SRCROOT)/ios-tenprintcover/TenPrintCover\"", "\"$(SRCROOT)/ios-audiobook-overdrive/OverdriveProcessor\"", "\"$(SRCROOT)/adept-ios\"", "\"$(SRCROOT)/adobe-content-filter/public\"", @@ -7982,7 +7976,6 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "\"$(SDKROOT)/usr/include/libxml2\"", - "\"$(SRCROOT)/ios-tenprintcover/TenPrintCover\"", "\"$(SRCROOT)/ios-audiobook-overdrive/OverdriveProcessor\"", "\"$(SRCROOT)/adept-ios\"", "\"$(SRCROOT)/adobe-content-filter/public\"", @@ -8283,7 +8276,6 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "\"$(SDKROOT)/usr/include/libxml2\"", - "\"$(SRCROOT)/ios-tenprintcover/TenPrintCover\"", "\"$(SRCROOT)/ios-audiobook-overdrive/OverdriveProcessor\"", "\"$(SRCROOT)/adept-ios\"", "\"$(SRCROOT)/adobe-content-filter/public\"", @@ -8347,7 +8339,6 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "\"$(SDKROOT)/usr/include/libxml2\"", - "\"$(SRCROOT)/ios-tenprintcover/TenPrintCover\"", "\"$(SRCROOT)/ios-audiobook-overdrive/OverdriveProcessor\"", "\"$(SRCROOT)/adept-ios\"", "\"$(SRCROOT)/adobe-content-filter/public\"", @@ -8410,7 +8401,6 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "\"$(SDKROOT)/usr/include/libxml2\"", - "\"$(SRCROOT)/ios-tenprintcover/TenPrintCover\"", "\"$(SRCROOT)/ios-audiobook-overdrive/OverdriveProcessor\"", "\"$(SRCROOT)/adept-ios\"", "\"$(SRCROOT)/adobe-content-filter/public\"", From d059bf0a9cbe873862230e7719345c6b9fc793df Mon Sep 17 00:00:00 2001 From: natlibfi-ptalosel <117168919+natlibfi-ptalosel@users.noreply.github.com> Date: Thu, 29 Oct 2026 15:25:04 +0200 Subject: [PATCH 03/18] EKIRJASTO-987 Remove TenPrintCover target and build references --- Palace.xcodeproj/project.pbxproj | 49 -------------------------------- 1 file changed, 49 deletions(-) diff --git a/Palace.xcodeproj/project.pbxproj b/Palace.xcodeproj/project.pbxproj index 0e76521a..c619e884 100644 --- a/Palace.xcodeproj/project.pbxproj +++ b/Palace.xcodeproj/project.pbxproj @@ -49,7 +49,6 @@ 1111973C19880E8D0014462F /* TPPLinearView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1111973B19880E8D0014462F /* TPPLinearView.m */; }; 1111973F1988226F0014462F /* TPPBookDetailDownloadFailedView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1111973E1988226F0014462F /* TPPBookDetailDownloadFailedView.m */; }; 11119742198827850014462F /* TPPBookDetailDownloadingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 11119741198827850014462F /* TPPBookDetailDownloadingView.m */; }; - 1112A8431A3249B4002B8CC1 /* libTenPrintCover.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1112A81F1A322C53002B8CC1 /* libTenPrintCover.a */; }; 1114A6A1195884CB007507A2 /* TPPCatalogUngroupedFeed.m in Sources */ = {isa = PBXBuildFile; fileRef = 1114A6A0195884CB007507A2 /* TPPCatalogUngroupedFeed.m */; }; 111559ED19B8FA590003BE94 /* TPPXML.m in Sources */ = {isa = PBXBuildFile; fileRef = 111559EC19B8FA590003BE94 /* TPPXML.m */; }; 1120749319D20BF9008203A4 /* TPPBookCellCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1120749219D20BF9008203A4 /* TPPBookCellCollectionViewController.m */; }; @@ -576,7 +575,6 @@ 3312643B2A287F53006BA87D /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84FCD2601B7BA79200BFEDD9 /* CoreLocation.framework */; }; 3312643C2A287F53006BA87D /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A823D814192BABA400B55DE2 /* UIKit.framework */; }; 3312643D2A287F53006BA87D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A823D810192BABA400B55DE2 /* Foundation.framework */; }; - 3312643F2A287F53006BA87D /* libTenPrintCover.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1112A81F1A322C53002B8CC1 /* libTenPrintCover.a */; }; 331264402A287F53006BA87D /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 119503E81993F919009FB788 /* libz.dylib */; }; 331264432A287F53006BA87D /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 119503E61993F914009FB788 /* libxml2.dylib */; }; 331264442A287F53006BA87D /* Minizip.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2198690C267E29B00041369E /* Minizip.xcframework */; }; @@ -979,7 +977,6 @@ 73EB0B3F25821DF4006BC997 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84FCD2601B7BA79200BFEDD9 /* CoreLocation.framework */; }; 73EB0B4025821DF4006BC997 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A823D814192BABA400B55DE2 /* UIKit.framework */; }; 73EB0B4125821DF4006BC997 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A823D810192BABA400B55DE2 /* Foundation.framework */; }; - 73EB0B4225821DF4006BC997 /* libTenPrintCover.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1112A81F1A322C53002B8CC1 /* libTenPrintCover.a */; }; 73EB0B4525821DF4006BC997 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 119503E81993F919009FB788 /* libz.dylib */; }; 73EB0B4625821DF4006BC997 /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 119503E61993F914009FB788 /* libxml2.dylib */; }; 73EB0B5925821DF4006BC997 /* OFL.txt in Resources */ = {isa = PBXBuildFile; fileRef = 84B7A3431B84E8FE00584FB2 /* OFL.txt */; }; @@ -1411,13 +1408,6 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 1112A81E1A322C53002B8CC1 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 1112A8191A322C53002B8CC1 /* TenPrintCover.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = CCAC4FAE195B3DD700D775F5; - remoteInfo = TenPrintCover; - }; 220AB4E72BB16DE100435D59 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = A823D805192BABA400B55DE2 /* Project object */; @@ -1425,13 +1415,6 @@ remoteGlobalIDString = 331262D32A287F53006BA87D; remoteInfo = Ekirjasto; }; - 59D9CC1B25FFDE1B00C89B81 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 1112A8191A322C53002B8CC1 /* TenPrintCover.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 59D9CBF525FFDBC500C89B81; - remoteInfo = TenPrintCoverTests; - }; 5A569A2B1B8351C6003B5B61 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 5A569A261B8351C6003B5B61 /* ADEPT.xcodeproj */; @@ -1588,7 +1571,6 @@ 1111973E1988226F0014462F /* TPPBookDetailDownloadFailedView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPPBookDetailDownloadFailedView.m; sourceTree = ""; }; 11119740198827850014462F /* TPPBookDetailDownloadingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPPBookDetailDownloadingView.h; sourceTree = ""; }; 11119741198827850014462F /* TPPBookDetailDownloadingView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPPBookDetailDownloadingView.m; sourceTree = ""; }; - 1112A8191A322C53002B8CC1 /* TenPrintCover.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = TenPrintCover.xcodeproj; path = "ios-tenprintcover/TenPrintCover.xcodeproj"; sourceTree = ""; }; 1114A69F195884CB007507A2 /* TPPCatalogUngroupedFeed.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPPCatalogUngroupedFeed.h; sourceTree = ""; }; 1114A6A0195884CB007507A2 /* TPPCatalogUngroupedFeed.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPPCatalogUngroupedFeed.m; sourceTree = ""; }; 1114A6AC1958B215007507A2 /* TPPLOG.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = TPPLOG.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; @@ -2244,7 +2226,6 @@ 3312643B2A287F53006BA87D /* CoreLocation.framework in Frameworks */, 3312643C2A287F53006BA87D /* UIKit.framework in Frameworks */, 3312643D2A287F53006BA87D /* Foundation.framework in Frameworks */, - 3312643F2A287F53006BA87D /* libTenPrintCover.a in Frameworks */, 33372CF92AC548FB0001BE2F /* FirebaseCrashlytics in Frameworks */, 331264402A287F53006BA87D /* libz.dylib in Frameworks */, 331264432A287F53006BA87D /* libxml2.dylib in Frameworks */, @@ -2286,7 +2267,6 @@ 73EB0B4025821DF4006BC997 /* UIKit.framework in Frameworks */, E78494992A8E99CF0059B4B0 /* PalaceAudiobookToolkit.framework in Frameworks */, 73EB0B4125821DF4006BC997 /* Foundation.framework in Frameworks */, - 73EB0B4225821DF4006BC997 /* libTenPrintCover.a in Frameworks */, 73EB0B4525821DF4006BC997 /* libz.dylib in Frameworks */, 73EB0B4625821DF4006BC997 /* libxml2.dylib in Frameworks */, 2198695B267E2A440041369E /* Minizip.xcframework in Frameworks */, @@ -2323,7 +2303,6 @@ E7498A8A2A0E7F460037DD93 /* FirebaseDynamicLinks in Frameworks */, A823D811192BABA400B55DE2 /* Foundation.framework in Frameworks */, E7CA1C4E29381068009CC6FD /* SQLite in Frameworks */, - 1112A8431A3249B4002B8CC1 /* libTenPrintCover.a in Frameworks */, 21986922267E29D70041369E /* PureLayout.xcframework in Frameworks */, E795A7822A744F0A00314EC8 /* ULID in Frameworks */, 119503E91993F919009FB788 /* libz.dylib in Frameworks */, @@ -2339,15 +2318,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 1112A81A1A322C53002B8CC1 /* Products */ = { - isa = PBXGroup; - children = ( - 1112A81F1A322C53002B8CC1 /* libTenPrintCover.a */, - 59D9CC1C25FFDE1B00C89B81 /* TenPrintCoverTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; 1183F34D194F775400DC322F /* OPDS */ = { isa = PBXGroup; children = ( @@ -3555,7 +3525,6 @@ E5C1F52927990929005FC6B2 /* OverdriveProcessor.xcodeproj */, E585662026979AC100A5FBD5 /* NYPLAEToolkit.xcodeproj */, 5A569A261B8351C6003B5B61 /* ADEPT.xcodeproj */, - 1112A8191A322C53002B8CC1 /* TenPrintCover.xcodeproj */, A823D816192BABA400B55DE2 /* Palace */, A823D82F192BABA400B55DE2 /* PalaceTests */, A823D80E192BABA400B55DE2 /* Products */, @@ -4229,10 +4198,6 @@ ProductGroup = E7F9BAB92A8E6811001697DE /* Products */; ProjectRef = E553424F293FB23F004FF34A /* PalaceAudiobookToolkit.xcodeproj */; }, - { - ProductGroup = 1112A81A1A322C53002B8CC1 /* Products */; - ProjectRef = 1112A8191A322C53002B8CC1 /* TenPrintCover.xcodeproj */; - }, ); projectRoot = ""; targets = ( @@ -4245,20 +4210,6 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - 1112A81F1A322C53002B8CC1 /* libTenPrintCover.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libTenPrintCover.a; - remoteRef = 1112A81E1A322C53002B8CC1 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 59D9CC1C25FFDE1B00C89B81 /* TenPrintCoverTests.xctest */ = { - isa = PBXReferenceProxy; - fileType = wrapper.cfbundle; - path = TenPrintCoverTests.xctest; - remoteRef = 59D9CC1B25FFDE1B00C89B81 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; 5A569A2C1B8351C6003B5B61 /* libADEPT.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; From 298e1881f2e8935372ed74130827aec5acba69f8 Mon Sep 17 00:00:00 2001 From: natlibfi-ptalosel <117168919+natlibfi-ptalosel@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:13:10 +0200 Subject: [PATCH 04/18] EKIRJASTO-987 Remove TenPrintCover test target --- .../xcshareddata/xcschemes/Palace.xcscheme | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Palace.xcodeproj/xcshareddata/xcschemes/Palace.xcscheme b/Palace.xcodeproj/xcshareddata/xcschemes/Palace.xcscheme index 0d7185c3..227cc567 100644 --- a/Palace.xcodeproj/xcshareddata/xcschemes/Palace.xcscheme +++ b/Palace.xcodeproj/xcshareddata/xcschemes/Palace.xcscheme @@ -59,19 +59,6 @@ ReferencedContainer = "container:Palace.xcodeproj"> - - - - - - Date: Thu, 29 Oct 2026 16:17:38 +0200 Subject: [PATCH 05/18] EKIRJASTO-987 Remove TenPrintCover metadata from project source control --- .../xcshareddata/Simplified.xcscmblueprint | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Palace.xcodeproj/project.xcworkspace/xcshareddata/Simplified.xcscmblueprint b/Palace.xcodeproj/project.xcworkspace/xcshareddata/Simplified.xcscmblueprint index d2ecca07..3fc7ac3a 100644 --- a/Palace.xcodeproj/project.xcworkspace/xcshareddata/Simplified.xcscmblueprint +++ b/Palace.xcodeproj/project.xcworkspace/xcshareddata/Simplified.xcscmblueprint @@ -7,7 +7,6 @@ "AD4E54206183D886987A2A3031947817F2F202B6" : 0, "306FC097E980A949FBFF7EC996125BA9F20AEF86" : 0, "CA647A59556D3849BD27CCFE9423C80ECF6C0F48" : 0, - "E375BEBE2A272874C7869C33EC6726F091440FD9" : 0, "FD8A948335A628DCE5C35F1A302BBE858DD773F0" : 0, "7EA574B7954BB140EB4FDBD252E584FCDFCE648C" : 0, "06CFE79786291A98E71F9F1158D5AAE92B0D88ED" : 0 @@ -17,7 +16,6 @@ "AD4E54206183D886987A2A3031947817F2F202B6" : "Simplified-iOS\/readium-shared-js\/", "306FC097E980A949FBFF7EC996125BA9F20AEF86" : "Simplified-iOS\/", "CA647A59556D3849BD27CCFE9423C80ECF6C0F48" : "Simplified-iOS\/adobe-content-filter\/", - "E375BEBE2A272874C7869C33EC6726F091440FD9" : "Simplified-iOS\/tenprintcover-ios\/", "FD8A948335A628DCE5C35F1A302BBE858DD773F0" : "Simplified-iOS\/preloaded-content\/", "7EA574B7954BB140EB4FDBD252E584FCDFCE648C" : "Simplified-iOS\/readium-sdk\/", "06CFE79786291A98E71F9F1158D5AAE92B0D88ED" : "Simplified-iOS\/adept-ios\/" @@ -51,15 +49,10 @@ "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "CA647A59556D3849BD27CCFE9423C80ECF6C0F48" }, - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:NYPL\/tenprintcover-ios.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "E375BEBE2A272874C7869C33EC6726F091440FD9" - }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:NYPL-Simplified\/preloaded-content.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "FD8A948335A628DCE5C35F1A302BBE858DD773F0" } ] -} \ No newline at end of file +} From d3dae207d9d0e3bff291729df3fdd7cb00fcea61 Mon Sep 17 00:00:00 2001 From: natlibfi-ptalosel <117168919+natlibfi-ptalosel@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:04:00 +0200 Subject: [PATCH 06/18] EKIRJASTO-987 Remove ios-tenprintcover git submodule --- .gitmodules | 3 --- ios-tenprintcover | 1 - 2 files changed, 4 deletions(-) delete mode 160000 ios-tenprintcover diff --git a/.gitmodules b/.gitmodules index 5e518e04..ed5613d1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "ios-tenprintcover"] - path = ios-tenprintcover - url = https://github.com/ThePalaceProject/ios-tenprintcover.git [submodule "mobile-bookmark-spec"] path = mobile-bookmark-spec url = https://github.com/ThePalaceProject/mobile-bookmark-spec.git diff --git a/ios-tenprintcover b/ios-tenprintcover deleted file mode 160000 index 3eba1fb2..00000000 --- a/ios-tenprintcover +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3eba1fb24806f2da32654d63290bb99c0edd2c80 From c1b6ca03f0b234fec5351035e85b1d4a466ce383 Mon Sep 17 00:00:00 2001 From: natlibfi-ptalosel <117168919+natlibfi-ptalosel@users.noreply.github.com> Date: Thu, 29 Oct 2026 15:27:43 +0200 Subject: [PATCH 07/18] EKIRJASTO-987 Remove TenPrintCover mention from software license list --- Palace/software-licenses.html | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Palace/software-licenses.html b/Palace/software-licenses.html index 72438342..13d07e2c 100644 --- a/Palace/software-licenses.html +++ b/Palace/software-licenses.html @@ -66,10 +66,5 @@

Readium

Modules: readium-sdk, readium-shared-js, r2-shared-swift, r2-streamer-swift, r2-lcp-swift, r2-navigator-swift.

This code is distributed under the terms and conditions of the BSD 3-Clause License.

-

TenPrintCover

-

Copyright (c) 2014 Mauricio Giraldo ( - https://github.com/ThePalaceProject/ios-tenprintcover)

-

This code is distributed under the terms and conditions of the MIT license.

- From be8ec3b39e30ab1c48f67365886d63a3206020bb Mon Sep 17 00:00:00 2001 From: natlibfi-ptalosel <117168919+natlibfi-ptalosel@users.noreply.github.com> Date: Thu, 29 Oct 2026 15:12:15 +0200 Subject: [PATCH 08/18] EKIRJASTO-987 Use placeholder image for missing book cover --- .../Registry/TPPBookCoverRegistry.swift | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift index 0f927547..a3a8f21b 100644 --- a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift +++ b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift @@ -31,7 +31,7 @@ class TPPBookCoverRegistry { if let thumbnailUrl = book.imageThumbnailURL { self.getBookCoverImage(url: thumbnailUrl, fileUrl: nil, handler: handler, forBook: book) } else { - handler(self.generateBookCoverImage(book)) + handler(self.getPlaceholderBookCoverImage()) } } @@ -84,7 +84,7 @@ class TPPBookCoverRegistry { _books.forEach { book in guard let thumbnailUrl = book.imageThumbnailURL else { DispatchQueue.main.async { - result[book.identifier] = self.generateBookCoverImage(book) + result[book.identifier] = self.getPlaceholderBookCoverImage() } return @@ -94,7 +94,7 @@ class TPPBookCoverRegistry { result[book.identifier] = image } else { DispatchQueue.main.async { - result[book.identifier] = self.generateBookCoverImage(book) + result[book.identifier] = self.getPlaceholderBookCoverImage() } } @@ -223,26 +223,14 @@ class TPPBookCoverRegistry { } } else { DispatchQueue.main.async { - handler(self.generateBookCoverImage(book)) + handler(self.getPlaceholderBookCoverImage()) } } }.resume() } - /// Generates TenPrintCover cover for books. - /// - Parameter book: `TPPBook` object. - /// - Returns: cover image - private func generateBookCoverImage(_ book: TPPBook) -> UIImage? { - var image: UIImage? - let width: CGFloat = 80 - let height: CGFloat = 120 - let coverView = NYPLTenPrintCoverView(frame: CGRect(x: 0, y: 0, width: width, height: height), withTitle: book.title, withAuthor: book.authors, withScale: 0.4) - UIGraphicsBeginImageContextWithOptions(CGSize(width: width, height: height), true, 0) - if let context = UIGraphicsGetCurrentContext() { - coverView?.layer.render(in: context) - image = UIGraphicsGetImageFromCurrentImageContext() - } - UIGraphicsEndImageContext() - return image + /// Returns the placeholder image used when a book has no available cover. + private func getPlaceholderBookCoverImage() -> UIImage? { + ImageProviders.MyBooksView.bookPlaceholder } } From 908788a2ed734ceb065ca475e44322e47e0cfa30 Mon Sep 17 00:00:00 2001 From: natlibfi-ptalosel <117168919+natlibfi-ptalosel@users.noreply.github.com> Date: Thu, 29 Oct 2026 15:16:09 +0200 Subject: [PATCH 09/18] EKIRJASTO-987 Use placeholder image for missing cached book cover --- Palace/Book/Models/Registry/TPPBookCoverRegistry.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift index a3a8f21b..3929fb2d 100644 --- a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift +++ b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift @@ -16,7 +16,7 @@ class TPPBookCoverRegistry { /// - handler: completion handler. func thumbnailImageForBook(_ book: TPPBook, handler: @escaping (_ image: UIImage?) -> Void) { guard let imagePath = pinnedThumbnailImageUrlOfBookIdentifier(book.identifier)?.path else { - handler(nil) + handler(self.getPlaceholderBookCoverImage()) return } let isPinned = TPPBookRegistry.shared.book(forIdentifier: book.identifier) != nil @@ -26,6 +26,8 @@ class TPPBookCoverRegistry { } else if let thumbnailUrl = book.imageThumbnailURL, let fileUrl = self.pinnedThumbnailImageUrlOfBookIdentifier(book.identifier) { self.getBookCoverImage(url: thumbnailUrl, fileUrl: fileUrl, handler: handler, forBook: book) + } else { + handler(self.getPlaceholderBookCoverImage()) } } else { if let thumbnailUrl = book.imageThumbnailURL { From 22188758a207ed5ccc57d287b6ba46e057c1cdc1 Mon Sep 17 00:00:00 2001 From: natlibfi-ptalosel <117168919+natlibfi-ptalosel@users.noreply.github.com> Date: Thu, 29 Oct 2026 17:29:30 +0200 Subject: [PATCH 10/18] EKIRJASTO-987 Define the image to be used as placeholder --- Palace/Utilities/SwiftUI/ImageProvider.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Palace/Utilities/SwiftUI/ImageProvider.swift b/Palace/Utilities/SwiftUI/ImageProvider.swift index 5a8f7947..7c01ebaf 100644 --- a/Palace/Utilities/SwiftUI/ImageProvider.swift +++ b/Palace/Utilities/SwiftUI/ImageProvider.swift @@ -18,7 +18,7 @@ struct ImageProviders { } struct MyBooksView { - static let bookPlaceholder = UIImage(systemName: "book.closed.fill") + static let bookPlaceholder = UIImage(named: "NoCover") static let audiobookBadge = Image("AudiobookBadge") static let unreadBadge = Image("Unread") static let clock = Image("Clock") From 4b5685c0e54346a66c0494bfd1ce82a5715a7a32 Mon Sep 17 00:00:00 2001 From: natlibfi-ptalosel <117168919+natlibfi-ptalosel@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:47:11 +0200 Subject: [PATCH 11/18] EKIRJASTO-987 Test for using placeholder image if book cover is missing --- Palace.xcodeproj/project.pbxproj | 4 ++ PalaceTests/TPPBookCoverRegistryTests.swift | 51 +++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 PalaceTests/TPPBookCoverRegistryTests.swift diff --git a/Palace.xcodeproj/project.pbxproj b/Palace.xcodeproj/project.pbxproj index c619e884..ac3472d9 100644 --- a/Palace.xcodeproj/project.pbxproj +++ b/Palace.xcodeproj/project.pbxproj @@ -1027,6 +1027,7 @@ 7566BEA12EA7C612009EEF9C /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7566BEA02EA7C604009EEF9C /* ToastView.swift */; }; 7566BEA22EA7C612009EEF9C /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7566BEA02EA7C604009EEF9C /* ToastView.swift */; }; 7566BEA32EA7C612009EEF9C /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7566BEA02EA7C604009EEF9C /* ToastView.swift */; }; + 7577C6FF30939308009C595A /* TPPBookCoverRegistryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7577C6FE30939308009C595A /* TPPBookCoverRegistryTests.swift */; }; 7582FFD02DB9188A0057AC71 /* BookSelectionButtonsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7582FFCF2DB918880057AC71 /* BookSelectionButtonsView.m */; }; 7582FFD12DB9188A0057AC71 /* BookSelectionButtonsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7582FFCF2DB918880057AC71 /* BookSelectionButtonsView.m */; }; 7582FFD22DB9188A0057AC71 /* BookSelectionButtonsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7582FFCF2DB918880057AC71 /* BookSelectionButtonsView.m */; }; @@ -1928,6 +1929,7 @@ 7557C4332E8AF69E00181B79 /* UserNotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationService.swift; sourceTree = ""; }; 7566BE9C2EA7C5F8009EEF9C /* ToastService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastService.swift; sourceTree = ""; }; 7566BEA02EA7C604009EEF9C /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; + 7577C6FE30939308009C595A /* TPPBookCoverRegistryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TPPBookCoverRegistryTests.swift; sourceTree = ""; }; 7582FFCE2DB918630057AC71 /* BookSelectionButtonsView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BookSelectionButtonsView.h; sourceTree = ""; }; 7582FFCF2DB918880057AC71 /* BookSelectionButtonsView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BookSelectionButtonsView.m; sourceTree = ""; }; 7582FFD32DBADF240057AC71 /* BookSelectionButtonsState.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BookSelectionButtonsState.h; sourceTree = ""; }; @@ -3685,6 +3687,7 @@ E5F8A57C28A48B8300A229AE /* BookPreviewTests.swift */, E534510429FA238500D88BC6 /* AudiobookmarkTests.swift */, E5024A8E2A0EB9D6006BF653 /* AudiobookBookmarkBusinessLogicTests.swift */, + 7577C6FE30939308009C595A /* TPPBookCoverRegistryTests.swift */, ); path = PalaceTests; sourceTree = ""; @@ -4592,6 +4595,7 @@ 735771B12537691800067CEA /* TPPDRMAuthorizingMock.swift in Sources */, E5F8A57D28A48B8300A229AE /* BookPreviewTests.swift in Sources */, 730EF263260955EF008E1DC3 /* TPPBookmarkSpecTests.swift in Sources */, + 7577C6FF30939308009C595A /* TPPBookCoverRegistryTests.swift in Sources */, B51C1DFE22860563003B49A5 /* OPDS2CatalogsFeedTests.swift in Sources */, E5A0CEA42AA9855200C304EC /* TPPBookMock.swift in Sources */, 73A794BD2548E36100C59CC1 /* TPPBookCreationTests.swift in Sources */, diff --git a/PalaceTests/TPPBookCoverRegistryTests.swift b/PalaceTests/TPPBookCoverRegistryTests.swift new file mode 100644 index 00000000..f555ff68 --- /dev/null +++ b/PalaceTests/TPPBookCoverRegistryTests.swift @@ -0,0 +1,51 @@ +// +// TPPBookCoverRegistryTests.swift +// PalaceTests +// +// Tests for book cover fallback behavior. +// + +import XCTest +@testable import Palace + +final class TPPBookCoverRegistryTests: XCTestCase { + func testThumbnailImageForBookWithoutThumbnailUsesPlaceholder() { + let book = TPPBook( + acquisitions: [], + authors: nil, + bookAccessibility: nil, + categoryStrings: [], + distributor: nil, + identifier: UUID().uuidString, + imageURL: nil, + imageThumbnailURL: nil, + published: nil, + publisher: nil, + subtitle: nil, + summary: nil, + title: "Book without a cover", + updated: Date(), + annotationsURL: nil, + analyticsURL: nil, + alternateURL: nil, + relatedWorksURL: nil, + previewLink: nil, + seriesURL: nil, + revokeURL: nil, + reportURL: nil, + timeTrackingURL: nil, + contributors: nil, + bookDuration: nil, + language: nil + ) + let expectedPlaceholder = ImageProviders.MyBooksView.bookPlaceholder + let expectation = expectation(description: "Placeholder image is returned") + + TPPBookCoverRegistry().thumbnailImageForBook(book) { image in + XCTAssertEqual(image?.pngData(), expectedPlaceholder?.pngData()) + expectation.fulfill() + } + + wait(for: [expectation], timeout: 1.0) + } +} From 8cada47d7498163d652626ca9045f6e7f5d22baf Mon Sep 17 00:00:00 2001 From: natlibfi-ptalosel <117168919+natlibfi-ptalosel@users.noreply.github.com> Date: Thu, 29 Oct 2026 18:44:13 +0200 Subject: [PATCH 12/18] EKIRJASTO-987 Format file TPPBookCoverRegistry.swift --- .../Registry/TPPBookCoverRegistry.swift | 193 +++++++++++++----- 1 file changed, 146 insertions(+), 47 deletions(-) diff --git a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift index 3929fb2d..bda5e960 100644 --- a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift +++ b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift @@ -9,59 +9,100 @@ import Foundation class TPPBookCoverRegistry { - + /// Downloads or returns cached cover image for the provided book. /// - Parameters: /// - book: `TPPBook` object. /// - handler: completion handler. - func thumbnailImageForBook(_ book: TPPBook, handler: @escaping (_ image: UIImage?) -> Void) { - guard let imagePath = pinnedThumbnailImageUrlOfBookIdentifier(book.identifier)?.path else { + func thumbnailImageForBook( + _ book: TPPBook, + handler: @escaping (_ image: UIImage?) -> Void + ) { + + guard + let imagePath = pinnedThumbnailImageUrlOfBookIdentifier(book.identifier)? + .path + else { handler(self.getPlaceholderBookCoverImage()) return } - let isPinned = TPPBookRegistry.shared.book(forIdentifier: book.identifier) != nil + + let isPinned = + TPPBookRegistry.shared.book(forIdentifier: book.identifier) != nil + if isPinned { + if let image = UIImage(contentsOfFile: imagePath) { handler(image) } else if let thumbnailUrl = book.imageThumbnailURL, - let fileUrl = self.pinnedThumbnailImageUrlOfBookIdentifier(book.identifier) { - self.getBookCoverImage(url: thumbnailUrl, fileUrl: fileUrl, handler: handler, forBook: book) + let fileUrl = self.pinnedThumbnailImageUrlOfBookIdentifier( + book.identifier + ) + { + self.getBookCoverImage( + url: thumbnailUrl, + fileUrl: fileUrl, + handler: handler, + forBook: book + ) } else { handler(self.getPlaceholderBookCoverImage()) } + } else { + if let thumbnailUrl = book.imageThumbnailURL { - self.getBookCoverImage(url: thumbnailUrl, fileUrl: nil, handler: handler, forBook: book) + self.getBookCoverImage( + url: thumbnailUrl, + fileUrl: nil, + handler: handler, + forBook: book + ) } else { handler(self.getPlaceholderBookCoverImage()) } + } - + } - + /// Downloads cover image for the provided book. /// - Parameters: /// - book: `TPPBook` object. /// - handler: completion handler. - func coverImageForBook(_ book: TPPBook, handler: @escaping (_ image: UIImage?) -> Void) { + func coverImageForBook( + _ book: TPPBook, + handler: @escaping (_ image: UIImage?) -> Void + ) { + thumbnailImageForBook(book, handler: handler) + guard let imageUrl = book.imageURL else { return } - urlSession.dataTask(with: URLRequest(url: imageUrl)) { imageData, response, error in + + urlSession.dataTask(with: URLRequest(url: imageUrl)) { + imageData, + response, + error in if let imageData = imageData, let image = UIImage(data: imageData) { DispatchQueue.main.async { handler(image) } } }.resume() + } - + /// Downloads or creates book covers for the provided set of books. /// - Parameters: /// - books: A set of `TPPBook` objects. /// - handler: completion handler. `handler()` is called once, after all covers are downloaded. - func thumbnailImagesForBooks(_ books: Set, handler: @escaping (_ bookIdentifiersToImages: [String: UIImage]) -> Void) { + func thumbnailImagesForBooks( + _ books: Set, + handler: @escaping (_ bookIdentifiersToImages: [String: UIImage]) -> Void + ) { + var result: [String: UIImage] = [:] { didSet { if _books.count == result.keys.count { @@ -71,9 +112,9 @@ class TPPBookCoverRegistry { } } } - - var _books : [TPPBook] = [] - + + var _books: [TPPBook] = [] + for b in books { if !_books.contains(where: { book in book.identifier == b.identifier @@ -82,8 +123,8 @@ class TPPBookCoverRegistry { } } - _books.forEach { book in + guard let thumbnailUrl = book.imageThumbnailURL else { DispatchQueue.main.async { result[book.identifier] = self.getPlaceholderBookCoverImage() @@ -91,79 +132,110 @@ class TPPBookCoverRegistry { return } - urlSession.dataTask(with: URLRequest(url: thumbnailUrl)) { imageData, response, error in + + urlSession.dataTask(with: URLRequest(url: thumbnailUrl)) { + imageData, + response, + error in if let imageData = imageData, let image = UIImage(data: imageData) { result[book.identifier] = image } else { DispatchQueue.main.async { result[book.identifier] = self.getPlaceholderBookCoverImage() } - + } }.resume() - + } + } - + /// Immediately returns the cached thumbnail if available, else nil. Generated images are not returned. /// - Parameter book: book /// - Returns: cover image, if one is available. func cachedThumbnailImageForBook(_ book: TPPBook) -> UIImage? { + guard let thumbnailUrl = book.imageThumbnailURL, - let cachedData = urlSession.configuration.urlCache?.cachedResponse(for: URLRequest(url: thumbnailUrl))?.data + let cachedData = urlSession.configuration.urlCache?.cachedResponse( + for: URLRequest(url: thumbnailUrl) + )?.data else { return nil } + return UIImage(data: cachedData) } - + /// Saves cover image of the book. /// - Parameter book: `TPPBook` object. func pinThumbnailImageForBook(_ book: TPPBook) { + guard let thumbnailUrl = book.imageThumbnailURL, - let fileUrl = pinnedThumbnailImageUrlOfBookIdentifier(book.identifier) + let fileUrl = pinnedThumbnailImageUrlOfBookIdentifier(book.identifier) else { return } + try? Data().write(to: fileUrl, options: .atomic) - urlSession.dataTask(with: URLRequest(url: thumbnailUrl)) { imageData, response, error in + + urlSession.dataTask(with: URLRequest(url: thumbnailUrl)) { + imageData, + response, + error in if let imageData = imageData { do { try imageData.write(to: fileUrl, options: .atomic) } catch { - Log.error(#file, "Error saving thumbnail file: \(error.localizedDescription)") + Log.error( + #file, + "Error saving thumbnail file: \(error.localizedDescription)" + ) } } }.resume() + } - + /// Deletes cover image of a book with `bookIdentifier`. /// - Parameter bookIdentifier: book identifier. func removePinnedThumbnailImageForBookIdentifier(_ bookIdentifier: String) { - guard let fileUrl = pinnedThumbnailImageUrlOfBookIdentifier(bookIdentifier) else { + + guard let fileUrl = pinnedThumbnailImageUrlOfBookIdentifier(bookIdentifier) + else { return } + do { try FileManager.default.removeItem(at: fileUrl) } catch { - Log.error(#file, "Error removeing thumbnail file: \(error.localizedDescription)") + Log.error( + #file, + "Error removeing thumbnail file: \(error.localizedDescription)" + ) } + } - + /// Deletes book cover directory func removeAllPinnedThumbnailImages() { + do { if let url = pinnedThumbnailImageDirectoryURL { try FileManager.default.removeItem(at: url) } } catch { - Log.error(#file, "Error removing thumbnail directory: \(error.localizedDescription)") + Log.error( + #file, + "Error removing thumbnail directory: \(error.localizedDescription)" + ) } + } - + private lazy var urlSession: URLSession = { - let diskCacheInMegabytes = 16; - let memoryCacheInMegabytes = 2; + let diskCacheInMegabytes = 16 + let memoryCacheInMegabytes = 2 let configuration = URLSessionConfiguration.default configuration.httpCookieStorage = nil @@ -174,47 +246,72 @@ class TPPBookCoverRegistry { configuration.urlCache?.diskCapacity = 1024 * 1024 * diskCacheInMegabytes configuration.urlCache?.memoryCapacity = 1024 * 1024 * memoryCacheInMegabytes configuration.urlCredentialStorage = nil - + return URLSession(configuration: configuration) }() - + /// Directory URL for pinned images private var pinnedThumbnailImageDirectoryURL: URL? { - guard let accountsDirectoryUrl = TPPBookContentMetadataFilesHelper - .currentAccountDirectory() else { + + guard let accountsDirectoryUrl = + TPPBookContentMetadataFilesHelper.currentAccountDirectory() + else { Log.error(#file, "currentAccountDirectory is nil") return nil } + do { - let pinnedThumbnailsFolderUrl = accountsDirectoryUrl.appendingPathComponent("pinned-thumbnail-images") + let pinnedThumbnailsFolderUrl = + accountsDirectoryUrl.appendingPathComponent("pinned-thumbnail-images") + if !FileManager.default.fileExists(atPath: pinnedThumbnailsFolderUrl.path) { - try FileManager.default.createDirectory(at: pinnedThumbnailsFolderUrl, withIntermediateDirectories: true) + try FileManager.default.createDirectory( + at: pinnedThumbnailsFolderUrl, + withIntermediateDirectories: true + ) } + return pinnedThumbnailsFolderUrl } catch { Log.error(#file, "Failed to create pinned thumbnail images folder.") } + return nil } - + /// URL for pinned thumbnail image /// - Parameter bookIdentifier: book identifier /// - Returns: cover image file URL - private func pinnedThumbnailImageUrlOfBookIdentifier(_ bookIdentifier: String) -> URL? { - pinnedThumbnailImageDirectoryURL?.appendingPathComponent(bookIdentifier.sha256()) + private func pinnedThumbnailImageUrlOfBookIdentifier(_ bookIdentifier: String) -> URL? + { + + pinnedThumbnailImageDirectoryURL?.appendingPathComponent( + bookIdentifier.sha256() + ) + } - + /// Downloads book cover image. /// - Parameters: /// - url: cover image URL. /// - fileUrl: file URL for storing the cover image. /// - handler: completion handler. /// - book: `TPPBook` object. - private func getBookCoverImage(url: URL, fileUrl: URL?, handler: @escaping (_ image: UIImage?) -> (), forBook book: TPPBook) { - urlSession.dataTask(with: URLRequest(url: url)) { imageData, response, error in + private func getBookCoverImage( + url: URL, + fileUrl: URL?, + handler: @escaping (_ image: UIImage?) -> Void, + forBook book: TPPBook + ) { + + urlSession.dataTask(with: URLRequest(url: url)) { + imageData, + response, + error in if let imageData = imageData, let image = UIImage(data: imageData) { DispatchQueue.main.async { handler(image) + if let fileUrl = fileUrl { do { try imageData.write(to: fileUrl, options: .atomic) @@ -222,6 +319,7 @@ class TPPBookCoverRegistry { // log error } } + } } else { DispatchQueue.main.async { @@ -229,8 +327,9 @@ class TPPBookCoverRegistry { } } }.resume() + } - + /// Returns the placeholder image used when a book has no available cover. private func getPlaceholderBookCoverImage() -> UIImage? { ImageProviders.MyBooksView.bookPlaceholder From 426b4e5ceb6388abb01f5888566562834173317a Mon Sep 17 00:00:00 2001 From: natlibfi-ptalosel <117168919+natlibfi-ptalosel@users.noreply.github.com> Date: Thu, 29 Oct 2026 18:49:04 +0200 Subject: [PATCH 13/18] EKIRJASTO-987 Remove unused parameter from getBookCoverImage function --- .../Book/Models/Registry/TPPBookCoverRegistry.swift | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift index bda5e960..789be4df 100644 --- a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift +++ b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift @@ -42,8 +42,7 @@ class TPPBookCoverRegistry { self.getBookCoverImage( url: thumbnailUrl, fileUrl: fileUrl, - handler: handler, - forBook: book + handler: handler ) } else { handler(self.getPlaceholderBookCoverImage()) @@ -55,8 +54,7 @@ class TPPBookCoverRegistry { self.getBookCoverImage( url: thumbnailUrl, fileUrl: nil, - handler: handler, - forBook: book + handler: handler ) } else { handler(self.getPlaceholderBookCoverImage()) @@ -293,15 +291,13 @@ class TPPBookCoverRegistry { /// Downloads book cover image. /// - Parameters: - /// - url: cover image URL. + /// - url: URL for the books cover image /// - fileUrl: file URL for storing the cover image. /// - handler: completion handler. - /// - book: `TPPBook` object. private func getBookCoverImage( url: URL, fileUrl: URL?, - handler: @escaping (_ image: UIImage?) -> Void, - forBook book: TPPBook + handler: @escaping (_ image: UIImage?) -> Void ) { urlSession.dataTask(with: URLRequest(url: url)) { From a486cf9b76a8d422cc9d5b2140d95a02f52dc4c3 Mon Sep 17 00:00:00 2001 From: natlibfi-ptalosel <117168919+natlibfi-ptalosel@users.noreply.github.com> Date: Thu, 29 Oct 2026 18:59:47 +0200 Subject: [PATCH 14/18] EKIRJASTO-987 Refactor function thumbnailImagesForBooks --- .../Registry/TPPBookCoverRegistry.swift | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift index 789be4df..21f2e0bf 100644 --- a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift +++ b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift @@ -92,17 +92,28 @@ class TPPBookCoverRegistry { } - /// Downloads or creates book covers for the provided set of books. + /// Downloads thumbnail images for the provided set of books. + /// Uses the placeholder image when a thumbnail is unavailable. /// - Parameters: - /// - books: A set of `TPPBook` objects. - /// - handler: completion handler. `handler()` is called once, after all covers are downloaded. + /// - books: The books whose thumbnail images should be loaded, a set of `TPPBook` objects + /// - handler: Called once with an image for each book, completion handler func thumbnailImagesForBooks( _ books: Set, handler: @escaping (_ bookIdentifiersToImages: [String: UIImage]) -> Void ) { + // Complete immediately if there are no books to load. + guard !books.isEmpty else { + handler([:]) + return + } + + // Collect one image for each requested book. + // Note: thread safety should be checked, + // URLSession callbacks may update this dictionary concurrently var result: [String: UIImage] = [:] { didSet { + // Notify the caller after all requested images are available. if _books.count == result.keys.count { DispatchQueue.main.async { handler(result) @@ -113,6 +124,7 @@ class TPPBookCoverRegistry { var _books: [TPPBook] = [] + // Keep only one book for each identifier. for b in books { if !_books.contains(where: { book in book.identifier == b.identifier @@ -121,6 +133,7 @@ class TPPBookCoverRegistry { } } + // Load a thumbnail or use the placeholder when no URL is available. _books.forEach { book in guard let thumbnailUrl = book.imageThumbnailURL else { @@ -131,13 +144,16 @@ class TPPBookCoverRegistry { return } + // Download the thumbnail asynchronously. urlSession.dataTask(with: URLRequest(url: thumbnailUrl)) { imageData, response, error in if let imageData = imageData, let image = UIImage(data: imageData) { + // Store the downloaded image. result[book.identifier] = image } else { + // Use the placeholder when the download or image decoding fails. DispatchQueue.main.async { result[book.identifier] = self.getPlaceholderBookCoverImage() } From 91117a89ec71e5e4965f72370d3f2091a3ff7058 Mon Sep 17 00:00:00 2001 From: natlibfi-ptalosel <117168919+natlibfi-ptalosel@users.noreply.github.com> Date: Thu, 29 Oct 2026 19:51:49 +0200 Subject: [PATCH 15/18] EKIRJASTO-987 Comment property pinnedThumbnailImageDirectoryURL --- .../Models/Registry/TPPBookCoverRegistry.swift | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift index 21f2e0bf..d89ed1cc 100644 --- a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift +++ b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift @@ -264,9 +264,12 @@ class TPPBookCoverRegistry { return URLSession(configuration: configuration) }() - /// Directory URL for pinned images + /// Returns the directory for locally saved thumbnail images. + /// Creates the directory when it does not exist. + /// This property performs file system I/O and may return nil. private var pinnedThumbnailImageDirectoryURL: URL? { + // resolve the directory for the currently active user account. guard let accountsDirectoryUrl = TPPBookContentMetadataFilesHelper.currentAccountDirectory() else { @@ -275,10 +278,14 @@ class TPPBookCoverRegistry { } do { + // store thumbnails in the current user's local on-device account data, + // the folder name is "pinned-thumbnail-images" let pinnedThumbnailsFolderUrl = accountsDirectoryUrl.appendingPathComponent("pinned-thumbnail-images") - if !FileManager.default.fileExists(atPath: pinnedThumbnailsFolderUrl.path) { + // create the thumbnail directory on first use + if !FileManager.default.fileExists(atPath: pinnedThumbnailsFolderUrl.path) + { try FileManager.default.createDirectory( at: pinnedThumbnailsFolderUrl, withIntermediateDirectories: true @@ -287,7 +294,10 @@ class TPPBookCoverRegistry { return pinnedThumbnailsFolderUrl } catch { - Log.error(#file, "Failed to create pinned thumbnail images folder.") + Log.error( + #file, + "Failed to create pinned thumbnail images folder: \(error.localizedDescription)" + ) } return nil From ee3815d02e306b25fc8e94650de3c92c627a7d4f Mon Sep 17 00:00:00 2001 From: natlibfi-ptalosel <117168919+natlibfi-ptalosel@users.noreply.github.com> Date: Fri, 30 Oct 2026 12:14:49 +0200 Subject: [PATCH 16/18] EKIRJASTO-987 Comment function thumbnailImageForBook --- Palace/Book/Models/Registry/TPPBookCoverRegistry.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift index d89ed1cc..a898ba86 100644 --- a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift +++ b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift @@ -10,10 +10,10 @@ import Foundation class TPPBookCoverRegistry { - /// Downloads or returns cached cover image for the provided book. + /// Returns a cached thumbnail, downloads one when needed, or uses a placeholder. /// - Parameters: - /// - book: `TPPBook` object. - /// - handler: completion handler. + /// - book: The book whose thumbnail image should be loaded, TPPBook object + /// - handler: Called with the cached, downloaded, or placeholder image func thumbnailImageForBook( _ book: TPPBook, handler: @escaping (_ image: UIImage?) -> Void @@ -27,6 +27,7 @@ class TPPBookCoverRegistry { return } + // a pinned book is saved in the user's local book registry. let isPinned = TPPBookRegistry.shared.book(forIdentifier: book.identifier) != nil From e14a71cd1ee1eb4bb805fac24a9569b9eb8d8fb8 Mon Sep 17 00:00:00 2001 From: natlibfi-ptalosel <117168919+natlibfi-ptalosel@users.noreply.github.com> Date: Fri, 30 Oct 2026 12:19:14 +0200 Subject: [PATCH 17/18] EKIRJASTO-987 Log error on saving file --- Palace/Book/Models/Registry/TPPBookCoverRegistry.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift index a898ba86..d819164b 100644 --- a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift +++ b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift @@ -339,7 +339,10 @@ class TPPBookCoverRegistry { do { try imageData.write(to: fileUrl, options: .atomic) } catch { - // log error + Log.error( + #file, + "Error saving thumbnail file: \(error.localizedDescription)" + ) } } From 88086bd7362a138cc1b49733aef5017010a7e04c Mon Sep 17 00:00:00 2001 From: natlibfi-ptalosel <117168919+natlibfi-ptalosel@users.noreply.github.com> Date: Fri, 30 Oct 2026 12:21:02 +0200 Subject: [PATCH 18/18] EKIRJASTO-987 Update comment for cachedThumbnailImageForBook function --- Palace/Book/Models/Registry/TPPBookCoverRegistry.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift index d819164b..520af3ab 100644 --- a/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift +++ b/Palace/Book/Models/Registry/TPPBookCoverRegistry.swift @@ -166,7 +166,8 @@ class TPPBookCoverRegistry { } - /// Immediately returns the cached thumbnail if available, else nil. Generated images are not returned. + /// Immediately returns a thumbnail from the URL cache or nil. + /// Placeholder images are not returned. /// - Parameter book: book /// - Returns: cover image, if one is available. func cachedThumbnailImageForBook(_ book: TPPBook) -> UIImage? {