Changed Project structure, added Hot Reload of gohtml files for Develop and embedding of gohtml files for Release
This commit is contained in:
15
internal/database/createDb.sql
Normal file
15
internal/database/createDb.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
create table if not exists Manga (
|
||||
ID integer not null primary key,
|
||||
Title text,
|
||||
TimeStampUnixEpoch int
|
||||
);
|
||||
|
||||
create table if not exists Chapter (
|
||||
ID integer not null primary key,
|
||||
MangaID integer not null,
|
||||
Url text not null,
|
||||
Name text null,
|
||||
Number int not null,
|
||||
TimeStampUnixEpoch int,
|
||||
foreign key(MangaID) references Manga(ID)
|
||||
);
|
||||
174
internal/database/database.go
Normal file
174
internal/database/database.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"sync"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type Manga struct {
|
||||
Id int
|
||||
Title string
|
||||
TimeStampUnix int64
|
||||
|
||||
// Not in DB
|
||||
LatestChapter *Chapter
|
||||
}
|
||||
|
||||
type Chapter struct {
|
||||
Id int
|
||||
Manga *Manga
|
||||
Url string
|
||||
Name string
|
||||
Number int
|
||||
TimeStampUnix int64
|
||||
}
|
||||
|
||||
type DatabaseManager struct {
|
||||
ConnectionString string
|
||||
db *sql.DB
|
||||
|
||||
Rw *sync.Mutex
|
||||
Mangas map[int]*Manga
|
||||
Chapters map[int]*Chapter
|
||||
|
||||
CreateIfNotExists bool
|
||||
}
|
||||
|
||||
func NewDatabase(connectionString string, createIfNotExists bool) DatabaseManager {
|
||||
return DatabaseManager{
|
||||
ConnectionString: connectionString,
|
||||
Rw: &sync.Mutex{},
|
||||
Mangas: make(map[int]*Manga),
|
||||
Chapters: make(map[int]*Chapter),
|
||||
CreateIfNotExists: createIfNotExists,
|
||||
}
|
||||
}
|
||||
|
||||
func (dbMgr *DatabaseManager) Open() error {
|
||||
db, err := sql.Open("sqlite3", dbMgr.ConnectionString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dbMgr.db = db
|
||||
if dbMgr.CreateIfNotExists {
|
||||
err = dbMgr.createDatabaseIfNotExists()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = dbMgr.load()
|
||||
return err
|
||||
}
|
||||
|
||||
func (dbMgr *DatabaseManager) Close() error {
|
||||
err := dbMgr.db.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dbMgr.Mangas = nil
|
||||
dbMgr.Chapters = nil
|
||||
dbMgr.db = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dbMgr *DatabaseManager) Save() error {
|
||||
db := dbMgr.db
|
||||
|
||||
dbMgr.Rw.Lock()
|
||||
defer dbMgr.Rw.Unlock()
|
||||
for _, m := range dbMgr.Mangas {
|
||||
count := 0
|
||||
err := db.QueryRow("SELECT COUNT(*) FROM Manga where ID = ?", m.Id).Scan(&count)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
_, err := db.Exec("INSERT INTO Manga(ID, Title, TimeStampUnixEpoch) values(?, ?, ?)", m.Id, m.Title, m.TimeStampUnix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
_, err := db.Exec("UPDATE Manga set Title = ?, TimeStampUnixEpoch = ? WHERE ID = ?", m.Title, m.TimeStampUnix, m.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range dbMgr.Chapters {
|
||||
count := 0
|
||||
err := db.QueryRow("SELECT COUNT(*) FROM Chapter where ID = ?", c.Id).Scan(&count)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
_, err := db.Exec("INSERT INTO Chapter(ID, MangaID, Url, Name, Number, TimeStampUnixEpoch) VALUES (?, ?, ?, ?, ?, ?)", c.Id, c.Manga.Id, c.Url, c.Name, c.Number, c.TimeStampUnix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
_, err = db.Exec("UPDATE Chapter set Name = ?, Url = ?, Number = ?, TimeStampUnixEpoch = ? where ID = ?", c.Name, c.Url, c.Number, c.TimeStampUnix, c.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:embed createDb.sql
|
||||
var createSql string
|
||||
|
||||
func (dbMgr *DatabaseManager) createDatabaseIfNotExists() error {
|
||||
_, err := dbMgr.db.Exec(createSql)
|
||||
return err
|
||||
}
|
||||
|
||||
func (dbMgr *DatabaseManager) load() error {
|
||||
db := dbMgr.db
|
||||
|
||||
dbMgr.Rw.Lock()
|
||||
defer dbMgr.Rw.Unlock()
|
||||
|
||||
rows, err := db.Query("SELECT * FROM Manga")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
manga := Manga{}
|
||||
if err = rows.Scan(&manga.Id, &manga.Title, &manga.TimeStampUnix); err != nil {
|
||||
return err
|
||||
}
|
||||
dbMgr.Mangas[manga.Id] = &manga
|
||||
}
|
||||
|
||||
rows, err = db.Query("SELECT * FROM Chapter")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
chapter := Chapter{}
|
||||
var mangaID int
|
||||
if err = rows.Scan(&chapter.Id, &mangaID, &chapter.Url, &chapter.Name, &chapter.Number, &chapter.TimeStampUnix); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chapter.Manga = dbMgr.Mangas[mangaID]
|
||||
if dbMgr.Mangas[mangaID].LatestChapter == nil || dbMgr.Mangas[mangaID].LatestChapter.TimeStampUnix < chapter.TimeStampUnix {
|
||||
dbMgr.Mangas[mangaID].LatestChapter = &chapter
|
||||
}
|
||||
|
||||
dbMgr.Chapters[chapter.Id] = &chapter
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
110
internal/provider/bato.go
Normal file
110
internal/provider/bato.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Bato struct{}
|
||||
|
||||
func (b *Bato) GetImageList(html string) ([]string, error) {
|
||||
reg, err := regexp.Compile(`<astro-island.*props=".*;imageFiles":\[1,"\[(.*)]"]`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := reg.FindStringSubmatch(html)
|
||||
|
||||
if len(m) <= 0 {
|
||||
return nil, errors.New("no more content")
|
||||
}
|
||||
match := m[1]
|
||||
|
||||
reg, err = regexp.Compile(`\[0,\\"([^&]*)\\"]`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matches := reg.FindAllStringSubmatch(match, -1)
|
||||
l := len(matches)
|
||||
result := make([]string, l)
|
||||
for i, m := range matches {
|
||||
result[i] = m[1]
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (b *Bato) GetHtml(titleSubUrl string) (string, error) {
|
||||
url := fmt.Sprintf("https://bato.to%s?load=2", titleSubUrl)
|
||||
resp, err := http.Get(url)
|
||||
|
||||
// TODO: Testing for above 300 is dirty
|
||||
if err != nil && resp.StatusCode > 300 {
|
||||
return "", errors.New("could not get html")
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
fmt.Printf("Could not close body because: %v\n", err)
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
all, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
h := string(all)
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (b *Bato) GetNext(html string) (subUrl string, err error) {
|
||||
reg, err := regexp.Compile(`<a data-hk="0-6-0" .*? href="(.*?)["']`)
|
||||
match := reg.FindStringSubmatch(html)
|
||||
|
||||
return match[1], err
|
||||
}
|
||||
|
||||
func (b *Bato) GetPrev(html string) (subUrl string, err error) {
|
||||
reg, err := regexp.Compile(`<a data-hk="0-5-0" .*? href="(.*?)["']`)
|
||||
match := reg.FindStringSubmatch(html)
|
||||
|
||||
return match[1], err
|
||||
}
|
||||
|
||||
func (b *Bato) GetTitleAndChapter(url string) (title string, chapter string, err error) {
|
||||
reg, err := regexp.Compile(`/title/\d*-(.*?)/\d*-(.*)`)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
matches := reg.FindAllStringSubmatch(url, -1)
|
||||
if len(matches) <= 0 {
|
||||
return "", "", errors.New("no title or chapter found")
|
||||
}
|
||||
|
||||
return matches[0][1], matches[0][2], nil
|
||||
}
|
||||
|
||||
func (b *Bato) GetTitleIdAndChapterId(url string) (titleId int, chapterId int, err error) {
|
||||
reg, err := regexp.Compile(`/title/(\d*)-.*?/(\d*)-.*`)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
matches := reg.FindAllStringSubmatch(url, -1)
|
||||
if len(matches) <= 0 {
|
||||
return 0, 0, errors.New("no title or chapter found")
|
||||
}
|
||||
t, err := strconv.Atoi(matches[0][1])
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
c, err := strconv.Atoi(matches[0][2])
|
||||
|
||||
return t, c, err
|
||||
}
|
||||
10
internal/provider/provider.go
Normal file
10
internal/provider/provider.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package provider
|
||||
|
||||
type Provider interface {
|
||||
GetImageList(html string) (imageUrls []string, err error)
|
||||
GetHtml(url string) (html string, err error)
|
||||
GetNext(html string) (url string, err error)
|
||||
GetPrev(html string) (url string, err error)
|
||||
GetTitleAndChapter(url string) (title string, chapter string, err error)
|
||||
GetTitleIdAndChapterId(url string) (titleId int, chapterId int, err error)
|
||||
}
|
||||
380
internal/server/server.go
Normal file
380
internal/server/server.go
Normal file
@@ -0,0 +1,380 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"golang.org/x/text/language"
|
||||
"html/template"
|
||||
"io"
|
||||
"mangaGetter/internal/database"
|
||||
"mangaGetter/internal/provider"
|
||||
"mangaGetter/internal/view"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
PrevViewModel *view.ImageViewModel
|
||||
CurrViewModel *view.ImageViewModel
|
||||
NextViewModel *view.ImageViewModel
|
||||
|
||||
ImageBuffers map[string]*bytes.Buffer
|
||||
Mutex *sync.Mutex
|
||||
|
||||
NextSubUrl string
|
||||
CurrSubUrl string
|
||||
PrevSubUrl string
|
||||
|
||||
Provider provider.Provider
|
||||
|
||||
IsFirst bool
|
||||
IsLast bool
|
||||
|
||||
DbMgr *database.DatabaseManager
|
||||
|
||||
// I'm not even sure if this helps.
|
||||
// If you press next and then prev too fast you still lock yourself out
|
||||
NextReady chan bool
|
||||
PrevReady chan bool
|
||||
}
|
||||
|
||||
func (s *Server) HandleImage(w http.ResponseWriter, r *http.Request) {
|
||||
u := r.PathValue("url")
|
||||
s.Mutex.Lock()
|
||||
buf := s.ImageBuffers[u]
|
||||
if buf == nil {
|
||||
fmt.Printf("url: %s is nil\n", u)
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "image/webp")
|
||||
_, err := w.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
s.Mutex.Unlock()
|
||||
}
|
||||
|
||||
func (s *Server) HandleNext(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("Received Next")
|
||||
|
||||
if s.PrevViewModel != nil {
|
||||
go func(viewModel view.ImageViewModel, s *Server) {
|
||||
s.Mutex.Lock()
|
||||
for _, img := range viewModel.Images {
|
||||
delete(s.ImageBuffers, img.Path)
|
||||
}
|
||||
s.Mutex.Unlock()
|
||||
fmt.Println("Cleaned out of scope Last")
|
||||
}(*s.PrevViewModel, s)
|
||||
}
|
||||
|
||||
s.PrevViewModel = s.CurrViewModel
|
||||
s.CurrViewModel = s.NextViewModel
|
||||
s.PrevSubUrl = s.CurrSubUrl
|
||||
s.CurrSubUrl = s.NextSubUrl
|
||||
|
||||
<-s.NextReady
|
||||
|
||||
go s.LoadNext()
|
||||
|
||||
http.Redirect(w, r, "/current/", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func (s *Server) LoadNext() {
|
||||
c, err := s.Provider.GetHtml(s.CurrSubUrl)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
next, err := s.Provider.GetNext(c)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
html, err := s.Provider.GetHtml(next)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
imagesNext, err := s.AppendImagesToBuf(html)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
title, chapter, err := s.Provider.GetTitleAndChapter(next)
|
||||
if err != nil {
|
||||
title = "Unknown"
|
||||
chapter = "ch_?"
|
||||
}
|
||||
|
||||
full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1)
|
||||
|
||||
s.NextViewModel = &view.ImageViewModel{Images: imagesNext, Title: full}
|
||||
|
||||
s.NextSubUrl = next
|
||||
fmt.Println("Loaded next")
|
||||
s.NextReady <- true
|
||||
}
|
||||
|
||||
func (s *Server) LoadPrev() {
|
||||
c, err := s.Provider.GetHtml(s.CurrSubUrl)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
prev, err := s.Provider.GetPrev(c)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
html, err := s.Provider.GetHtml(prev)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
imagesNext, err := s.AppendImagesToBuf(html)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
title, chapter, err := s.Provider.GetTitleAndChapter(prev)
|
||||
if err != nil {
|
||||
title = "Unknown"
|
||||
chapter = "ch_?"
|
||||
}
|
||||
|
||||
full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1)
|
||||
|
||||
s.PrevViewModel = &view.ImageViewModel{Images: imagesNext, Title: full}
|
||||
|
||||
s.PrevSubUrl = prev
|
||||
fmt.Println("Loaded prev")
|
||||
s.PrevReady <- true
|
||||
}
|
||||
|
||||
func (s *Server) HandlePrev(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("Received Prev")
|
||||
if s.NextViewModel != nil {
|
||||
go func(viewModel view.ImageViewModel, s *Server) {
|
||||
s.Mutex.Lock()
|
||||
for _, img := range viewModel.Images {
|
||||
delete(s.ImageBuffers, img.Path)
|
||||
}
|
||||
s.Mutex.Unlock()
|
||||
fmt.Println("Cleaned out of scope Last")
|
||||
}(*s.NextViewModel, s)
|
||||
}
|
||||
|
||||
s.NextViewModel = s.CurrViewModel
|
||||
s.CurrViewModel = s.PrevViewModel
|
||||
s.NextSubUrl = s.CurrSubUrl
|
||||
s.CurrSubUrl = s.PrevSubUrl
|
||||
|
||||
<-s.PrevReady
|
||||
|
||||
go s.LoadPrev()
|
||||
|
||||
http.Redirect(w, r, "/current/", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func (s *Server) HandleCurrent(w http.ResponseWriter, _ *http.Request) {
|
||||
tmpl := template.Must(view.GetViewTemplate(view.Viewer))
|
||||
|
||||
s.DbMgr.Rw.Lock()
|
||||
defer s.DbMgr.Rw.Unlock()
|
||||
|
||||
mangaId, chapterId, err := s.Provider.GetTitleIdAndChapterId(s.CurrSubUrl)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
title, chapter, err := s.Provider.GetTitleAndChapter(s.CurrSubUrl)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
var manga *database.Manga
|
||||
if s.DbMgr.Mangas[mangaId] == nil {
|
||||
manga = &database.Manga{
|
||||
Id: mangaId,
|
||||
Title: title,
|
||||
TimeStampUnix: time.Now().Unix(),
|
||||
}
|
||||
s.DbMgr.Mangas[mangaId] = manga
|
||||
} else {
|
||||
manga = s.DbMgr.Mangas[mangaId]
|
||||
s.DbMgr.Mangas[mangaId].TimeStampUnix = time.Now().Unix()
|
||||
}
|
||||
|
||||
if s.DbMgr.Chapters[chapterId] == nil {
|
||||
chapterNumberStr := strings.Replace(chapter, "ch_", "", 1)
|
||||
number, err := strconv.Atoi(chapterNumberStr)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
number = 0
|
||||
}
|
||||
|
||||
s.DbMgr.Chapters[chapterId] = &database.Chapter{
|
||||
Id: chapterId,
|
||||
Manga: manga,
|
||||
Url: s.CurrSubUrl,
|
||||
Name: chapter,
|
||||
Number: number,
|
||||
TimeStampUnix: time.Now().Unix(),
|
||||
}
|
||||
} else {
|
||||
s.DbMgr.Chapters[chapterId].TimeStampUnix = time.Now().Unix()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = tmpl.Execute(w, s.CurrViewModel)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) HandleNew(w http.ResponseWriter, r *http.Request) {
|
||||
title := r.PathValue("title")
|
||||
chapter := r.PathValue("chapter")
|
||||
|
||||
url := fmt.Sprintf("/title/%s/%s", title, chapter)
|
||||
|
||||
s.Mutex.Lock()
|
||||
s.ImageBuffers = make(map[string]*bytes.Buffer)
|
||||
s.Mutex.Unlock()
|
||||
s.CurrSubUrl = url
|
||||
s.PrevSubUrl = ""
|
||||
s.NextSubUrl = ""
|
||||
s.LoadCurr()
|
||||
|
||||
go s.LoadNext()
|
||||
go s.LoadPrev()
|
||||
|
||||
http.Redirect(w, r, "/current/", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func (s *Server) LoadCurr() {
|
||||
html, err := s.Provider.GetHtml(s.CurrSubUrl)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
imagesCurr, err := s.AppendImagesToBuf(html)
|
||||
|
||||
title, chapter, err := s.Provider.GetTitleAndChapter(s.CurrSubUrl)
|
||||
if err != nil {
|
||||
title = "Unknown"
|
||||
chapter = "ch_?"
|
||||
}
|
||||
|
||||
full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1)
|
||||
|
||||
s.CurrViewModel = &view.ImageViewModel{Images: imagesCurr, Title: full}
|
||||
fmt.Println("Loaded current")
|
||||
}
|
||||
|
||||
func (s *Server) AppendImagesToBuf(html string) ([]view.Image, error) {
|
||||
imgList, err := s.Provider.GetImageList(html)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
images := make([]view.Image, len(imgList))
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
for i, url := range imgList {
|
||||
wg.Add(1)
|
||||
go func(i int, url string, wg *sync.WaitGroup) {
|
||||
buf, err := addFileToRam(url)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
name := filepath.Base(url)
|
||||
s.Mutex.Lock()
|
||||
s.ImageBuffers[name] = buf
|
||||
s.Mutex.Unlock()
|
||||
images[i] = view.Image{Path: name, Index: i}
|
||||
wg.Done()
|
||||
}(i, url, &wg)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return images, nil
|
||||
}
|
||||
|
||||
func (s *Server) HandleMenu(w http.ResponseWriter, _ *http.Request) {
|
||||
tmpl := template.Must(view.GetViewTemplate(view.Menu))
|
||||
|
||||
s.DbMgr.Rw.Lock()
|
||||
defer s.DbMgr.Rw.Unlock()
|
||||
|
||||
all := s.DbMgr.Mangas
|
||||
l := len(all)
|
||||
mangaViewModels := make([]view.MangaViewModel, l)
|
||||
counter := 0
|
||||
|
||||
for _, manga := range all {
|
||||
title := cases.Title(language.English, cases.Compact).String(strings.Replace(manga.Title, "-", " ", -1))
|
||||
|
||||
mangaViewModels[counter] = view.MangaViewModel{
|
||||
Title: title,
|
||||
Number: manga.LatestChapter.Number,
|
||||
// I Hate this time Format... 15 = hh, 04 = mm, 02 = DD, 01 = MM, 06 == YY
|
||||
LastTime: time.Unix(manga.TimeStampUnix, 0).Format("15:04 (02-01-06)"),
|
||||
Url: manga.LatestChapter.Url,
|
||||
}
|
||||
counter++
|
||||
}
|
||||
|
||||
menuViewModel := view.MenuViewModel{
|
||||
Mangas: mangaViewModels,
|
||||
}
|
||||
|
||||
err := tmpl.Execute(w, menuViewModel)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) HandleExit(w http.ResponseWriter, r *http.Request) {
|
||||
err := s.DbMgr.Save()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func addFileToRam(url string) (*bytes.Buffer, error) {
|
||||
// Get the data
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// Write the body to file
|
||||
_, err = io.Copy(buf, resp.Body)
|
||||
return buf, err
|
||||
}
|
||||
21
internal/view/Views/menu.gohtml
Normal file
21
internal/view/Views/menu.gohtml
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Main Menu</title>
|
||||
</head>
|
||||
<body>
|
||||
<form method="post" action="/new/">
|
||||
|
||||
</form>
|
||||
|
||||
{{range .Mangas}}
|
||||
<div>
|
||||
<text>Title: {{.Title}}</text>
|
||||
<text>Current Chapter: {{.Number}}</text>
|
||||
<text>Last Accessed: {{.LastTime}}</text>
|
||||
<a href="/new/{{.Url}}}" class="button">Go to last Chapter</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
||||
103
internal/view/Views/viewer.gohtml
Normal file
103
internal/view/Views/viewer.gohtml
Normal file
@@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{.Title}}</title>
|
||||
|
||||
<style>
|
||||
body {background-color: #171717}
|
||||
|
||||
.scroll-container {
|
||||
overflow: auto;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
width: 100%;
|
||||
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.scroll-container img {
|
||||
padding: 0;
|
||||
display: block;
|
||||
/*min-width: 20%;*/
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 100%;
|
||||
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/*
|
||||
* I have no clue what css is, jesus christ ...
|
||||
*/
|
||||
.center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 50%;
|
||||
padding: 10px;
|
||||
|
||||
vertical-align: middle;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.button-36 {
|
||||
background-image: linear-gradient(92.88deg, #455EB5 9.16%, #5643CC 43.89%, #673FD7 64.72%);
|
||||
border-radius: 8px;
|
||||
border-style: none;
|
||||
box-sizing: border-box;
|
||||
color: #FFFFFF;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
font-family: "Inter UI","SF Pro Display",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Open Sans","Helvetica Neue",sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
height: 4rem;
|
||||
padding: 0 1.6rem;
|
||||
text-align: center;
|
||||
text-shadow: rgba(0, 0, 0, 0.25) 0 3px 8px;
|
||||
transition: all .5s;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.button-36:hover {
|
||||
box-shadow: rgba(80, 63, 205, 0.5) 0 1px 30px;
|
||||
transition-duration: .1s;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.button-36 {
|
||||
padding: 0 2.6rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="center">
|
||||
<form method="post" action="/prev">
|
||||
<input type="submit" name="Prev" value="Prev" class="button-36">
|
||||
<input type="submit" name="Exit" value="Exit" class="button-36" formaction="/exit">
|
||||
<input type="submit" name="Next" value="Next" class="button-36" formaction="/next">
|
||||
</form>
|
||||
</div>
|
||||
<div class="scroll-container">
|
||||
{{range .Images}}
|
||||
<img src="/img/{{.Path}}" alt="img_{{.Index}}"/>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="center">
|
||||
<form method="post" action="/prev">
|
||||
<input type="submit" name="Prev" value="Prev" class="button-36">
|
||||
<input type="submit" name="Exit" value="Exit" class="button-36" formaction="/exit">
|
||||
<input type="submit" name="Next" value="Next" class="button-36" formaction="/next">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
25
internal/view/embedded.go
Normal file
25
internal/view/embedded.go
Normal file
@@ -0,0 +1,25 @@
|
||||
//go:build !Develop
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"errors"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
//go:embed Views/menu.gohtml
|
||||
var menu string
|
||||
|
||||
//go:embed Views/viewer.gohtml
|
||||
var viewer string
|
||||
|
||||
func GetViewTemplate(view View) (*template.Template, error) {
|
||||
switch view {
|
||||
case Menu:
|
||||
return template.New("menu").Parse(menu)
|
||||
case Viewer:
|
||||
return template.New("viewer").Parse(viewer)
|
||||
}
|
||||
return nil, errors.New("invalid view")
|
||||
}
|
||||
18
internal/view/hotreload.go
Normal file
18
internal/view/hotreload.go
Normal file
@@ -0,0 +1,18 @@
|
||||
//go:build Develop
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
)
|
||||
|
||||
func GetViewTemplate(view View) (*template.Template, error) {
|
||||
var path string
|
||||
switch view {
|
||||
case Menu:
|
||||
path = "internal/view/Views/menu.gohtml"
|
||||
case Viewer:
|
||||
path = "internal/view/Views/viewer.gohtml"
|
||||
}
|
||||
return template.ParseFiles(path)
|
||||
}
|
||||
22
internal/view/viewmodels.go
Normal file
22
internal/view/viewmodels.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package view
|
||||
|
||||
type Image struct {
|
||||
Path string
|
||||
Index int
|
||||
}
|
||||
|
||||
type ImageViewModel struct {
|
||||
Title string
|
||||
Images []Image
|
||||
}
|
||||
|
||||
type MangaViewModel struct {
|
||||
Title string
|
||||
Number int
|
||||
LastTime string
|
||||
Url string
|
||||
}
|
||||
|
||||
type MenuViewModel struct {
|
||||
Mangas []MangaViewModel
|
||||
}
|
||||
8
internal/view/views.go
Normal file
8
internal/view/views.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package view
|
||||
|
||||
type View int
|
||||
|
||||
const (
|
||||
Menu View = iota
|
||||
Viewer View = iota
|
||||
)
|
||||
Reference in New Issue
Block a user