Skip to content

Commit

Permalink
Move all sub processes of MyArticleModel.savePublic() into a new .swi…
Browse files Browse the repository at this point in the history
…ft file
  • Loading branch information
livid committed Nov 6, 2023
1 parent 87e8d69 commit c76c26b
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 227 deletions.
6 changes: 6 additions & 0 deletions Planet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,8 @@
6AABC6A7291A6216009FD13F /* WalletConnectV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AABC6A6291A6216009FD13F /* WalletConnectV1.swift */; };
6AABC6AA291A62A9009FD13F /* WalletConnectSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 6AABC6A9291A62A9009FD13F /* WalletConnectSwift */; };
6AABC6AC291A698B009FD13F /* WalletConnectV1QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AABC6AB291A698B009FD13F /* WalletConnectV1QRCodeView.swift */; };
6AADD6C62AF8809600898A6E /* MyArticleModel+SavePublic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AADD6C52AF8809600898A6E /* MyArticleModel+SavePublic.swift */; };
6AADD6C72AF8809600898A6E /* MyArticleModel+SavePublic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AADD6C52AF8809600898A6E /* MyArticleModel+SavePublic.swift */; };
6AB6E4FB2915227600F17328 /* WalletManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AB6E4FA2915227600F17328 /* WalletManager.swift */; };
6AB6E50F291527B800F17328 /* WalletConnect in Frameworks */ = {isa = PBXBuildFile; productRef = 6AB6E50E291527B800F17328 /* WalletConnect */; };
6AB6E511291527B800F17328 /* WalletConnectAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 6AB6E510291527B800F17328 /* WalletConnectAuth */; };
Expand Down Expand Up @@ -668,6 +670,7 @@
6AA2BC3227DC09AE00AC96B5 /* Planet v3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Planet v3.xcdatamodel"; sourceTree = "<group>"; };
6AABC6A6291A6216009FD13F /* WalletConnectV1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectV1.swift; sourceTree = "<group>"; };
6AABC6AB291A698B009FD13F /* WalletConnectV1QRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectV1QRCodeView.swift; sourceTree = "<group>"; };
6AADD6C52AF8809600898A6E /* MyArticleModel+SavePublic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MyArticleModel+SavePublic.swift"; sourceTree = "<group>"; };
6AB6E4FA2915227600F17328 /* WalletManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletManager.swift; sourceTree = "<group>"; };
6ABAB91728215FF30014F209 /* TemplatePreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplatePreviewView.swift; sourceTree = "<group>"; };
6AC01AED291D258900EB6B5F /* AccountBadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountBadgeView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1375,6 +1378,7 @@
6A941CEC2AF5E7A300CA8261 /* PlanetStore+ServerInfo.swift */,
72114BAE1D6E54DD9D1059CB /* MyArticleModel.swift */,
6ADD7D872A51B616003D2E54 /* MyArticleModel+Save.swift */,
6AADD6C52AF8809600898A6E /* MyArticleModel+SavePublic.swift */,
6A52FAEB2A889577000E85F0 /* BackupArticleModel.swift */,
72114299E3D896326AFFF5D0 /* MyPlanetModel.swift */,
6A52FAEE2A88969A000E85F0 /* BackupMyPlanetModel.swift */,
Expand Down Expand Up @@ -1809,6 +1813,7 @@
2A95E6602A19A3AA001288B8 /* TemplateBrowserInspectorView.swift in Sources */,
6A52FAF32A88A2C2000E85F0 /* PublicPlanetModel.swift in Sources */,
2A95E6612A19A3AA001288B8 /* TemplateStore.swift in Sources */,
6AADD6C72AF8809600898A6E /* MyArticleModel+SavePublic.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -1847,6 +1852,7 @@
2AA7974929496F730031E873 /* PFDashboardInspectorViewController.swift in Sources */,
2A09A35F27C2581F0003A0B5 /* PlanetSidebarView.swift in Sources */,
2AF1318727C7CDAF007792FE /* ArticleView.swift in Sources */,
6AADD6C62AF8809600898A6E /* MyArticleModel+SavePublic.swift in Sources */,
2AFB896C29BA1477003D893E /* PlanetKeyManagerViewModel.swift in Sources */,
2A441BAC27C40AAC0008A694 /* WriterWindow.swift in Sources */,
6A1DA07D28B4278300C6B5A9 /* DecentralizedApp.swift in Sources */,
Expand Down
239 changes: 14 additions & 225 deletions Planet/Entities/MyArticleModel+Save.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,265 +40,54 @@ extension MyArticleModel {

// MARK: - Save to Public/:planet_id/:article_id/index.html

// TODO: Fix a potential crash here
func obtainCoverImageCID() -> String? {
if let coverImageURL = getAttachmentURL(name: "_cover.png") {
if FileManager.default.fileExists(atPath: coverImageURL.path) {
do {
let coverImageCID = try IPFSDaemon.shared.getFileCIDv0(url: coverImageURL)
return coverImageCID
}
catch {
return nil
}
}
}
return nil
}

/// Save article.md
func saveMarkdownInBackground() {
Task(priority: .background) {
await self.saveMarkdown()
}
}

func processContent() throws {
guard self.content.count > 0, self.contentRendered == nil else { return }

if let contentHTML = CMarkRenderer.renderMarkdownHTML(markdown: self.content) {
self.contentRendered = contentHTML
try self.save()
}
}

func saveCoverImageIfNeeded(with marks: inout [String: Date]) throws {
let coverImageText = self.getCoverImageText()
let doneCoverImageText: Date = Date()
marks["CoverImageText"] = doneCoverImageText
debugPrint(
"Cover image text for \(self.title) took: \(doneCoverImageText.timeIntervalSince(marks["ContentRendered"]!))"
)

if self.planet.templateName == "Croptop" {
saveCoverImage(
with: coverImageText,
filename: publicCoverImagePath.path,
imageSize: NSSize(width: 512, height: 512)
)
}

let doneCoverImage: Date = Date()
marks["CoverImage"] = doneCoverImage
debugPrint(
"Cover image for \(self.title) took: \(doneCoverImage.timeIntervalSince(marks["CoverImageText"]!))"
)
}

func getCoverImageCIDIfNeeded() -> String? {
var needsCoverImageCID = false
if let attachments = self.attachments, attachments.count == 0, self.planet.templateName == "Croptop" {
needsCoverImageCID = true
}
if audioFilename != nil, self.planet.templateName == "Croptop" {
needsCoverImageCID = true
}

var coverImageCID: String? = nil
if needsCoverImageCID {
coverImageCID = obtainCoverImageCID()
}

return coverImageCID
}

/// Save the article into UUID/index.html along with its attachments.
func savePublic() throws {
let started: Date = Date()
var marks: [String: Date] = ["Started": started]

guard let template = planet.template else {
throw PlanetError.MissingTemplateError
}
removeDSStore()
saveMarkdownInBackground()

try processContent()
marks.recordEvent("ContentRendered", for: self.title)

try saveCoverImageIfNeeded(with: &marks)
// MARK: - Cover Image `_cover.png`

var coverImageCID: String? = getCoverImageCIDIfNeeded()
marks.recordEvent("CoverImageCID", for: self.title)
try saveCoverImageIfNeeded()
marks.recordEvent("SaveCoverImage", for: self.title)

if let attachments = self.attachments, attachments.count == 0 {
if self.planet.templateName == "Croptop" {
// _cover.png CID is only needed by Croptop now
let newAttachments: [String] = ["_cover.png"]
self.attachments = newAttachments
}
}
var attachmentCIDs: [String: String] = self.cids ?? [:]
let needsToUpdateCIDs = {
if let cids = self.cids, cids.count > 0 {
for attachment in self.attachments ?? [] {
if cids[attachment] == nil {
debugPrint(
"CID Update for \(self.title): NEEDED because \(attachment) is missing"
)
return true
}
if let cid = cids[attachment], cid.hasPrefix("Qm") == false {
debugPrint(
"CID Update for \(self.title): NEEDED because \(attachment) is not CIDv0"
)
return true
}
}
return false
}
if self.attachments?.count ?? 0 > 0 {
debugPrint("CID Update for \(self.title): NEEDED because cids is nil")
return true
}
return false
}()
if needsToUpdateCIDs {
debugPrint("CID Update for \(self.title): NEEDED")
attachmentCIDs = getCIDs()
self.cids = attachmentCIDs
try? self.save()
}
else {
debugPrint("CID Update for \(self.title): NOT NEEDED")
}
let coverImageCID: String? = getCoverImageCIDIfNeeded()
marks.recordEvent("CoverImageCID", for: self.title)

processAttachmentCIDIfNeeded()
marks.recordEvent("AttachmentCID", for: self.title)

// MARK: - Video
if self.hasVideoContent() {
self.saveVideoThumbnail()
}

processVideoThumbnail()
marks.recordEvent("VideoThumbnail", for: self.title)

// MARK: - NFT
// TODO: Move all NFT-related operations into an extension
if attachmentCIDs.count > 0, let firstKeyValuePair = attachmentCIDs.first,
let generateNFTMetadata = template.generateNFTMetadata, generateNFTMetadata
{
debugPrint("Writing NFT metadata for \(self.title) \(cids)")
let (firstKey, firstValue) = firstKeyValuePair
// For image and text-only NFTs, we use the first image as the NFT image
var imageCID: String
imageCID = firstValue
// For video NFTs, we use the CID of _videoThumbnail.png for image
if self.hasVideoContent(),
let videoThumbnailURL = getAttachmentURL(name: "_videoThumbnail.png"),
let videoThumbnailCID = try? IPFSDaemon.shared.getFileCIDv0(url: videoThumbnailURL)
{
imageCID = videoThumbnailCID
}
var animationCID: String? = nil
// For audio NFTs, we use the CID of _cover.png for image
if let audioFilename = audioFilename, let coverImageCID = coverImageCID {
debugPrint(
"Audio NFT for \(self.title): \(audioFilename) coverImageCID: \(coverImageCID)"
)
imageCID = coverImageCID
}
if let audioFilename = audioFilename, let audioCID = attachmentCIDs[audioFilename] {
debugPrint(
"Audio NFT for \(self.title): \(audioFilename) animationCID: \(audioCID)"
)
animationCID = audioCID
}
if let videoFilename = videoFilename, let videoCID = attachmentCIDs[videoFilename] {
animationCID = videoCID
}
debugPrint("NFT image CIDv0 for \(self.title): \(imageCID)")
var attributes: [NFTAttribute] = []
let titleAttribute = NFTAttribute(
trait_type: "title",
value: self.title
)
attributes.append(titleAttribute)
let titleSHA256Attribute = NFTAttribute(
trait_type: "title_sha256",
value: self.title.sha256()
)
attributes.append(titleSHA256Attribute)
let contentSHA256Attribute =
self.content.count > 0
? NFTAttribute(
trait_type: "content_sha256",
value: self.content.sha256()
)
: nil
if let contentSHA256Attribute = contentSHA256Attribute {
attributes.append(contentSHA256Attribute)
}
let createdAtAttribute = NFTAttribute(
trait_type: "created_at",
value: String(Int(self.created.timeIntervalSince1970))
)
attributes.append(createdAtAttribute)
let nft = NFTMetadata(
name: self.title,
description: self.summary ?? firstKey,
image: "https://ipfs.io/ipfs/\(imageCID)",
external_url: (self.externalLink ?? self.browserURL?.absoluteString) ?? "",
mimeType: self.getAttachmentMimeType(name: firstKey),
animation_url: animationCID != nil ? "https://ipfs.io/ipfs/\(animationCID!)" : nil,
attributes: attributes
)
let nftData = try JSONEncoder.shared.encode(nft)
try nftData.write(to: publicNFTMetadataPath)
let nftMetadataCID = self.getNFTJSONCID()
debugPrint("NFT metadata CID: \(nftMetadataCID ?? "nil")")
let nftMetadataCIDPath = publicBasePath.appendingPathComponent("nft.json.cid.txt")
try? nftMetadataCID?.write(to: nftMetadataCIDPath, atomically: true, encoding: .utf8)
}
else {
debugPrint(
"Not writing NFT metadata for \(self.title) and CIDs: \(self.cids ?? [:]) \(template.generateNFTMetadata ?? false) \(self.attachments ?? [])"
)
}

try processNFTMetadata(with: coverImageCID)
marks.recordEvent("NFTMetadata", for: self.title)

// MARK: - Render Markdown
// TODO: This part seems very slow, it takes seconds to render the article HTML
let articleHTML = try template.render(article: self)
try articleHTML.data(using: .utf8)?.write(to: publicIndexPath)

if template.hasSimpleHTML {
let simpleHTML = try template.render(article: self, forSimpleHTML: true)
try simpleHTML.data(using: .utf8)?.write(to: publicSimplePath)
}

try processArticleHTML()
marks.recordEvent("ArticleHTML", for: self.title)

if self.hasHeroImage() || self.hasVideoContent() {
self.saveHeroGrid()
}

// MARK: - Hero Grid
processHeroGrid()
marks.recordEvent("HeroGrid", for: self.title)

try JSONEncoder.shared.encode(publicArticle).write(to: publicInfoPath)
if let articleSlug = self.slug, articleSlug.count > 0 {
let publicSlugBasePath = planet.publicBasePath.appendingPathComponent(
articleSlug,
isDirectory: true
)
if FileManager.default.fileExists(atPath: publicSlugBasePath.path) {
try? FileManager.default.removeItem(at: publicSlugBasePath)
}
try? FileManager.default.copyItem(at: publicBasePath, to: publicSlugBasePath)
}

// MARK: - Slug copy
processSlug()
marks.recordEvent("ArticleSlug", for: self.title)

// MARK: - Send notification when done
Task { @MainActor in
debugPrint("Sending notification: myArticleBuilt \(self.id) \(self.title)")
NotificationCenter.default.post(name: .myArticleBuilt, object: self)
Expand Down
Loading

0 comments on commit c76c26b

Please sign in to comment.