Changed Project structure, added Hot Reload of gohtml files for Develop and embedding of gohtml files for Release
This commit is contained in:
@@ -7,41 +7,24 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
|
||||
"mangaGetter/internal/database"
|
||||
"mangaGetter/internal/provider"
|
||||
"mangaGetter/internal/server"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func main() {
|
||||
db := NewDatabase("db.sqlite", true)
|
||||
db := database.NewDatabase("db.sqlite", true)
|
||||
err := db.Open()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
server := Server{
|
||||
s := server.Server{
|
||||
ImageBuffers: make(map[string]*bytes.Buffer),
|
||||
NextReady: make(chan bool),
|
||||
PrevReady: make(chan bool),
|
||||
Provider: &Bato{},
|
||||
Provider: &provider.Bato{},
|
||||
DbMgr: &db,
|
||||
Mutex: &sync.Mutex{},
|
||||
}
|
||||
@@ -55,13 +38,13 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
http.HandleFunc("/", server.HandleMenu)
|
||||
http.HandleFunc("/new/title/{title}/{chapter}", server.HandleNew)
|
||||
http.HandleFunc("/current/", server.HandleCurrent)
|
||||
http.HandleFunc("/img/{url}/", server.HandleImage)
|
||||
http.HandleFunc("POST /next", server.HandleNext)
|
||||
http.HandleFunc("POST /prev", server.HandlePrev)
|
||||
http.HandleFunc("POST /exit", server.HandleExit)
|
||||
http.HandleFunc("/", s.HandleMenu)
|
||||
http.HandleFunc("/new/title/{title}/{chapter}", s.HandleNew)
|
||||
http.HandleFunc("/current/", s.HandleCurrent)
|
||||
http.HandleFunc("/img/{url}/", s.HandleImage)
|
||||
http.HandleFunc("POST /next", s.HandleNext)
|
||||
http.HandleFunc("POST /prev", s.HandlePrev)
|
||||
http.HandleFunc("POST /exit", s.HandleExit)
|
||||
|
||||
fmt.Println("Server starting...")
|
||||
err = http.ListenAndServe(":8000", nil)
|
||||
@@ -71,7 +54,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func Close(db *DatabaseManager) {
|
||||
func Close(db *database.DatabaseManager) {
|
||||
fmt.Println("Attempting to save and close DB")
|
||||
err := db.Save()
|
||||
if err != nil {
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
@@ -30,7 +30,7 @@ type DatabaseManager struct {
|
||||
ConnectionString string
|
||||
db *sql.DB
|
||||
|
||||
rw *sync.Mutex
|
||||
Rw *sync.Mutex
|
||||
Mangas map[int]*Manga
|
||||
Chapters map[int]*Chapter
|
||||
|
||||
@@ -40,7 +40,7 @@ type DatabaseManager struct {
|
||||
func NewDatabase(connectionString string, createIfNotExists bool) DatabaseManager {
|
||||
return DatabaseManager{
|
||||
ConnectionString: connectionString,
|
||||
rw: &sync.Mutex{},
|
||||
Rw: &sync.Mutex{},
|
||||
Mangas: make(map[int]*Manga),
|
||||
Chapters: make(map[int]*Chapter),
|
||||
CreateIfNotExists: createIfNotExists,
|
||||
@@ -78,8 +78,8 @@ func (dbMgr *DatabaseManager) Close() error {
|
||||
func (dbMgr *DatabaseManager) Save() error {
|
||||
db := dbMgr.db
|
||||
|
||||
dbMgr.rw.Lock()
|
||||
defer dbMgr.rw.Unlock()
|
||||
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)
|
||||
@@ -134,8 +134,8 @@ func (dbMgr *DatabaseManager) createDatabaseIfNotExists() error {
|
||||
func (dbMgr *DatabaseManager) load() error {
|
||||
db := dbMgr.db
|
||||
|
||||
dbMgr.rw.Lock()
|
||||
defer dbMgr.rw.Unlock()
|
||||
dbMgr.Rw.Lock()
|
||||
defer dbMgr.Rw.Unlock()
|
||||
|
||||
rows, err := db.Query("SELECT * FROM Manga")
|
||||
if err != nil {
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -6,15 +6,9 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
type Bato struct{}
|
||||
|
||||
func (b *Bato) GetImageList(html string) ([]string, error) {
|
||||
@@ -81,3 +75,36 @@ func (b *Bato) GetPrev(html string) (subUrl string, err error) {
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
package main
|
||||
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"
|
||||
@@ -16,9 +20,9 @@ import (
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
PrevViewModel *ImageViewModel
|
||||
CurrViewModel *ImageViewModel
|
||||
NextViewModel *ImageViewModel
|
||||
PrevViewModel *view.ImageViewModel
|
||||
CurrViewModel *view.ImageViewModel
|
||||
NextViewModel *view.ImageViewModel
|
||||
|
||||
ImageBuffers map[string]*bytes.Buffer
|
||||
Mutex *sync.Mutex
|
||||
@@ -27,12 +31,12 @@ type Server struct {
|
||||
CurrSubUrl string
|
||||
PrevSubUrl string
|
||||
|
||||
Provider Provider
|
||||
Provider provider.Provider
|
||||
|
||||
IsFirst bool
|
||||
IsLast bool
|
||||
|
||||
DbMgr *DatabaseManager
|
||||
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
|
||||
@@ -62,7 +66,7 @@ func (s *Server) HandleNext(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("Received Next")
|
||||
|
||||
if s.PrevViewModel != nil {
|
||||
go func(viewModel ImageViewModel, s *Server) {
|
||||
go func(viewModel view.ImageViewModel, s *Server) {
|
||||
s.Mutex.Lock()
|
||||
for _, img := range viewModel.Images {
|
||||
delete(s.ImageBuffers, img.Path)
|
||||
@@ -109,7 +113,7 @@ func (s *Server) LoadNext() {
|
||||
return
|
||||
}
|
||||
|
||||
title, chapter, err := getTitleAndChapter(next)
|
||||
title, chapter, err := s.Provider.GetTitleAndChapter(next)
|
||||
if err != nil {
|
||||
title = "Unknown"
|
||||
chapter = "ch_?"
|
||||
@@ -117,7 +121,7 @@ func (s *Server) LoadNext() {
|
||||
|
||||
full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1)
|
||||
|
||||
s.NextViewModel = &ImageViewModel{Images: imagesNext, Title: full}
|
||||
s.NextViewModel = &view.ImageViewModel{Images: imagesNext, Title: full}
|
||||
|
||||
s.NextSubUrl = next
|
||||
fmt.Println("Loaded next")
|
||||
@@ -147,7 +151,7 @@ func (s *Server) LoadPrev() {
|
||||
return
|
||||
}
|
||||
|
||||
title, chapter, err := getTitleAndChapter(prev)
|
||||
title, chapter, err := s.Provider.GetTitleAndChapter(prev)
|
||||
if err != nil {
|
||||
title = "Unknown"
|
||||
chapter = "ch_?"
|
||||
@@ -155,7 +159,7 @@ func (s *Server) LoadPrev() {
|
||||
|
||||
full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1)
|
||||
|
||||
s.PrevViewModel = &ImageViewModel{Images: imagesNext, Title: full}
|
||||
s.PrevViewModel = &view.ImageViewModel{Images: imagesNext, Title: full}
|
||||
|
||||
s.PrevSubUrl = prev
|
||||
fmt.Println("Loaded prev")
|
||||
@@ -165,7 +169,7 @@ func (s *Server) LoadPrev() {
|
||||
func (s *Server) HandlePrev(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("Received Prev")
|
||||
if s.NextViewModel != nil {
|
||||
go func(viewModel ImageViewModel, s *Server) {
|
||||
go func(viewModel view.ImageViewModel, s *Server) {
|
||||
s.Mutex.Lock()
|
||||
for _, img := range viewModel.Images {
|
||||
delete(s.ImageBuffers, img.Path)
|
||||
@@ -188,22 +192,22 @@ func (s *Server) HandlePrev(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (s *Server) HandleCurrent(w http.ResponseWriter, _ *http.Request) {
|
||||
tmpl := template.Must(template.ParseFiles("viewer.gohtml"))
|
||||
tmpl := template.Must(view.GetViewTemplate(view.Viewer))
|
||||
|
||||
s.DbMgr.rw.Lock()
|
||||
defer s.DbMgr.rw.Unlock()
|
||||
s.DbMgr.Rw.Lock()
|
||||
defer s.DbMgr.Rw.Unlock()
|
||||
|
||||
mangaId, chapterId, err := getMangaIdAndChapterId(s.CurrSubUrl)
|
||||
mangaId, chapterId, err := s.Provider.GetTitleIdAndChapterId(s.CurrSubUrl)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
title, chapter, err := getTitleAndChapter(s.CurrSubUrl)
|
||||
title, chapter, err := s.Provider.GetTitleAndChapter(s.CurrSubUrl)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
var manga *Manga
|
||||
var manga *database.Manga
|
||||
if s.DbMgr.Mangas[mangaId] == nil {
|
||||
manga = &Manga{
|
||||
manga = &database.Manga{
|
||||
Id: mangaId,
|
||||
Title: title,
|
||||
TimeStampUnix: time.Now().Unix(),
|
||||
@@ -222,7 +226,7 @@ func (s *Server) HandleCurrent(w http.ResponseWriter, _ *http.Request) {
|
||||
number = 0
|
||||
}
|
||||
|
||||
s.DbMgr.Chapters[chapterId] = &Chapter{
|
||||
s.DbMgr.Chapters[chapterId] = &database.Chapter{
|
||||
Id: chapterId,
|
||||
Manga: manga,
|
||||
Url: s.CurrSubUrl,
|
||||
@@ -270,7 +274,7 @@ func (s *Server) LoadCurr() {
|
||||
|
||||
imagesCurr, err := s.AppendImagesToBuf(html)
|
||||
|
||||
title, chapter, err := getTitleAndChapter(s.CurrSubUrl)
|
||||
title, chapter, err := s.Provider.GetTitleAndChapter(s.CurrSubUrl)
|
||||
if err != nil {
|
||||
title = "Unknown"
|
||||
chapter = "ch_?"
|
||||
@@ -278,17 +282,17 @@ func (s *Server) LoadCurr() {
|
||||
|
||||
full := strings.Replace(title, "-", " ", -1) + " - " + strings.Replace(chapter, "_", " ", -1)
|
||||
|
||||
s.CurrViewModel = &ImageViewModel{Images: imagesCurr, Title: full}
|
||||
s.CurrViewModel = &view.ImageViewModel{Images: imagesCurr, Title: full}
|
||||
fmt.Println("Loaded current")
|
||||
}
|
||||
|
||||
func (s *Server) AppendImagesToBuf(html string) ([]Image, error) {
|
||||
func (s *Server) AppendImagesToBuf(html string) ([]view.Image, error) {
|
||||
imgList, err := s.Provider.GetImageList(html)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
images := make([]Image, len(imgList))
|
||||
images := make([]view.Image, len(imgList))
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
for i, url := range imgList {
|
||||
@@ -302,7 +306,7 @@ func (s *Server) AppendImagesToBuf(html string) ([]Image, error) {
|
||||
s.Mutex.Lock()
|
||||
s.ImageBuffers[name] = buf
|
||||
s.Mutex.Unlock()
|
||||
images[i] = Image{Path: name, Index: i}
|
||||
images[i] = view.Image{Path: name, Index: i}
|
||||
wg.Done()
|
||||
}(i, url, &wg)
|
||||
}
|
||||
@@ -311,21 +315,21 @@ func (s *Server) AppendImagesToBuf(html string) ([]Image, error) {
|
||||
return images, nil
|
||||
}
|
||||
|
||||
func (s *Server) HandleMenu(w http.ResponseWriter, r *http.Request) {
|
||||
tmpl := template.Must(template.ParseFiles("menu.gohtml"))
|
||||
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()
|
||||
s.DbMgr.Rw.Lock()
|
||||
defer s.DbMgr.Rw.Unlock()
|
||||
|
||||
all := s.DbMgr.Mangas
|
||||
l := len(all)
|
||||
mangaViewModels := make([]MangaViewModel, l)
|
||||
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] = MangaViewModel{
|
||||
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
|
||||
@@ -335,7 +339,7 @@ func (s *Server) HandleMenu(w http.ResponseWriter, r *http.Request) {
|
||||
counter++
|
||||
}
|
||||
|
||||
menuViewModel := MenuViewModel{
|
||||
menuViewModel := view.MenuViewModel{
|
||||
Mangas: mangaViewModels,
|
||||
}
|
||||
|
||||
@@ -354,3 +358,23 @@ func (s *Server) HandleExit(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
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
|
||||
}
|
||||
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
|
||||
)
|
||||
64
util.go
64
util.go
@@ -1,64 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func 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 getMangaIdAndChapterId(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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user