[add] search
This commit is contained in:
parent
70c7f4b991
commit
edca763396
@ -82,6 +82,7 @@ func (api *API) registerWebAppRoutes() {
|
||||
|
||||
render.AddFromFilesFuncs("login", helperFuncs, "templates/login.html")
|
||||
render.AddFromFilesFuncs("home", helperFuncs, "templates/base.html", "templates/home.html")
|
||||
render.AddFromFilesFuncs("search", helperFuncs, "templates/base.html", "templates/search.html")
|
||||
render.AddFromFilesFuncs("settings", helperFuncs, "templates/base.html", "templates/settings.html")
|
||||
render.AddFromFilesFuncs("activity", helperFuncs, "templates/base.html", "templates/activity.html")
|
||||
render.AddFromFilesFuncs("documents", helperFuncs, "templates/base.html", "templates/documents.html")
|
||||
@ -107,6 +108,12 @@ func (api *API) registerWebAppRoutes() {
|
||||
api.Router.POST("/documents/:document/edit", api.authWebAppMiddleware, api.editDocument)
|
||||
api.Router.POST("/documents/:document/identify", api.authWebAppMiddleware, api.identifyDocument)
|
||||
api.Router.POST("/documents/:document/delete", api.authWebAppMiddleware, api.deleteDocument)
|
||||
|
||||
// Behind Configuration Flag
|
||||
if api.Config.SearchEnabled {
|
||||
api.Router.GET("/search", api.authWebAppMiddleware, api.createAppResourcesRoute("search"))
|
||||
api.Router.POST("/search", api.authWebAppMiddleware, api.saveNewDocument)
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) registerKOAPIRoutes(apiGroup *gin.RouterGroup) {
|
||||
|
@ -17,6 +17,8 @@ import (
|
||||
"golang.org/x/exp/slices"
|
||||
"reichard.io/bbank/database"
|
||||
"reichard.io/bbank/metadata"
|
||||
"reichard.io/bbank/search"
|
||||
"reichard.io/bbank/utils"
|
||||
)
|
||||
|
||||
type queryParams struct {
|
||||
@ -25,6 +27,11 @@ type queryParams struct {
|
||||
Document *string `form:"document"`
|
||||
}
|
||||
|
||||
type searchParams struct {
|
||||
Query *string `form:"query"`
|
||||
BookType *string `form:"book_type"`
|
||||
}
|
||||
|
||||
type requestDocumentEdit struct {
|
||||
Title *string `form:"title"`
|
||||
Author *string `form:"author"`
|
||||
@ -48,6 +55,13 @@ type requestSettingsEdit struct {
|
||||
TimeOffset *string `form:"time_offset"`
|
||||
}
|
||||
|
||||
type requestDocumentAdd struct {
|
||||
ID *string `form:"id"`
|
||||
Title *string `form:"title"`
|
||||
Author *string `form:"author"`
|
||||
BookType *string `form:"book_type"`
|
||||
}
|
||||
|
||||
func (api *API) webManifest(c *gin.Context) {
|
||||
c.Header("Content-Type", "application/manifest+json")
|
||||
c.File("./assets/manifest.json")
|
||||
@ -60,6 +74,7 @@ func (api *API) createAppResourcesRoute(routeName string, args ...map[string]any
|
||||
templateVarsBase = args[0]
|
||||
}
|
||||
templateVarsBase["RouteName"] = routeName
|
||||
templateVarsBase["SearchEnabled"] = api.Config.SearchEnabled
|
||||
|
||||
return func(c *gin.Context) {
|
||||
var userID string
|
||||
@ -174,6 +189,26 @@ func (api *API) createAppResourcesRoute(routeName string, args ...map[string]any
|
||||
},
|
||||
"Devices": devices,
|
||||
}
|
||||
} else if routeName == "search" {
|
||||
var sParams searchParams
|
||||
c.BindQuery(&sParams)
|
||||
|
||||
// Only Handle Query
|
||||
if sParams.BookType != nil && !slices.Contains([]string{"NON_FICTION", "FICTION"}, *sParams.BookType) {
|
||||
templateVars["SearchErrorMessage"] = "Invalid Book Type"
|
||||
} else if sParams.Query != nil && *sParams.Query == "" {
|
||||
templateVars["SearchErrorMessage"] = "Invalid Query"
|
||||
} else if sParams.BookType != nil && sParams.Query != nil {
|
||||
var bType search.BookType = search.BOOK_FICTION
|
||||
if *sParams.BookType == "NON_FICTION" {
|
||||
bType = search.BOOK_NON_FICTION
|
||||
}
|
||||
|
||||
// Search
|
||||
searchResults := search.SearchBook(*sParams.Query, bType)
|
||||
templateVars["Data"] = searchResults
|
||||
templateVars["BookType"] = *sParams.BookType
|
||||
}
|
||||
} else if routeName == "login" {
|
||||
templateVars["RegistrationEnabled"] = api.Config.RegistrationEnabled
|
||||
}
|
||||
@ -430,7 +465,8 @@ func (api *API) identifyDocument(c *gin.Context) {
|
||||
|
||||
// Template Variables
|
||||
templateVars := gin.H{
|
||||
"RelBase": "../../",
|
||||
"RelBase": "../../",
|
||||
"SearchEnabled": api.Config.SearchEnabled,
|
||||
}
|
||||
|
||||
// Get Metadata
|
||||
@ -479,6 +515,103 @@ func (api *API) identifyDocument(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "document", templateVars)
|
||||
}
|
||||
|
||||
func (api *API) saveNewDocument(c *gin.Context) {
|
||||
var rDocAdd requestDocumentAdd
|
||||
if err := c.ShouldBind(&rDocAdd); err != nil {
|
||||
log.Error("[saveNewDocument] Invalid Form Bind")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate Form Exists
|
||||
if rDocAdd.ID == nil ||
|
||||
rDocAdd.BookType == nil ||
|
||||
rDocAdd.Title == nil ||
|
||||
rDocAdd.Author == nil {
|
||||
log.Error("[saveNewDocument] Missing Form Values")
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
|
||||
return
|
||||
}
|
||||
|
||||
var bType search.BookType = search.BOOK_FICTION
|
||||
if *rDocAdd.BookType == "NON_FICTION" {
|
||||
bType = search.BOOK_NON_FICTION
|
||||
}
|
||||
|
||||
// Save Book
|
||||
tempFilePath, err := search.SaveBook(*rDocAdd.ID, bType)
|
||||
if err != nil {
|
||||
log.Warn("[saveNewDocument] Temp File Error: ", err)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate Partial MD5 ID
|
||||
partialMD5, err := utils.CalculatePartialMD5(tempFilePath)
|
||||
if err != nil {
|
||||
log.Warn("[saveNewDocument] Partial MD5 Error: ", err)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Derive Extension on MIME
|
||||
fileMime, err := mimetype.DetectFile(tempFilePath)
|
||||
fileExtension := fileMime.Extension()
|
||||
|
||||
// Derive Filename
|
||||
var fileName string
|
||||
if *rDocAdd.Author != "" {
|
||||
fileName = fileName + *rDocAdd.Author
|
||||
} else {
|
||||
fileName = fileName + "Unknown"
|
||||
}
|
||||
|
||||
if *rDocAdd.Title != "" {
|
||||
fileName = fileName + " - " + *rDocAdd.Title
|
||||
} else {
|
||||
fileName = fileName + " - Unknown"
|
||||
}
|
||||
|
||||
// Remove Slashes
|
||||
fileName = strings.ReplaceAll(fileName, "/", "")
|
||||
|
||||
// Derive & Sanitize File Name
|
||||
fileName = "." + filepath.Clean(fmt.Sprintf("/%s [%s]%s", fileName, partialMD5, fileExtension))
|
||||
|
||||
// Generate Storage Path
|
||||
safePath := filepath.Join(api.Config.DataPath, "documents", fileName)
|
||||
|
||||
// Move File
|
||||
if err := os.Rename(tempFilePath, safePath); err != nil {
|
||||
log.Warn("[saveNewDocument] Move Temp File Error: ", err)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Get MD5 Hash
|
||||
fileHash, err := getFileMD5(safePath)
|
||||
if err != nil {
|
||||
log.Error("[saveNewDocument] Hash Failure:", err)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Upsert Document
|
||||
if _, err = api.DB.Queries.UpsertDocument(api.DB.Ctx, database.UpsertDocumentParams{
|
||||
ID: partialMD5,
|
||||
Title: rDocAdd.Title,
|
||||
Author: rDocAdd.Author,
|
||||
Md5: fileHash,
|
||||
Filepath: &fileName,
|
||||
}); err != nil {
|
||||
log.Error("[saveNewDocument] UpsertDocument DB Error:", err)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
c.Redirect(http.StatusFound, fmt.Sprintf("./documents/%s", partialMD5))
|
||||
}
|
||||
|
||||
func (api *API) editSettings(c *gin.Context) {
|
||||
rUser, _ := c.Get("AuthorizedUser")
|
||||
|
||||
@ -555,7 +688,8 @@ func (api *API) editSettings(c *gin.Context) {
|
||||
"Settings": gin.H{
|
||||
"TimeOffset": *user.TimeOffset,
|
||||
},
|
||||
"Devices": devices,
|
||||
"Devices": devices,
|
||||
"SearchEnabled": api.Config.SearchEnabled,
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "settings", templateVars)
|
||||
|
@ -22,6 +22,7 @@ type Config struct {
|
||||
// Miscellaneous Settings
|
||||
RegistrationEnabled bool
|
||||
CookieSessionKey string
|
||||
SearchEnabled bool
|
||||
}
|
||||
|
||||
func Load() *Config {
|
||||
@ -35,6 +36,7 @@ func Load() *Config {
|
||||
ListenPort: getEnv("LISTEN_PORT", "8585"),
|
||||
CookieSessionKey: trimLowerString(getEnv("COOKIE_SESSION_KEY", "")),
|
||||
RegistrationEnabled: trimLowerString(getEnv("REGISTRATION_ENABLED", "false")) == "true",
|
||||
SearchEnabled: trimLowerString(getEnv("SEARCH_ENABLED", "false")) == "true",
|
||||
}
|
||||
}
|
||||
|
||||
|
2
go.mod
2
go.mod
@ -17,6 +17,8 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.8.1 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/bytedance/sonic v1.10.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
|
8
go.sum
8
go.sum
@ -1,5 +1,9 @@
|
||||
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
||||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||
github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736 h1:qZaEtLxnqY5mJ0fVKbk31NVhlgi0yrKm51Pq/I5wcz4=
|
||||
github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736/go.mod h1:mTeFRcTdnpzOlRjMoFYC/80HwVUreupyAiqPkCZQOXc=
|
||||
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
@ -174,8 +178,10 @@ golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
@ -187,6 +193,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@ -218,6 +225,7 @@ golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
|
253
search/search.go
Normal file
253
search/search.go
Normal file
@ -0,0 +1,253 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Cadence string
|
||||
|
||||
const (
|
||||
TOP_YEAR Cadence = "y"
|
||||
TOP_MONTH Cadence = "m"
|
||||
)
|
||||
|
||||
type BookType int
|
||||
|
||||
const (
|
||||
BOOK_FICTION BookType = iota
|
||||
BOOK_NON_FICTION
|
||||
)
|
||||
|
||||
type SearchItem struct {
|
||||
ID string
|
||||
Title string
|
||||
Author string
|
||||
Language string
|
||||
Series string
|
||||
FileType string
|
||||
FileSize string
|
||||
UploadDate string
|
||||
}
|
||||
|
||||
func SearchBook(query string, bookType BookType) (allEntries []SearchItem) {
|
||||
log.Info(query)
|
||||
if bookType == BOOK_FICTION {
|
||||
// Search Fiction
|
||||
url := "https://libgen.is/fiction/?q=" + url.QueryEscape(query) + "&language=English&format=epub"
|
||||
body := getPage(url)
|
||||
allEntries = parseLibGenFiction(body)
|
||||
} else if bookType == BOOK_NON_FICTION {
|
||||
// Search NonFiction
|
||||
url := "https://libgen.is/search.php?req=" + url.QueryEscape(query)
|
||||
body := getPage(url)
|
||||
allEntries = parseLibGenNonFiction(body)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func GoodReadsMostRead(c Cadence) []SearchItem {
|
||||
body := getPage("https://www.goodreads.com/book/most_read?category=all&country=US&duration=" + string(c))
|
||||
return parseGoodReads(body)
|
||||
}
|
||||
|
||||
func GetBookURL(id string, bookType BookType) string {
|
||||
// Derive Info URL
|
||||
var infoURL string
|
||||
if bookType == BOOK_FICTION {
|
||||
infoURL = "http://library.lol/fiction/" + id
|
||||
} else if bookType == BOOK_NON_FICTION {
|
||||
infoURL = "http://library.lol/main/" + id
|
||||
}
|
||||
|
||||
// Parse & Derive Download URL
|
||||
body := getPage(infoURL)
|
||||
|
||||
// downloadURL := parseLibGenDownloadURL(body)
|
||||
return parseLibGenDownloadURL(body)
|
||||
}
|
||||
|
||||
func SaveBook(id string, bookType BookType) (string, error) {
|
||||
// Derive Info URL
|
||||
var infoURL string
|
||||
if bookType == BOOK_FICTION {
|
||||
infoURL = "http://library.lol/fiction/" + id
|
||||
} else if bookType == BOOK_NON_FICTION {
|
||||
infoURL = "http://library.lol/main/" + id
|
||||
}
|
||||
|
||||
// Parse & Derive Download URL
|
||||
body := getPage(infoURL)
|
||||
bookURL := parseLibGenDownloadURL(body)
|
||||
|
||||
// Create File
|
||||
tempFile, err := os.CreateTemp("", "book")
|
||||
if err != nil {
|
||||
log.Error("[SaveBook] File Create Error: ", err)
|
||||
return "", errors.New("File Failure")
|
||||
}
|
||||
defer tempFile.Close()
|
||||
|
||||
// Download File
|
||||
log.Info("[SaveBook] Downloading Book")
|
||||
resp, err := http.Get(bookURL)
|
||||
if err != nil {
|
||||
os.Remove(tempFile.Name())
|
||||
log.Error("[SaveBook] Cover URL API Failure")
|
||||
return "", errors.New("API Failure")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Copy File to Disk
|
||||
log.Info("[SaveBook] Saving Book")
|
||||
_, err = io.Copy(tempFile, resp.Body)
|
||||
if err != nil {
|
||||
os.Remove(tempFile.Name())
|
||||
log.Error("[SaveBook] File Copy Error")
|
||||
return "", errors.New("File Failure")
|
||||
}
|
||||
|
||||
return tempFile.Name(), nil
|
||||
}
|
||||
|
||||
func getPage(page string) io.ReadCloser {
|
||||
resp, _ := http.Get(page)
|
||||
return resp.Body
|
||||
}
|
||||
|
||||
func parseLibGenFiction(body io.ReadCloser) []SearchItem {
|
||||
// Parse
|
||||
defer body.Close()
|
||||
doc, _ := goquery.NewDocumentFromReader(body)
|
||||
|
||||
// Normalize Results
|
||||
var allEntries []SearchItem
|
||||
doc.Find("table.catalog tbody > tr").Each(func(ix int, rawBook *goquery.Selection) {
|
||||
|
||||
// Parse File Details
|
||||
fileItem := rawBook.Find("td:nth-child(5)")
|
||||
fileDesc := fileItem.Text()
|
||||
fileDescSplit := strings.Split(fileDesc, "/")
|
||||
fileType := strings.ToLower(strings.TrimSpace(fileDescSplit[0]))
|
||||
fileSize := strings.TrimSpace(fileDescSplit[1])
|
||||
|
||||
// Parse Upload Date
|
||||
uploadedRaw, _ := fileItem.Attr("title")
|
||||
uploadedDateRaw := strings.Split(uploadedRaw, "Uploaded at ")[1]
|
||||
uploadDate, _ := time.Parse("2006-01-02 15:04:05", uploadedDateRaw)
|
||||
|
||||
// Parse MD5
|
||||
editHref, _ := rawBook.Find("td:nth-child(7) a").Attr("href")
|
||||
hrefArray := strings.Split(editHref, "/")
|
||||
id := hrefArray[len(hrefArray)-1]
|
||||
|
||||
// Parse Other Details
|
||||
title := rawBook.Find("td:nth-child(3) p a").Text()
|
||||
author := rawBook.Find(".catalog_authors li a").Text()
|
||||
language := rawBook.Find("td:nth-child(4)").Text()
|
||||
series := rawBook.Find("td:nth-child(2)").Text()
|
||||
|
||||
item := SearchItem{
|
||||
ID: id,
|
||||
Title: title,
|
||||
Author: author,
|
||||
Series: series,
|
||||
Language: language,
|
||||
FileType: fileType,
|
||||
FileSize: fileSize,
|
||||
UploadDate: uploadDate.Format(time.RFC3339),
|
||||
}
|
||||
|
||||
allEntries = append(allEntries, item)
|
||||
})
|
||||
|
||||
// Return Results
|
||||
return allEntries
|
||||
}
|
||||
|
||||
func parseLibGenNonFiction(body io.ReadCloser) []SearchItem {
|
||||
// Parse
|
||||
defer body.Close()
|
||||
doc, _ := goquery.NewDocumentFromReader(body)
|
||||
|
||||
// Normalize Results
|
||||
var allEntries []SearchItem
|
||||
doc.Find("table.c tbody > tr:nth-child(n + 2)").Each(func(ix int, rawBook *goquery.Selection) {
|
||||
|
||||
// Parse Type & Size
|
||||
fileSize := strings.ToLower(strings.TrimSpace(rawBook.Find("td:nth-child(8)").Text()))
|
||||
fileType := strings.ToLower(strings.TrimSpace(rawBook.Find("td:nth-child(9)").Text()))
|
||||
|
||||
// Parse MD5
|
||||
titleRaw := rawBook.Find("td:nth-child(3) [id]")
|
||||
editHref, _ := titleRaw.Attr("href")
|
||||
hrefArray := strings.Split(editHref, "?md5=")
|
||||
id := hrefArray[1]
|
||||
|
||||
// Parse Other Details
|
||||
title := titleRaw.Text()
|
||||
author := rawBook.Find("td:nth-child(2)").Text()
|
||||
language := rawBook.Find("td:nth-child(7)").Text()
|
||||
series := rawBook.Find("td:nth-child(3) [href*='column=series']").Text()
|
||||
|
||||
item := SearchItem{
|
||||
ID: id,
|
||||
Title: title,
|
||||
Author: author,
|
||||
Series: series,
|
||||
Language: language,
|
||||
FileType: fileType,
|
||||
FileSize: fileSize,
|
||||
}
|
||||
|
||||
allEntries = append(allEntries, item)
|
||||
})
|
||||
|
||||
// Return Results
|
||||
return allEntries
|
||||
}
|
||||
|
||||
func parseLibGenDownloadURL(body io.ReadCloser) string {
|
||||
// Parse
|
||||
defer body.Close()
|
||||
doc, _ := goquery.NewDocumentFromReader(body)
|
||||
|
||||
// Return Download URL
|
||||
// downloadURL, _ := doc.Find("#download [href*=cloudflare]").Attr("href")
|
||||
downloadURL, _ := doc.Find("#download h2 a").Attr("href")
|
||||
|
||||
return downloadURL
|
||||
}
|
||||
|
||||
func parseGoodReads(body io.ReadCloser) []SearchItem {
|
||||
// Parse
|
||||
defer body.Close()
|
||||
doc, _ := goquery.NewDocumentFromReader(body)
|
||||
|
||||
// Normalize Results
|
||||
var allEntries []SearchItem
|
||||
|
||||
doc.Find("[itemtype=\"http://schema.org/Book\"]").Each(func(ix int, rawBook *goquery.Selection) {
|
||||
title := rawBook.Find(".bookTitle span").Text()
|
||||
author := rawBook.Find(".authorName span").Text()
|
||||
|
||||
item := SearchItem{
|
||||
Title: title,
|
||||
Author: author,
|
||||
}
|
||||
|
||||
allEntries = append(allEntries, item)
|
||||
})
|
||||
|
||||
// Return Results
|
||||
return allEntries
|
||||
}
|
@ -33,6 +33,12 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-black dark:text-white">
|
||||
{{ if not .Data }}
|
||||
<tr>
|
||||
<td class="text-center p-3" colspan="4">No Results</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
|
||||
{{range $activity := .Data }}
|
||||
<tr>
|
||||
<td class="p-3 border-b border-gray-200">
|
||||
|
@ -41,7 +41,7 @@
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.5192 7.82274C2 8.77128 2 9.91549 2 12.2039V13.725C2 17.6258 2 19.5763 3.17157 20.7881C4.34315 22 6.22876 22 10 22H14C17.7712 22 19.6569 22 20.8284 20.7881C22 19.5763 22 17.6258 22 13.725V12.2039C22 9.91549 22 8.77128 21.4808 7.82274C20.9616 6.87421 20.0131 6.28551 18.116 5.10812L16.116 3.86687C14.1106 2.62229 13.1079 2 12 2C10.8921 2 9.88939 2.62229 7.88403 3.86687L5.88403 5.10813C3.98695 6.28551 3.0384 6.87421 2.5192 7.82274ZM11.25 18C11.25 18.4142 11.5858 18.75 12 18.75C12.4142 18.75 12.75 18.4142 12.75 18V15C12.75 14.5858 12.4142 14.25 12 14.25C11.5858 14.25 11.25 14.5858 11.25 15V18Z" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="mx-4 text-sm font-normal"> Home </span>
|
||||
<span class="mx-4 text-sm font-normal">Home</span>
|
||||
</a>
|
||||
<a
|
||||
class="flex items-center justify-start w-full p-2 pl-6 my-2 transition-colors duration-200 border-l-4 {{if eq .RouteName "documents"}}border-purple-500 dark:text-white{{else}}border-transparent text-gray-400 hover:text-gray-800 dark:hover:text-gray-100{{end}}"
|
||||
@ -59,7 +59,7 @@
|
||||
<path d="M7.47341 17.1351H8.68965H13.1034H19.9991C19.9956 18.2657 19.9776 19.1088 19.8862 19.775C19.7773 20.5683 19.5782 20.9884 19.2728 21.2876C18.9674 21.5868 18.5387 21.7818 17.729 21.8885C16.8955 21.9983 15.7908 22 14.2069 22H9.7931C8.2092 22 7.10452 21.9983 6.27103 21.8885C5.46135 21.7818 5.03258 21.5868 4.72718 21.2876C4.42179 20.9884 4.22268 20.5683 4.11382 19.775C4.07259 19.4746 4.0463 19.1382 4.02952 18.7558C4.30088 18.0044 4.93365 17.4264 5.72738 17.218C6.01657 17.1421 6.39395 17.1351 7.47341 17.1351Z" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="mx-4 text-sm font-normal"> Documents </span>
|
||||
<span class="mx-4 text-sm font-normal">Documents</span>
|
||||
</a>
|
||||
<a
|
||||
class="flex items-center justify-start w-full p-2 pl-6 my-2 transition-colors duration-200 border-l-4 {{if eq .RouteName "activity"}}border-purple-500 dark:text-white{{else}}border-transparent text-gray-400 hover:text-gray-800 dark:hover:text-gray-100{{end}}"
|
||||
@ -77,8 +77,31 @@
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.5 4.03662C5.24209 4.10719 4.44798 4.30764 3.87868 4.87694C3 5.75562 3 7.16983 3 9.99826V15.9983C3 18.8267 3 20.2409 3.87868 21.1196C4.75736 21.9983 6.17157 21.9983 9 21.9983H15C17.8284 21.9983 19.2426 21.9983 20.1213 21.1196C21 20.2409 21 18.8267 21 15.9983V9.99826C21 7.16983 21 5.75562 20.1213 4.87694C19.552 4.30764 18.7579 4.10719 17.5 4.03662V4.5C17.5 6.15685 16.1569 7.5 14.5 7.5H9.5C7.84315 7.5 6.5 6.15685 6.5 4.5V4.03662ZM7 9.75C6.58579 9.75 6.25 10.0858 6.25 10.5C6.25 10.9142 6.58579 11.25 7 11.25H7.5C7.91421 11.25 8.25 10.9142 8.25 10.5C8.25 10.0858 7.91421 9.75 7.5 9.75H7ZM10.5 9.75C10.0858 9.75 9.75 10.0858 9.75 10.5C9.75 10.9142 10.0858 11.25 10.5 11.25H17C17.4142 11.25 17.75 10.9142 17.75 10.5C17.75 10.0858 17.4142 9.75 17 9.75H10.5ZM7 13.25C6.58579 13.25 6.25 13.5858 6.25 14C6.25 14.4142 6.58579 14.75 7 14.75H7.5C7.91421 14.75 8.25 14.4142 8.25 14C8.25 13.5858 7.91421 13.25 7.5 13.25H7ZM10.5 13.25C10.0858 13.25 9.75 13.5858 9.75 14C9.75 14.4142 10.0858 14.75 10.5 14.75H17C17.4142 14.75 17.75 14.4142 17.75 14C17.75 13.5858 17.4142 13.25 17 13.25H10.5ZM7 16.75C6.58579 16.75 6.25 17.0858 6.25 17.5C6.25 17.9142 6.58579 18.25 7 18.25H7.5C7.91421 18.25 8.25 17.9142 8.25 17.5C8.25 17.0858 7.91421 16.75 7.5 16.75H7ZM10.5 16.75C10.0858 16.75 9.75 17.0858 9.75 17.5C9.75 17.9142 10.0858 18.25 10.5 18.25H17C17.4142 18.25 17.75 17.9142 17.75 17.5C17.75 17.0858 17.4142 16.75 17 16.75H10.5Z"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="mx-4 text-sm font-normal"> Activity </span>
|
||||
<span class="mx-4 text-sm font-normal">Activity</span>
|
||||
</a>
|
||||
{{ if .SearchEnabled }}
|
||||
<a
|
||||
class="flex items-center justify-start w-full p-2 pl-6 my-2 transition-colors duration-200 border-l-4 {{if eq .RouteName "search"}}border-purple-500 dark:text-white{{else}}border-transparent text-gray-400 hover:text-gray-800 dark:hover:text-gray-100{{end}}"
|
||||
href="/search"
|
||||
>
|
||||
<span class="text-left">
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M2 12C2 7.28595 2 4.92893 3.46447 3.46447C4.92893 2 7.28595 2 12 2C16.714 2 19.0711 2 20.5355 3.46447C22 4.92893 22 7.28595 22 12C22 16.714 22 19.0711 20.5355 20.5355C19.0711 22 16.714 22 12 22C7.28595 22 4.92893 22 3.46447 20.5355C2 19.0711 2 16.714 2 12ZM9 11.5C9 10.1193 10.1193 9 11.5 9C12.8807 9 14 10.1193 14 11.5C14 12.8807 12.8807 14 11.5 14C10.1193 14 9 12.8807 9 11.5ZM11.5 7C9.01472 7 7 9.01472 7 11.5C7 13.9853 9.01472 16 11.5 16C12.3805 16 13.202 15.7471 13.8957 15.31L15.2929 16.7071C15.6834 17.0976 16.3166 17.0976 16.7071 16.7071C17.0976 16.3166 17.0976 15.6834 16.7071 15.2929L15.31 13.8957C15.7471 13.202 16 12.3805 16 11.5C16 9.01472 13.9853 7 11.5 7Z"
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="mx-4 text-sm font-normal">Search</span>
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
197
templates/search.html
Normal file
197
templates/search.html
Normal file
@ -0,0 +1,197 @@
|
||||
{{template "base.html" .}} {{define "title"}}Search{{end}} {{define "header"}}
|
||||
<a href="./search">Search</a>
|
||||
{{end}} {{define "content"}}
|
||||
<div class="w-full flex flex-col md:flex-row gap-4">
|
||||
<div class="flex flex-col gap-4 grow">
|
||||
<div
|
||||
class="flex flex-col gap-2 grow p-4 rounded shadow-lg bg-white dark:bg-gray-700 text-gray-500 dark:text-white"
|
||||
>
|
||||
<form class="flex gap-4 flex-col lg:flex-row" action="./search">
|
||||
<div class="flex flex-col w-full grow">
|
||||
<div class="flex relative">
|
||||
<span
|
||||
class="inline-flex items-center px-3 border-t bg-white border-l border-b border-gray-300 text-gray-500 shadow-sm text-sm"
|
||||
>
|
||||
<svg
|
||||
width="15"
|
||||
height="15"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="24" height="24" fill="none" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M10 2C5.58172 2 2 5.58172 2 10C2 14.4183 5.58172 18 10 18C11.8487 18 13.551 17.3729 14.9056 16.3199L20.2929 21.7071C20.6834 22.0976 21.3166 22.0976 21.7071 21.7071C22.0976 21.3166 22.0976 20.6834 21.7071 20.2929L16.3199 14.9056C17.3729 13.551 18 11.8487 18 10C18 5.58172 14.4183 2 10 2Z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
id="query"
|
||||
name="query"
|
||||
class="flex-1 appearance-none rounded-none border border-gray-300 w-full py-2 px-4 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-purple-600 focus:border-transparent"
|
||||
placeholder="Query"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex relative min-w-[12em]">
|
||||
<span
|
||||
class="inline-flex items-center px-3 border-t bg-white border-l border-b border-gray-300 text-gray-500 shadow-sm text-sm"
|
||||
>
|
||||
<svg
|
||||
width="15"
|
||||
height="15"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.65517 2.22732C5.2225 2.34037 4.9438 2.50021 4.72718 2.71244C4.42179 3.01165 4.22268 3.43172 4.11382 4.225C4.00176 5.04159 4 6.12387 4 7.67568V16.2442C4.38867 15.9781 4.82674 15.7756 5.29899 15.6517C5.41296 15.6217 5.53103 15.5983 5.65517 15.5799V2.22732Z"
|
||||
/>
|
||||
<path
|
||||
d="M7.31034 15.5135C7.32206 15.5135 7.33382 15.5135 7.34563 15.5135L20 15.5135V7.67568C20 6.12387 19.9982 5.04159 19.8862 4.22499C19.7773 3.43172 19.5782 3.01165 19.2728 2.71244C18.9674 2.41324 18.5387 2.21816 17.729 2.11151C16.8955 2.00172 15.7908 2 14.2069 2H9.7931C8.79138 2 7.98133 2.00069 7.31034 2.02897V15.5135Z"
|
||||
/>
|
||||
<path
|
||||
d="M7.47341 17.1351C6.39395 17.1351 6.01657 17.1421 5.72738 17.218C4.93365 17.4264 4.30088 18.0044 4.02952 18.7558C4.0463 19.1382 4.07259 19.4746 4.11382 19.775C4.22268 20.5683 4.42179 20.9884 4.72718 21.2876C5.03258 21.5868 5.46135 21.7818 6.27103 21.8885C7.10452 21.9983 8.2092 22 9.7931 22H14.2069C15.7908 22 16.8955 21.9983 17.729 21.8885C18.5387 21.7818 18.9674 21.5868 19.2728 21.2876C19.5782 20.9884 19.7773 20.5683 19.8862 19.775C19.9776 19.1088 19.9956 18.2657 19.9991 17.1351H7.47341Z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<select
|
||||
class="flex-1 appearance-none rounded-none border border-gray-300 w-full py-2 px-4 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-purple-600 focus:border-transparent"
|
||||
id="book_type"
|
||||
name="book_type"
|
||||
>
|
||||
<option value="FICTION">Fiction</option>
|
||||
<option value="NON_FICTION">Non-Fiction</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="px-10 py-2 text-base font-semibold text-center text-white transition duration-200 ease-in bg-black shadow-md hover:text-black hover:bg-white focus:outline-none focus:ring-2"
|
||||
>
|
||||
<span class="w-full">Search</span>
|
||||
</button>
|
||||
</form>
|
||||
{{ if .SearchErrorMessage }}
|
||||
<span class="text-red-400 text-xs">{{ .SearchErrorMessage }}</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="inline-block min-w-full overflow-hidden rounded shadow">
|
||||
<table
|
||||
class="min-w-full leading-normal bg-white dark:bg-gray-700 text-sm md:text-sm"
|
||||
>
|
||||
<thead class="text-gray-800 dark:text-gray-400">
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
class="w-12 p-3 font-normal text-left uppercase border-b border-gray-200 dark:border-gray-800"
|
||||
></th>
|
||||
<th
|
||||
scope="col"
|
||||
class="p-3 font-normal text-left uppercase border-b border-gray-200 dark:border-gray-800"
|
||||
>
|
||||
Document
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="p-3 font-normal text-left uppercase border-b border-gray-200 dark:border-gray-800"
|
||||
>
|
||||
Series
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="p-3 font-normal text-left uppercase border-b border-gray-200 dark:border-gray-800"
|
||||
>
|
||||
Type
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="p-3 font-normal text-left uppercase border-b border-gray-200 dark:border-gray-800"
|
||||
>
|
||||
Size
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="p-3 hidden md:block font-normal text-left uppercase border-b border-gray-200 dark:border-gray-800"
|
||||
>
|
||||
Date
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-black dark:text-white">
|
||||
{{ if not .Data }}
|
||||
<tr>
|
||||
<td class="text-center p-3" colspan="6">No Results</td>
|
||||
</tr>
|
||||
{{ end }} {{range $item := .Data }}
|
||||
<tr>
|
||||
<td
|
||||
class="p-3 border-b border-gray-200 text-gray-500 dark:text-gray-500"
|
||||
>
|
||||
<form action="./search" method="POST">
|
||||
<input
|
||||
class="hidden"
|
||||
type="text"
|
||||
id="book_type"
|
||||
name="book_type"
|
||||
value="{{ $.BookType }}"
|
||||
/>
|
||||
<input
|
||||
class="hidden"
|
||||
type="text"
|
||||
id="title"
|
||||
name="title"
|
||||
value="{{ $item.Title }}"
|
||||
/>
|
||||
<input
|
||||
class="hidden"
|
||||
type="text"
|
||||
id="author"
|
||||
name="author"
|
||||
value="{{ $item.Author }}"
|
||||
/>
|
||||
<button name="id" value="{{ $item.ID }}">
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
class="cursor-pointer hover:text-gray-800 dark:hover:text-gray-100"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M2 12C2 7.28595 2 4.92893 3.46447 3.46447C4.92893 2 7.28595 2 12 2C16.714 2 19.0711 2 20.5355 3.46447C22 4.92893 22 7.28595 22 12C22 16.714 22 19.0711 20.5355 20.5355C19.0711 22 16.714 22 12 22C7.28595 22 4.92893 22 3.46447 20.5355C2 19.0711 2 16.714 2 12ZM12 6.25C12.4142 6.25 12.75 6.58579 12.75 7V12.1893L14.4697 10.4697C14.7626 10.1768 15.2374 10.1768 15.5303 10.4697C15.8232 10.7626 15.8232 11.2374 15.5303 11.5303L12.5303 14.5303C12.3897 14.671 12.1989 14.75 12 14.75C11.8011 14.75 11.6103 14.671 11.4697 14.5303L8.46967 11.5303C8.17678 11.2374 8.17678 10.7626 8.46967 10.4697C8.76256 10.1768 9.23744 10.1768 9.53033 10.4697L11.25 12.1893V7C11.25 6.58579 11.5858 6.25 12 6.25ZM8 16.25C7.58579 16.25 7.25 16.5858 7.25 17C7.25 17.4142 7.58579 17.75 8 17.75H16C16.4142 17.75 16.75 17.4142 16.75 17C16.75 16.5858 16.4142 16.25 16 16.25H8Z"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td class="p-3 border-b border-gray-200">
|
||||
{{ $item.Author }} - {{ $item.Title }}
|
||||
</td>
|
||||
<td class="p-3 border-b border-gray-200">
|
||||
<p>{{ or $item.Series "N/A" }}</p>
|
||||
</td>
|
||||
<td class="p-3 border-b border-gray-200">
|
||||
<p>{{ or $item.FileType "N/A" }}</p>
|
||||
</td>
|
||||
<td class="p-3 border-b border-gray-200">
|
||||
<p>{{ or $item.FileSize "N/A" }}</p>
|
||||
</td>
|
||||
<td class="hidden md:table-cell p-3 border-b border-gray-200">
|
||||
<p>{{ or $item.UploadDate "N/A" }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
@ -195,7 +195,11 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-black dark:text-white">
|
||||
{{ range $device := .Data.Devices }}
|
||||
{{ if not .Data.Devices }}
|
||||
<tr>
|
||||
<td class="text-center p-3" colspan="3">No Results</td>
|
||||
</tr>
|
||||
{{ end }} {{ range $device := .Data.Devices }}
|
||||
<tr>
|
||||
<td class="p-3 pl-0">
|
||||
<p>{{ $device.DeviceName }}</p>
|
||||
|
@ -1,8 +1,12 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
)
|
||||
|
||||
type UTCOffset struct {
|
||||
@ -82,3 +86,38 @@ func NiceSeconds(input int64) (result string) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Reimplemented KOReader Partial MD5 Calculation
|
||||
func CalculatePartialMD5(filePath string) (string, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
var step int64 = 1024
|
||||
var size int64 = 1024
|
||||
var buf bytes.Buffer
|
||||
|
||||
for i := -1; i <= 10; i++ {
|
||||
byteStep := make([]byte, size)
|
||||
|
||||
var newShift int64 = int64(i * 2)
|
||||
var newOffset int64
|
||||
if i == -1 {
|
||||
newOffset = 0
|
||||
} else {
|
||||
newOffset = step << newShift
|
||||
}
|
||||
|
||||
_, err := file.ReadAt(byteStep, newOffset)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
buf.Write(byteStep)
|
||||
}
|
||||
|
||||
allBytes := buf.Bytes()
|
||||
return fmt.Sprintf("%x", md5.Sum(allBytes)), nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user