more of a xml/rdf/opf play..
This commit is contained in:
parent
5dfd1950d1
commit
103292cd38
81
cmd/build.go
81
cmd/build.go
|
@ -1,12 +1,14 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"accorder/pkg/calibre"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"accorder/pkg/calibre"
|
"github.com/araddon/dateparse"
|
||||||
|
|
||||||
"github.com/beevik/etree"
|
"github.com/beevik/etree"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -83,6 +85,8 @@ should be enough for the next successful build).`,
|
||||||
root := doc.SelectElement("rdf:RDF")
|
root := doc.SelectElement("rdf:RDF")
|
||||||
for _, attachmentNode := range root.FindElements("[name()='link:type']") {
|
for _, attachmentNode := range root.FindElements("[name()='link:type']") {
|
||||||
var zoteroItem ZoteroItem
|
var zoteroItem ZoteroItem
|
||||||
|
var bookOpf calibre.BookOpfW
|
||||||
|
|
||||||
if attachmentNode.Text() != "application/pdf" {
|
if attachmentNode.Text() != "application/pdf" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -91,17 +95,54 @@ should be enough for the next successful build).`,
|
||||||
filePathElement := zoteroUnion.CreateElement("filePath")
|
filePathElement := zoteroUnion.CreateElement("filePath")
|
||||||
filePathQuery := attachmentNode.Parent().FindElement("[name()='rdf:resource']").SelectAttr("rdf:resource").Value
|
filePathQuery := attachmentNode.Parent().FindElement("[name()='rdf:resource']").SelectAttr("rdf:resource").Value
|
||||||
filePathElement.CreateText(filePathQuery)
|
filePathElement.CreateText(filePathQuery)
|
||||||
|
|
||||||
mimeTypeElement := zoteroUnion.CreateElement("mimeType")
|
mimeTypeElement := zoteroUnion.CreateElement("mimeType")
|
||||||
mimeType := attachmentNode.Text()
|
mimeType := attachmentNode.Text()
|
||||||
mimeTypeElement.CreateText(mimeType)
|
mimeTypeElement.CreateText(mimeType)
|
||||||
|
|
||||||
bibliographyNode := root.FindElement(fmt.Sprintf("[@rdf:resource='%s']", attachmentNode.Parent().SelectAttr("rdf:about").Value)).Parent().Copy()
|
bibliographyNode := root.FindElement(fmt.Sprintf("[@rdf:resource='%s']", attachmentNode.Parent().SelectAttr("rdf:about").Value)).Parent().Copy()
|
||||||
|
|
||||||
|
// newDoc.AddChild(bibliographyNode)
|
||||||
|
// newDoc.WriteTo(os.Stdout)
|
||||||
|
|
||||||
|
dateQuery := bibliographyNode.FindElement("[name()='dc:date']")
|
||||||
|
if dateQuery != nil {
|
||||||
|
dateElement := zoteroUnion.CreateElement("date")
|
||||||
|
date, err := dateparse.ParseAny(dateQuery.Text())
|
||||||
|
if err == nil {
|
||||||
|
formattedDate := date.Format("2006-01-02")
|
||||||
|
dateElement.CreateText(formattedDate)
|
||||||
|
|
||||||
|
bookOpf.Metadata.Published = formattedDate
|
||||||
|
} else {
|
||||||
|
newDateQuery := fmt.Sprintf("1 %s", dateQuery.Text())
|
||||||
|
newDate, err := dateparse.ParseAny(newDateQuery)
|
||||||
|
if err == nil {
|
||||||
|
newFormattedDate := newDate.Format("2006-01-02")
|
||||||
|
dateElement.CreateText(newFormattedDate)
|
||||||
|
bookOpf.Metadata.Published = newFormattedDate
|
||||||
|
} else {
|
||||||
|
lastChanceDate := dateQuery.Text()[len(dateQuery.Text())-4:]
|
||||||
|
year, err := strconv.Atoi(lastChanceDate)
|
||||||
|
if err == nil {
|
||||||
|
justYear := fmt.Sprintf("%d-01-01", year)
|
||||||
|
dateElement.CreateText(justYear)
|
||||||
|
bookOpf.Metadata.Published = justYear
|
||||||
|
} else {
|
||||||
|
fmt.Println("ERROR parsing date...", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
publisherQuery := bibliographyNode.FindElement("[name()='foaf:name']")
|
publisherQuery := bibliographyNode.FindElement("[name()='foaf:name']")
|
||||||
if publisherQuery != nil {
|
if publisherQuery != nil {
|
||||||
publisherElement := zoteroUnion.CreateElement("publisher")
|
publisherElement := zoteroUnion.CreateElement("publisher")
|
||||||
publisher := publisherQuery.Text()
|
publisher := publisherQuery.Text()
|
||||||
publisherElement.CreateText(publisher)
|
publisherElement.CreateText(publisher)
|
||||||
|
|
||||||
|
bookOpf.Metadata.Publisher = publisher
|
||||||
}
|
}
|
||||||
|
|
||||||
authorsQuery := bibliographyNode.FindElements("[name()='foaf:Person']")
|
authorsQuery := bibliographyNode.FindElements("[name()='foaf:Person']")
|
||||||
|
@ -118,7 +159,13 @@ should be enough for the next successful build).`,
|
||||||
if surNameNode != nil {
|
if surNameNode != nil {
|
||||||
surName = surNameNode.Text()
|
surName = surNameNode.Text()
|
||||||
}
|
}
|
||||||
author.CreateText(fmt.Sprintf("%s %s", firstName, surName))
|
fullName := fmt.Sprintf("%s %s", firstName, surName)
|
||||||
|
author.CreateText(fullName)
|
||||||
|
|
||||||
|
bookOpf.Metadata.Creators = append(bookOpf.Metadata.Creators, calibre.Creator{
|
||||||
|
Role: "aut",
|
||||||
|
Name: fullName,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
titleQuery := bibliographyNode.FindElement("[name()='dc:title']")
|
titleQuery := bibliographyNode.FindElement("[name()='dc:title']")
|
||||||
|
@ -126,6 +173,8 @@ should be enough for the next successful build).`,
|
||||||
titleNode := zoteroUnion.CreateElement("title")
|
titleNode := zoteroUnion.CreateElement("title")
|
||||||
title := titleQuery.Text()
|
title := titleQuery.Text()
|
||||||
titleNode.CreateText(title)
|
titleNode.CreateText(title)
|
||||||
|
|
||||||
|
bookOpf.Metadata.Title = title
|
||||||
}
|
}
|
||||||
|
|
||||||
descriptionQuery := bibliographyNode.FindElement("[name()='dcterms:abstract']")
|
descriptionQuery := bibliographyNode.FindElement("[name()='dcterms:abstract']")
|
||||||
|
@ -133,8 +182,13 @@ should be enough for the next successful build).`,
|
||||||
descriptionNode := zoteroUnion.CreateElement("description")
|
descriptionNode := zoteroUnion.CreateElement("description")
|
||||||
description := descriptionQuery.Text()
|
description := descriptionQuery.Text()
|
||||||
descriptionNode.CreateText(description)
|
descriptionNode.CreateText(description)
|
||||||
|
|
||||||
|
bookOpf.Metadata.Description = description
|
||||||
}
|
}
|
||||||
|
|
||||||
// newDoc.WriteTo(os.Stdout)
|
// newDoc.WriteTo(os.Stdout)
|
||||||
|
// fmt.Println("")
|
||||||
|
|
||||||
b, err := newDoc.WriteToBytes()
|
b, err := newDoc.WriteToBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -142,8 +196,27 @@ should be enough for the next successful build).`,
|
||||||
if err := xml.Unmarshal(b, &zoteroItem); err != nil {
|
if err := xml.Unmarshal(b, &zoteroItem); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
fmt.Printf("\nZoteroItem: %#v\n", zoteroItem)
|
// fmt.Printf("\nZoteroItem: %#v\n", zoteroItem)
|
||||||
|
|
||||||
|
bookOpf.Version = "2.0"
|
||||||
|
bookOpf.Xmlns = "http://www.idpf.org/2007/opf"
|
||||||
|
bookOpf.UniqueIdentifier = "uuid_id"
|
||||||
|
bookOpf.Metadata.DC = "http://purl.org/dc/elements/1.1/"
|
||||||
|
bookOpf.Metadata.OPF = "http://www.idpf.org/2007/opf"
|
||||||
|
|
||||||
|
bookOpf.Metadata.Identifiers = append(bookOpf.Metadata.Identifiers, calibre.Identifier{
|
||||||
|
Scheme: "calibre",
|
||||||
|
Id: "calibre_id",
|
||||||
|
Value: "-1",
|
||||||
|
})
|
||||||
|
|
||||||
|
bookOpfOutput, err := xml.MarshalIndent(bookOpf, " ", " ")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
// _ = bookOpfOutput
|
||||||
|
os.Stdout.Write(bookOpfOutput)
|
||||||
|
fmt.Println("")
|
||||||
}
|
}
|
||||||
calibre.RenderStandaloneApp(calibrePath, librarianName, libraryUUID, librarySecret, jsonPath)
|
calibre.RenderStandaloneApp(calibrePath, librarianName, libraryUUID, librarySecret, jsonPath)
|
||||||
},
|
},
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -3,6 +3,7 @@ module accorder
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
|
||||||
github.com/beevik/etree v1.1.0
|
github.com/beevik/etree v1.1.0
|
||||||
github.com/karrick/godirwalk v1.16.1
|
github.com/karrick/godirwalk v1.16.1
|
||||||
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f
|
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f
|
||||||
|
|
5
go.sum
5
go.sum
|
@ -55,6 +55,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
|
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
|
||||||
|
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
|
@ -343,6 +345,7 @@ github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
||||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
|
@ -446,6 +449,7 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
|
||||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
||||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8=
|
github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8=
|
||||||
|
@ -460,6 +464,7 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
|
||||||
github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE=
|
github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE=
|
||||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
|
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=
|
github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=
|
||||||
github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
|
github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
|
||||||
|
|
|
@ -0,0 +1,465 @@
|
||||||
|
package calibre
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/md5"
|
||||||
|
"embed"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
// "github.com/cespare/xxhash"
|
||||||
|
"github.com/karrick/godirwalk"
|
||||||
|
uuid "github.com/satori/go.uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed embResources
|
||||||
|
embResources embed.FS
|
||||||
|
)
|
||||||
|
|
||||||
|
func c(err error, m string) {
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type HugoCatalogItem struct {
|
||||||
|
Key string
|
||||||
|
Book *BookJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataJs struct {
|
||||||
|
Portable bool `json:"portable"`
|
||||||
|
Total int `json:"total,omitempty"`
|
||||||
|
Books []*BookJSON `json:"books"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// type BooksJSON struct {
|
||||||
|
// Books []*BookJSON `json:"books"`
|
||||||
|
// }
|
||||||
|
|
||||||
|
type IdentifierJSON struct {
|
||||||
|
Scheme string `json:"scheme"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FormatJSON struct {
|
||||||
|
Format string `json:"format"`
|
||||||
|
DirPath string `json:"dir_path"`
|
||||||
|
FileName string `json:"file_name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
XXHash uint64 `json:"xxhash,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BookJSON struct {
|
||||||
|
Id string `json:"_id"`
|
||||||
|
Librarian string `json:"librarian"`
|
||||||
|
LibraryUUID string `json:"library_uuid"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
TitleSort string `json:"title_sort"`
|
||||||
|
Authors []string `json:"authors"`
|
||||||
|
Abstract string `json:"abstract"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
Publisher string `json:"publisher"`
|
||||||
|
Pubdate string `json:"pubdate"`
|
||||||
|
LastModified string `json:"last_modified"`
|
||||||
|
Languages []string `json:"languages"`
|
||||||
|
Identifiers []*IdentifierJSON `json:"identifiers"`
|
||||||
|
Formats []*FormatJSON `json:"formats"`
|
||||||
|
CoverUrl string `json:"cover_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Creator struct {
|
||||||
|
Role string `xml:"opf:role,attr"`
|
||||||
|
Name string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Identifier struct {
|
||||||
|
Scheme string `xml:"opf:scheme,attr"`
|
||||||
|
Id string `xml:"id,attr"` // Calibre has two internal Ids: calibre_id and uuid_id
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BookOpfW struct {
|
||||||
|
XMLName xml.Name `xml:"package"`
|
||||||
|
Version string `xml:"version,attr"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
UniqueIdentifier string `xml:"unique-identifier,attr"`
|
||||||
|
Metadata struct {
|
||||||
|
DC string `xml:"xmlns:dc,attr"`
|
||||||
|
OPF string `xml:"xmlns:opf,attr"`
|
||||||
|
Identifiers []struct {
|
||||||
|
Scheme string `xml:"opf:scheme,attr"`
|
||||||
|
Id string `xml:"id,attr"` // Calibre has two internal Ids: calibre_id and uuid_id
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
} `xml:"dc:identifier"`
|
||||||
|
Title string `xml:"dc:title"`
|
||||||
|
Creators []struct {
|
||||||
|
Role string `xml:"opf:role,attr"`
|
||||||
|
Name string `xml:",chardata"`
|
||||||
|
} `xml:"dc:creator"`
|
||||||
|
Published string `xml:"dc:date"`
|
||||||
|
Description string `xml:"dc:description"`
|
||||||
|
Publisher string `xml:"dc:publisher"`
|
||||||
|
Languages []struct {
|
||||||
|
Language string `xml:",chardata"`
|
||||||
|
} `xml:"dc:language"`
|
||||||
|
Tags []struct {
|
||||||
|
Tag string `xml:",chardata"`
|
||||||
|
} `xml:"dc:subject"`
|
||||||
|
Meta []struct {
|
||||||
|
Content string `xml:"content,attr"`
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
} `xml:"meta"`
|
||||||
|
} `xml:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BookOpf struct {
|
||||||
|
XMLName xml.Name `xml:"package"`
|
||||||
|
Version string `xml:"version,attr"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
UniqueIdentifier string `xml:"unique-identifier,attr"`
|
||||||
|
Metadata struct {
|
||||||
|
DC string `xml:"dc,attr"`
|
||||||
|
OPF string `xml:"opf,attr"`
|
||||||
|
Identifiers []struct {
|
||||||
|
Scheme string `xml:"scheme,attr"`
|
||||||
|
Id string `xml:"id,attr"` // Calibre has two internal Ids: calibre_id and uuid_id
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
} `xml:"identifier"`
|
||||||
|
Title string `xml:"title"`
|
||||||
|
Creators []struct {
|
||||||
|
Role string `xml:"role,attr"`
|
||||||
|
Name string `xml:",chardata"`
|
||||||
|
} `xml:"creator"`
|
||||||
|
Published string `xml:"date"`
|
||||||
|
Description string `xml:"description"`
|
||||||
|
Publisher string `xml:"publisher"`
|
||||||
|
Languages []struct {
|
||||||
|
Language string `xml:",chardata"`
|
||||||
|
} `xml:"language"`
|
||||||
|
Tags []struct {
|
||||||
|
Tag string `xml:",chardata"`
|
||||||
|
} `xml:"subject"`
|
||||||
|
Meta []struct {
|
||||||
|
Content string `xml:"content,attr"`
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
} `xml:"meta"`
|
||||||
|
} `xml:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TitleSort returns if Calibre processed Title for sorting order
|
||||||
|
func (book BookOpf) TitleSort() string {
|
||||||
|
for _, meta := range book.Metadata.Meta {
|
||||||
|
if meta.Name == "calibre:title_sort" {
|
||||||
|
return meta.Content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastModified returns when Calibre last time touched the file
|
||||||
|
func (book BookOpf) LastModified() string {
|
||||||
|
lastModified := ""
|
||||||
|
for _, meta := range book.Metadata.Meta {
|
||||||
|
if meta.Name == "calibre:timestamp" {
|
||||||
|
lastModified = meta.Content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lastModified
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authors parses creators and give back authors
|
||||||
|
func (book BookOpf) Authors() []string {
|
||||||
|
authors := []string{}
|
||||||
|
if len(book.Metadata.Creators) > 0 {
|
||||||
|
for _, author := range book.Metadata.Creators {
|
||||||
|
if author.Role == "aut" {
|
||||||
|
authors = append(authors, author.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return authors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags gets a list of tags from Subject nodes in OPF
|
||||||
|
func (book BookOpf) Tags() []string {
|
||||||
|
tags := []string{}
|
||||||
|
for _, tag := range book.Metadata.Tags {
|
||||||
|
tags = append(tags, tag.Tag)
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Languages gets a list of languages
|
||||||
|
func (book BookOpf) Languages() []string {
|
||||||
|
languages := []string{}
|
||||||
|
for _, l := range book.Metadata.Languages {
|
||||||
|
languages = append(languages, l.Language)
|
||||||
|
}
|
||||||
|
return languages
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifiers returns list of identifiers like ISBN, DOI, Google, Amazon...
|
||||||
|
func (book BookOpf) Identifiers() []*IdentifierJSON {
|
||||||
|
identifiers := []*IdentifierJSON{}
|
||||||
|
for _, i := range book.Metadata.Identifiers {
|
||||||
|
if i.Id == "" {
|
||||||
|
identifiers = append(identifiers, &IdentifierJSON{
|
||||||
|
Scheme: strings.ToLower(i.Scheme),
|
||||||
|
Code: i.Value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return identifiers
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uuid returns calibre's book uuid...
|
||||||
|
func (book BookOpf) Uuid(librarySecret string) string {
|
||||||
|
for _, i := range book.Metadata.Identifiers {
|
||||||
|
if i.Id == "uuid_id" {
|
||||||
|
h := hmac.New(md5.New, []byte(librarySecret))
|
||||||
|
h.Write([]byte(i.Value))
|
||||||
|
|
||||||
|
sha := hex.EncodeToString(h.Sum(nil))
|
||||||
|
u, err := uuid.FromString(sha)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formats return list of files and info on them
|
||||||
|
func Formats(calibrePath, relativeDirPath string) []*FormatJSON {
|
||||||
|
path := filepath.Join(calibrePath, relativeDirPath)
|
||||||
|
if strings.HasSuffix(path, "/") != true {
|
||||||
|
path = path + "/"
|
||||||
|
}
|
||||||
|
formats := []*FormatJSON{}
|
||||||
|
files, err := os.ReadDir(path)
|
||||||
|
c(err, "list of formats...")
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
if f.Name() != "metadata.opf" && f.Name() != "cover.jpg" {
|
||||||
|
fi, err := f.Info()
|
||||||
|
c(err, "file info in Formats")
|
||||||
|
x, err := os.Open(filepath.Join(path, f.Name()))
|
||||||
|
c(err, "open file in Formats")
|
||||||
|
defer x.Close()
|
||||||
|
// xxHash := xxhash.New()
|
||||||
|
// _, err = io.Copy(xxHash, x)
|
||||||
|
// c(err, "io.Copy xxHash in Formats")
|
||||||
|
formats = append(formats, &FormatJSON{
|
||||||
|
Format: strings.ReplaceAll(filepath.Ext(f.Name()), ".", ""),
|
||||||
|
DirPath: relativeDirPath,
|
||||||
|
FileName: f.Name(),
|
||||||
|
Size: fi.Size(),
|
||||||
|
// XXHash: xxHash.Sum64(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return formats
|
||||||
|
}
|
||||||
|
|
||||||
|
func lsEmbResources() {
|
||||||
|
_ = fs.WalkDir(embResources, ".", func(path string, d fs.DirEntry, err error) error {
|
||||||
|
c(err, "")
|
||||||
|
// fmt.Println(" ", path)
|
||||||
|
_ = path
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyEmbResources(destPath string) {
|
||||||
|
_ = fs.WalkDir(embResources, ".", func(fsPath string, fsEntry fs.DirEntry, err error) error {
|
||||||
|
destFsPath := strings.ReplaceAll(fsPath, "embResources/", "")
|
||||||
|
if fsPath == "." || fsPath == "embResources" {
|
||||||
|
return nil
|
||||||
|
} else if fsEntry.IsDir() {
|
||||||
|
err := os.MkdirAll(filepath.Join(destPath, destFsPath), 0755)
|
||||||
|
c(err, "make new directory at the destination...")
|
||||||
|
} else {
|
||||||
|
newFile, err := os.Create(filepath.Join(destPath, destFsPath))
|
||||||
|
c(err, "create new file at the destination..")
|
||||||
|
defer newFile.Close()
|
||||||
|
|
||||||
|
embFile, err := embResources.Open(fsPath)
|
||||||
|
_, err = io.Copy(newFile, embFile)
|
||||||
|
c(err, "copy embResources/ file into the new file at the destination...")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeDataJs(calibrePath string, books []*BookJSON) {
|
||||||
|
// j, _ := json.MarshalIndent(&BooksJSON{books})
|
||||||
|
dataJsFirst, err := os.Create(filepath.Join(calibrePath, "static", "data1.js"))
|
||||||
|
c(err, "create dataJsFirst file...")
|
||||||
|
defer dataJsFirst.Close()
|
||||||
|
endB := 24
|
||||||
|
if endB >= len(books) {
|
||||||
|
endB = len(books)
|
||||||
|
}
|
||||||
|
j1, _ := json.Marshal(&DataJs{
|
||||||
|
Portable: true,
|
||||||
|
Total: len(books),
|
||||||
|
Books: books[0:endB],
|
||||||
|
})
|
||||||
|
calibreBooks1 := []byte("CALIBRE_BOOKS1=")
|
||||||
|
_, _ = dataJsFirst.Write(append(calibreBooks1, j1...))
|
||||||
|
|
||||||
|
// [24:120], [120:216], [216:3288], [3288:6360], [6360:9432], [9432:12504]
|
||||||
|
block := endB
|
||||||
|
counter := 2
|
||||||
|
for i := range [2]int{} {
|
||||||
|
ii := make([]int, int(math.Pow(2, float64(i+1))))
|
||||||
|
for j := range ii {
|
||||||
|
_ = j
|
||||||
|
|
||||||
|
var jsn []byte
|
||||||
|
|
||||||
|
endBlock := block + int(math.Pow(32, float64(i+1)))*3
|
||||||
|
|
||||||
|
if endBlock > len(books) {
|
||||||
|
endBlock = len(books)
|
||||||
|
}
|
||||||
|
|
||||||
|
dataJs, err := os.Create(filepath.Join(calibrePath, "static", fmt.Sprintf("data%d.js", counter)))
|
||||||
|
c(err, "create dataJs file...")
|
||||||
|
defer dataJs.Close()
|
||||||
|
|
||||||
|
if endBlock <= len(books) {
|
||||||
|
jsn, _ = json.Marshal(&DataJs{
|
||||||
|
Portable: true,
|
||||||
|
Books: books[block:endBlock],
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
jsn, _ = json.Marshal(&DataJs{
|
||||||
|
Portable: true,
|
||||||
|
Books: []*BookJSON{},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
calibreBooks := []byte(fmt.Sprintf("CALIBRE_BOOKS%d=", counter))
|
||||||
|
_, _ = dataJs.Write(append(calibreBooks, jsn...))
|
||||||
|
block = endBlock
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataJsLast, err := os.Create(filepath.Join(calibrePath, "static", "data8.js"))
|
||||||
|
c(err, "create dataJsLast file...")
|
||||||
|
defer dataJsLast.Close()
|
||||||
|
j8, _ := json.Marshal(&DataJs{
|
||||||
|
Portable: true,
|
||||||
|
Total: len(books),
|
||||||
|
Books: books[block:],
|
||||||
|
})
|
||||||
|
calibreBooks8 := []byte("CALIBRE_BOOKS8=")
|
||||||
|
_, _ = dataJsLast.Write(append(calibreBooks8, j8...))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrintExePath() {
|
||||||
|
fmt.Println(os.Executable())
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenderStandaloneApp(calibrePath, librarianName, libraryUUID, librarySecret, jsonPath string) {
|
||||||
|
var outputs sync.Map
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
count := 0
|
||||||
|
err := godirwalk.Walk(calibrePath, &godirwalk.Options{
|
||||||
|
Callback: func(path string, info *godirwalk.Dirent) error {
|
||||||
|
if info.Name() == "metadata.opf" {
|
||||||
|
bookOpf := &BookOpf{}
|
||||||
|
count++
|
||||||
|
wg.Add(1)
|
||||||
|
go func(bookOpf *BookOpf) {
|
||||||
|
// randSuffix := uuid.New()
|
||||||
|
// randSuffix := uuid.NewV4()
|
||||||
|
relativeDirPath := strings.NewReplacer(calibrePath, "", "metadata.opf", "").Replace(path)
|
||||||
|
f, _ := os.ReadFile(path)
|
||||||
|
_ = xml.Unmarshal([]byte(f), &bookOpf)
|
||||||
|
|
||||||
|
book := &BookJSON{
|
||||||
|
// Id: randSuffix.String(),
|
||||||
|
Id: bookOpf.Uuid(librarySecret),
|
||||||
|
Title: bookOpf.Metadata.Title,
|
||||||
|
Librarian: librarianName,
|
||||||
|
// LibraryUUID: randSuffix.String(),
|
||||||
|
LibraryUUID: libraryUUID,
|
||||||
|
TitleSort: bookOpf.TitleSort(),
|
||||||
|
Authors: bookOpf.Authors(),
|
||||||
|
Pubdate: bookOpf.Metadata.Published,
|
||||||
|
LastModified: bookOpf.LastModified(),
|
||||||
|
Tags: bookOpf.Tags(),
|
||||||
|
Publisher: bookOpf.Metadata.Publisher,
|
||||||
|
Abstract: bookOpf.Metadata.Description,
|
||||||
|
Languages: bookOpf.Languages(),
|
||||||
|
Identifiers: bookOpf.Identifiers(),
|
||||||
|
Formats: Formats(calibrePath, relativeDirPath),
|
||||||
|
CoverUrl: filepath.Join(relativeDirPath, "cover.jpg"),
|
||||||
|
}
|
||||||
|
// outputs.Store(fmt.Sprintf("%s_%s", book.LastModified, randSuffix.String()), book)
|
||||||
|
outputs.Store(fmt.Sprintf("%s_%s", book.LastModified, uuid.NewV4().String()), book)
|
||||||
|
wg.Done()
|
||||||
|
}(bookOpf)
|
||||||
|
}
|
||||||
|
if count > 100 {
|
||||||
|
wg.Wait()
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
ErrorCallback: func(path string, err error) godirwalk.ErrorAction {
|
||||||
|
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
|
||||||
|
return godirwalk.SkipNode
|
||||||
|
},
|
||||||
|
Unsorted: true})
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
c(err, "")
|
||||||
|
|
||||||
|
var keysByDate []string
|
||||||
|
outputs.Range(func(k, v interface{}) bool {
|
||||||
|
keysByDate = append(keysByDate, k.(string))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
sort.Sort(sort.Reverse(sort.StringSlice(keysByDate)))
|
||||||
|
|
||||||
|
var books []*BookJSON
|
||||||
|
for _, keyByDate := range keysByDate {
|
||||||
|
b, _ := outputs.Load(keyByDate)
|
||||||
|
books = append(books, b.(*BookJSON))
|
||||||
|
}
|
||||||
|
|
||||||
|
lsEmbResources()
|
||||||
|
copyEmbResources(calibrePath)
|
||||||
|
writeDataJs(calibrePath, books)
|
||||||
|
|
||||||
|
hugoCatalog := map[string]*BookJSON{}
|
||||||
|
|
||||||
|
for _, book := range books {
|
||||||
|
hugoCatalog[book.Id] = book
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsonPath != "" {
|
||||||
|
jsonDump, _ := json.Marshal(hugoCatalog)
|
||||||
|
// _ = j
|
||||||
|
err = os.WriteFile(jsonPath, jsonDump, 0666)
|
||||||
|
c(err, "writing json file failed...")
|
||||||
|
}
|
||||||
|
fmt.Printf("Check out: %sBROWSE_LIBRARY.html", calibrePath)
|
||||||
|
}
|
Loading…
Reference in New Issue