Generate item excerpts in the background, use lol-html
This commit is contained in:
parent
30ec5e54e0
commit
f8026125cc
|
@ -0,0 +1,3 @@
|
|||
[submodule "lol-html"]
|
||||
path = lol-html
|
||||
url = https://github.com/cloudflare/lol-html.git
|
|
@ -0,0 +1,11 @@
|
|||
# Reader
|
||||
|
||||
In order to build reader you need the appropriate targets added to your Rust toolchain.
|
||||
|
||||
```sh
|
||||
$ rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
|
||||
```
|
||||
|
||||
x86_64-apple-ios is only necessary if you're on an Intel Mac, and aarch-64-apple-ios-sim if you're on Apple Silicon.
|
||||
|
||||
The Xcode build script will take care of actually building the Rust code.
|
|
@ -15,6 +15,7 @@
|
|||
D65B18BC27504FE7004A9448 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18BB27504FE7004A9448 /* Token.swift */; };
|
||||
D65B18BE275051A1004A9448 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18BD275051A1004A9448 /* LocalData.swift */; };
|
||||
D65B18C127505348004A9448 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18C027505348004A9448 /* HomeViewController.swift */; };
|
||||
D68B303627907D9200E8B3FA /* ExcerptGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68B303527907D9200E8B3FA /* ExcerptGenerator.swift */; };
|
||||
D6A8A33427766C2800CCEC72 /* PersistentContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A8A33327766C2800CCEC72 /* PersistentContainer.swift */; };
|
||||
D6C687EC272CD27600874C10 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C687EB272CD27600874C10 /* AppDelegate.swift */; };
|
||||
D6C687EE272CD27600874C10 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C687ED272CD27600874C10 /* SceneDelegate.swift */; };
|
||||
|
@ -41,7 +42,6 @@
|
|||
D6E2435F278B97240005E546 /* Group+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2435B278B97240005E546 /* Group+CoreDataClass.swift */; };
|
||||
D6E24360278B97240005E546 /* Group+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2435C278B97240005E546 /* Group+CoreDataProperties.swift */; };
|
||||
D6E24363278BA1410005E546 /* ItemCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24361278BA1410005E546 /* ItemCollectionViewCell.swift */; };
|
||||
D6E24367278BA2660005E546 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = D6E24366278BA2660005E546 /* SwiftSoup */; };
|
||||
D6E24369278BABB40005E546 /* UIColor+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24368278BABB40005E546 /* UIColor+App.swift */; };
|
||||
D6E2436B278BB1880005E546 /* HomeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2436A278BB1880005E546 /* HomeCollectionViewCell.swift */; };
|
||||
D6E2436E278BD8160005E546 /* ReadViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2436D278BD8160005E546 /* ReadViewController.swift */; };
|
||||
|
@ -98,6 +98,9 @@
|
|||
D65B18BB27504FE7004A9448 /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = "<group>"; };
|
||||
D65B18BD275051A1004A9448 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; };
|
||||
D65B18C027505348004A9448 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = "<group>"; };
|
||||
D68B3032278FDD1A00E8B3FA /* Reader-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Reader-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
D68B303527907D9200E8B3FA /* ExcerptGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExcerptGenerator.swift; sourceTree = "<group>"; };
|
||||
D68B3037279099FD00E8B3FA /* liblolhtml.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = liblolhtml.a; path = "lol-html/c-api/target/aarch64-apple-ios-sim/release/liblolhtml.a"; sourceTree = "<group>"; };
|
||||
D6A8A33327766C2800CCEC72 /* PersistentContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistentContainer.swift; sourceTree = "<group>"; };
|
||||
D6C687E8272CD27600874C10 /* Reader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Reader.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D6C687EB272CD27600874C10 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
|
@ -141,7 +144,6 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D6C68829272CD2BA00874C10 /* Fervor.framework in Frameworks */,
|
||||
D6E24367278BA2660005E546 /* SwiftSoup in Frameworks */,
|
||||
D6E24371278BE1250005E546 /* HTMLEntities in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -199,6 +201,14 @@
|
|||
path = Home;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D68B302E278FDCE200E8B3FA /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D68B3037279099FD00E8B3FA /* liblolhtml.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D6A8A33527766E9300CCEC72 /* CoreData */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -222,6 +232,7 @@
|
|||
D6C6880E272CD27700874C10 /* ReaderUITests */,
|
||||
D6C68824272CD2BA00874C10 /* Fervor */,
|
||||
D6C687E9272CD27600874C10 /* Products */,
|
||||
D68B302E278FDCE200E8B3FA /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
@ -239,12 +250,14 @@
|
|||
D6C687EA272CD27600874C10 /* Reader */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D68B3032278FDD1A00E8B3FA /* Reader-Bridging-Header.h */,
|
||||
D6C687EB272CD27600874C10 /* AppDelegate.swift */,
|
||||
D6C687ED272CD27600874C10 /* SceneDelegate.swift */,
|
||||
D65B18B527504920004A9448 /* FervorController.swift */,
|
||||
D65B18BD275051A1004A9448 /* LocalData.swift */,
|
||||
D6E24368278BABB40005E546 /* UIColor+App.swift */,
|
||||
D6EB531E278E4A7500AD2E61 /* StretchyMenuInteraction.swift */,
|
||||
D68B303527907D9200E8B3FA /* ExcerptGenerator.swift */,
|
||||
D6A8A33527766E9300CCEC72 /* CoreData */,
|
||||
D65B18AF2750468B004A9448 /* Screens */,
|
||||
D6C687F7272CD27700874C10 /* Assets.xcassets */,
|
||||
|
@ -325,6 +338,7 @@
|
|||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D6C68815272CD27700874C10 /* Build configuration list for PBXNativeTarget "Reader" */;
|
||||
buildPhases = (
|
||||
D68B303B2791D2A900E8B3FA /* Compile lol-html c-api */,
|
||||
D6C687E4272CD27600874C10 /* Sources */,
|
||||
D6C687E5272CD27600874C10 /* Frameworks */,
|
||||
D6C687E6272CD27600874C10 /* Resources */,
|
||||
|
@ -337,7 +351,6 @@
|
|||
);
|
||||
name = Reader;
|
||||
packageProductDependencies = (
|
||||
D6E24366278BA2660005E546 /* SwiftSoup */,
|
||||
D6E24370278BE1250005E546 /* HTMLEntities */,
|
||||
);
|
||||
productName = Reader;
|
||||
|
@ -435,7 +448,6 @@
|
|||
);
|
||||
mainGroup = D6C687DF272CD27600874C10;
|
||||
packageReferences = (
|
||||
D6E24365278BA2660005E546 /* XCRemoteSwiftPackageReference "SwiftSoup" */,
|
||||
D6E2436F278BE1250005E546 /* XCRemoteSwiftPackageReference "swift-html-entities" */,
|
||||
);
|
||||
productRefGroup = D6C687E9272CD27600874C10 /* Products */;
|
||||
|
@ -484,6 +496,28 @@
|
|||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
D68B303B2791D2A900E8B3FA /* Compile lol-html c-api */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Compile lol-html c-api";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/bash;
|
||||
shellScript = "pushd \"$PROJECT_DIR/lol-html/c-api/\"\n\nif [ \"$PLATFORM_NAME\" == \"iphonesimulator\" ]; then\n if [ \"$ARCHS\" == \"arm64\" ]; then\n export CARGO_TARGET=\"aarch64-apple-ios-sim\"\n elif [ \"$ARCHS\" == \"x86_64\" ]; then\n export CARGO_TARGET=\"x86_64-apple-ios\"\n else\n echo \"error: unknown value for \\$ARCHS\"\n fi\nelif [ \"$PLATFORM_NAME\" == \"iphoneos\" ]; then\n export CARGO_TARGET=\"aarch64-apple-ios\"\nfi\n\necho \"Building lol-html with CARGO_TARGET: $CARGO_TARGET\"\n\n~/.cargo/bin/cargo build --release --target $CARGO_TARGET\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
D6C687E4272CD27600874C10 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
|
@ -506,6 +540,7 @@
|
|||
D6E24358278B96E40005E546 /* Feed+CoreDataProperties.swift in Sources */,
|
||||
D65B18BE275051A1004A9448 /* LocalData.swift in Sources */,
|
||||
D65B18B22750469D004A9448 /* LoginViewController.swift in Sources */,
|
||||
D68B303627907D9200E8B3FA /* ExcerptGenerator.swift in Sources */,
|
||||
D6E24363278BA1410005E546 /* ItemCollectionViewCell.swift in Sources */,
|
||||
D6E2436E278BD8160005E546 /* ReadViewController.swift in Sources */,
|
||||
D65B18C127505348004A9448 /* HomeViewController.swift in Sources */,
|
||||
|
@ -630,6 +665,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
HEADER_SEARCH_PATHS = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
|
@ -685,6 +721,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
HEADER_SEARCH_PATHS = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
|
@ -705,6 +742,7 @@
|
|||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = ZPBBSK8L8B;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/lol-html/c-api/include/";
|
||||
INFOPLIST_FILE = Reader/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
|
@ -715,10 +753,19 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/lol-html/c-api/target/aarch64-apple-ios-sim/release",
|
||||
);
|
||||
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(PROJECT_DIR)/lol-html/c-api/target/aarch64-apple-ios/release/";
|
||||
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "$(PROJECT_DIR)/lol-html/c-api/target/aarch64-apple-ios-sim/release/";
|
||||
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = "$(PROJECT_DIR)/lol-html/c-api/target/x86_64-apple-ios/release/";
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_LDFLAGS = "-llolhtml";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.Reader;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Reader/Reader-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
|
@ -734,6 +781,7 @@
|
|||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = ZPBBSK8L8B;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/lol-html/c-api/include/";
|
||||
INFOPLIST_FILE = Reader/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
|
@ -744,10 +792,19 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/lol-html/c-api/target/aarch64-apple-ios-sim/release",
|
||||
);
|
||||
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(PROJECT_DIR)/lol-html/c-api/target/aarch64-apple-ios/release/";
|
||||
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "$(PROJECT_DIR)/lol-html/c-api/target/aarch64-apple-ios-sim/release/";
|
||||
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]" = "$(PROJECT_DIR)/lol-html/c-api/target/x86_64-apple-ios/release/";
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_LDFLAGS = "-llolhtml";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.Reader;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Reader/Reader-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
|
@ -762,6 +819,7 @@
|
|||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = ZPBBSK8L8B;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/lol-html/c-api/include/";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.ReaderTests;
|
||||
|
@ -782,6 +840,7 @@
|
|||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = ZPBBSK8L8B;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/lol-html/c-api/include/";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.ReaderTests;
|
||||
|
@ -945,14 +1004,6 @@
|
|||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
D6E24365278BA2660005E546 /* XCRemoteSwiftPackageReference "SwiftSoup" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/scinfu/SwiftSoup";
|
||||
requirement = {
|
||||
kind = upToNextMinorVersion;
|
||||
minimumVersion = 2.3.0;
|
||||
};
|
||||
};
|
||||
D6E2436F278BE1250005E546 /* XCRemoteSwiftPackageReference "swift-html-entities" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/Kitura/swift-html-entities.git";
|
||||
|
@ -964,11 +1015,6 @@
|
|||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
D6E24366278BA2660005E546 /* SwiftSoup */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D6E24365278BA2660005E546 /* XCRemoteSwiftPackageReference "SwiftSoup" */;
|
||||
productName = SwiftSoup;
|
||||
};
|
||||
D6E24370278BE1250005E546 /* HTMLEntities */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D6E2436F278BE1250005E546 /* XCRemoteSwiftPackageReference "swift-html-entities" */;
|
||||
|
|
|
@ -18,6 +18,8 @@ extension Item {
|
|||
|
||||
@NSManaged public var author: String?
|
||||
@NSManaged public var content: String?
|
||||
@NSManaged public var excerpt: String?
|
||||
@NSManaged public var generatedExcerpt: Bool
|
||||
@NSManaged public var id: String?
|
||||
@NSManaged public var needsReadStateSync: Bool
|
||||
@NSManaged public var published: Date?
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
<entity name="Item" representedClassName="Item" syncable="YES">
|
||||
<attribute name="author" optional="YES" attributeType="String"/>
|
||||
<attribute name="content" optional="YES" attributeType="String"/>
|
||||
<attribute name="excerpt" optional="YES" attributeType="String"/>
|
||||
<attribute name="generatedExcerpt" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="needsReadStateSync" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="published" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
|
@ -30,7 +32,7 @@
|
|||
<elements>
|
||||
<element name="Feed" positionX="-54" positionY="9" width="128" height="119"/>
|
||||
<element name="Group" positionX="-63" positionY="-18" width="128" height="74"/>
|
||||
<element name="Item" positionX="-45" positionY="63" width="128" height="164"/>
|
||||
<element name="Item" positionX="-45" positionY="63" width="128" height="194"/>
|
||||
<element name="SyncState" positionX="-63" positionY="90" width="128" height="44"/>
|
||||
</elements>
|
||||
</model>
|
|
@ -0,0 +1,131 @@
|
|||
//
|
||||
// ExcerptGenerator.swift
|
||||
// Reader
|
||||
//
|
||||
// Created by Shadowfacts on 1/13/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import OSLog
|
||||
import CoreData
|
||||
|
||||
// public so that it can be imported in ReaderTests even when Reader is compiled in release mode (w/ testing disabled)
|
||||
public struct ExcerptGenerator {
|
||||
private init() {}
|
||||
|
||||
private static let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ExcerptGenerator")
|
||||
|
||||
static func generateAll(_ fervorController: FervorController) {
|
||||
let req = Item.fetchRequest()
|
||||
req.predicate = NSPredicate(format: "generatedExcerpt = NO")
|
||||
req.sortDescriptors = [NSSortDescriptor(key: "published", ascending: false)]
|
||||
req.fetchBatchSize = 50
|
||||
fervorController.persistentContainer.performBackgroundTask { ctx in
|
||||
guard let items = try? ctx.fetch(req) else { return }
|
||||
var count = 0
|
||||
for item in items {
|
||||
if let excerpt = excerpt(for: item) {
|
||||
item.excerpt = excerpt
|
||||
count += 1
|
||||
if count % 50 == 0 {
|
||||
logger.debug("Generated \(count, privacy: .public) excerpts")
|
||||
}
|
||||
}
|
||||
item.generatedExcerpt = true
|
||||
}
|
||||
logger.log("Generated excerpts for \(count, privacy: .public) items")
|
||||
if ctx.hasChanges {
|
||||
do {
|
||||
// get the updated objects now, because this set is empty after .save is called
|
||||
let updated = ctx.updatedObjects
|
||||
try ctx.save()
|
||||
|
||||
// make sure the view context has the newly added excerpts
|
||||
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: [
|
||||
NSUpdatedObjectsKey: Array(updated)
|
||||
], into: [fervorController.persistentContainer.viewContext])
|
||||
} catch {
|
||||
logger.error("Unable to save context: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func excerpt(for item: Item) -> String? {
|
||||
guard let content = item.content else {
|
||||
return nil
|
||||
}
|
||||
return excerpt(from: content)
|
||||
}
|
||||
|
||||
public static func excerpt(from html: String) -> String? {
|
||||
var html = html
|
||||
|
||||
let builder = lol_html_rewriter_builder_new()!
|
||||
let pSelector = lol_html_selector_parse("p", 1)!
|
||||
var userData = UserData()
|
||||
withUnsafeMutablePointer(to: &userData) { userDataPtr in
|
||||
let rawPtr = UnsafeMutableRawPointer(userDataPtr)
|
||||
let res = lol_html_rewriter_builder_add_element_content_handlers(builder, pSelector, elementHandler, rawPtr, nil, nil, textHandler, rawPtr)
|
||||
guard res == 0 else {
|
||||
lolHtmlError()
|
||||
}
|
||||
let memSettings = lol_html_memory_settings_t(preallocated_parsing_buffer_size: 1024, max_allowed_memory_usage: .max)
|
||||
let rewriter = lol_html_rewriter_build(builder, "utf-8", 5, memSettings, outputSink, nil, true)
|
||||
lol_html_rewriter_builder_free(builder)
|
||||
lol_html_selector_free(pSelector)
|
||||
|
||||
guard let rewriter = rewriter else {
|
||||
lolHtmlError()
|
||||
}
|
||||
|
||||
_ = html.withUTF8 { buffer in
|
||||
buffer.withMemoryRebound(to: CChar.self) { buffer in
|
||||
lol_html_rewriter_write(rewriter, buffer.baseAddress!, buffer.count)
|
||||
}
|
||||
}
|
||||
}
|
||||
if userData.isInParagraph {
|
||||
return userData.paragraphText.htmlUnescape().trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
// todo: steal css whitespace collapsing from tusker
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private static func lolHtmlError() -> Never {
|
||||
let lastError = lol_html_take_last_error()
|
||||
let message = String(bytesNoCopy: UnsafeMutableRawPointer(mutating: lastError.data!), length: lastError.len, encoding: .utf8, freeWhenDone: false)
|
||||
fatalError(message ?? "Unknown lol-html error")
|
||||
}
|
||||
}
|
||||
|
||||
private struct UserData {
|
||||
var isInParagraph = false
|
||||
var paragraphText = ""
|
||||
}
|
||||
|
||||
private func elementHandler(element: OpaquePointer!, userData: UnsafeMutableRawPointer!) -> lol_html_rewriter_directive_t {
|
||||
let userDataPtr = userData.assumingMemoryBound(to: UserData.self)
|
||||
if userDataPtr.pointee.isInParagraph {
|
||||
return LOL_HTML_STOP
|
||||
} else {
|
||||
let s = lol_html_element_tag_name_get(element)
|
||||
let tagName = String(bytesNoCopy: UnsafeMutableRawPointer(mutating: s.data), length: s.len, encoding: .utf8, freeWhenDone: false)!
|
||||
userDataPtr.pointee.isInParagraph = tagName == "p" || tagName == "P"
|
||||
lol_html_str_free(s)
|
||||
return LOL_HTML_CONTINUE
|
||||
}
|
||||
}
|
||||
|
||||
private func textHandler(chunk: OpaquePointer!, userData: UnsafeMutableRawPointer!) -> lol_html_rewriter_directive_t {
|
||||
let userDataPtr = userData.assumingMemoryBound(to: UserData.self)
|
||||
let s = lol_html_text_chunk_content_get(chunk)
|
||||
let content = String(bytesNoCopy: UnsafeMutableRawPointer(mutating: s.data), length: s.len, encoding: .utf8, freeWhenDone: false)!
|
||||
userDataPtr.pointee.paragraphText += content
|
||||
return LOL_HTML_CONTINUE
|
||||
}
|
||||
|
||||
private func outputSink(chunk: UnsafePointer<CChar>!, chunkLen: Int, userData: UnsafeMutableRawPointer!) {
|
||||
// no-op
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// Reader-Bridging-Header.h
|
||||
// Reader
|
||||
//
|
||||
// Created by Shadowfacts on 1/12/22.
|
||||
//
|
||||
|
||||
#ifndef Reader_Bridging_Header_h
|
||||
#define Reader_Bridging_Header_h
|
||||
|
||||
#import "lol_html.h"
|
||||
|
||||
#endif /* Reader_Bridging_Header_h */
|
|
@ -86,6 +86,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
} catch {
|
||||
logger.error("Unable to sync from server: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
ExcerptGenerator.generateAll(fervorController)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftSoup
|
||||
|
||||
protocol ItemCollectionViewCellDelegate: AnyObject {
|
||||
func itemCellSelected(cell: ItemCollectionViewCell, item: Item)
|
||||
|
@ -74,13 +73,12 @@ class ItemCollectionViewCell: UICollectionViewListCell {
|
|||
|
||||
titleLabel.text = item.title
|
||||
feedTitleLabel.text = item.feed!.title ?? item.feed!.url?.host
|
||||
if let content = item.content {
|
||||
let doc = try! SwiftSoup.parse(content)
|
||||
contentLabel.text = try! doc.select("p").first()?.text()
|
||||
if let excerpt = item.excerpt {
|
||||
contentLabel.text = excerpt
|
||||
contentLabel.isHidden = false
|
||||
} else {
|
||||
contentLabel.text = ""
|
||||
contentLabel.isHidden = true
|
||||
}
|
||||
contentLabel.isHidden = contentLabel.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true
|
||||
|
||||
updateColors()
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
Subproject commit f32bd14b229ed1088c25725cce242817ea2fe43a
|
Loading…
Reference in New Issue