<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"runtime/debug"
	"strings"
	"sync"
	"time"

	"github.com/geek1011/kepubify/kepub"
	"github.com/julienschmidt/httprouter"
)

//go:generate go-bindata-assetfs static/...

// Server is a BookBrowser server.
type Server struct {
	Books     *BookList
	booksLock *sync.RWMutex
	BookDir   string
	CoverDir  string
	Addr      string
	Verbose   bool
	router    *httprouter.Router
}

// NewServer creates a new BookBrowser server. It will not index the books automatically.
func NewServer(addr, bookdir, coverdir string, verbose bool) *Server {
	s := &Server{
		Books:     &BookList{},
		booksLock: &sync.RWMutex{},
		BookDir:   bookdir,
		Addr:      addr,
		CoverDir:  coverdir,
		Verbose:   verbose,
		router:    httprouter.New(),
	}

	s.initRouter()

	return s
}

// printLog runs log.Printf if verbose is true.
func (s *Server) printLog(format string, v ...interface{}) {
	if s.Verbose {
		log.Printf(format, v...)
	}
}

// RefreshBookIndex refreshes the book index
func (s *Server) RefreshBookIndex() error {
	s.printLog("Locking book index\n")
	s.booksLock.Lock()
	defer s.printLog("Unlocking book index\n")
	defer s.booksLock.Unlock()

	books, err := NewBookListFromDir(s.BookDir, s.CoverDir, s.Verbose)
	if err != nil {
		debug.FreeOSMemory()
		return err
	}

	s.Books = books
	debug.FreeOSMemory()
	return nil
}

// Serve starts the BookBrowser server. It does not return unless there is an error.
func (s *Server) Serve() error {
	s.printLog("Serving on %s\n", s.Addr)
	err := http.ListenAndServe(s.Addr, s.router)
	if err != nil {
		return err
	}
	return nil
}

// initRouter initializes the router for the BookBrowser server.
func (s *Server) initRouter() {
	s.router = httprouter.New()

	s.router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		http.Redirect(w, r, "/books/", http.StatusTemporaryRedirect)
	})

	s.router.GET("/random", s.handleRandom)

	s.router.GET("/search", s.handleSearch)

	s.router.GET("/books.json", s.handleBooksJSON)

	s.router.GET("/books", s.handleBookList)
	s.router.GET("/books/:id", s.handleBook)

	s.router.GET("/authors", s.handleAuthorList)
	s.router.GET("/authors/:id", s.handleAuthor)

	s.router.GET("/series", s.handleSeriesList)
	s.router.GET("/series/:id", s.handleSeries)

	s.router.GET("/download", s.handleDownloadList)
	s.router.GET("/download/:filename", s.handleDownload)

	s.router.ServeFiles("/static/*filepath", assetFS())
	s.router.ServeFiles("/covers/*filepath", http.Dir(s.CoverDir))
}

func (s *Server) handleDownloadList(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	s.booksLock.RLock()
	defer s.booksLock.RUnlock()

	w.Header().Set("Content-Type", "text/html")
	var buf bytes.Buffer
	buf.WriteString(`
<!DOCTYPE html>
<html>
<head>
<title>BookBrowser</title>
<style>
a,
a:link,
a:visited {
display:  block;
white-space: nowrap;
text-overflow: ellipsis;
color: inherit;
text-decoration: none;
font-family: sans-serif;
padding: 5px 7px;
background:  #FAFAFA;
border-bottom: 1px solid #DDDDDD;
cursor: pointer;
}

a:hover,
a:active {
background: #EEEEEE;
}

html, body {
background: #FAFAFA;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
	`)
	sbl := s.Books.Sorted(func(a Book, b Book) bool {
		return a.Title < b.Title
	})
	for _, b := range sbl {
		buf.WriteString(fmt.Sprintf("<a href=\"/download/%s.%s\">%s - %s - %s (%v)</a>", b.ID, b.FileType, b.Title, b.Author, b.Series.Name, b.Series.Index))
	}
	buf.WriteString(`
</body>
</html>
	`)
	io.WriteString(w, buf.String())
}

func (s *Server) handleDownload(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	s.booksLock.RLock()
	defer s.booksLock.RUnlock()

	bid := p.ByName("filename")<commit_msg>Fixed download list author name<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"runtime/debug"
	"strings"
	"sync"
	"time"

	"github.com/geek1011/kepubify/kepub"
	"github.com/julienschmidt/httprouter"
)

//go:generate go-bindata-assetfs static/...

// Server is a BookBrowser server.
type Server struct {
	Books     *BookList
	booksLock *sync.RWMutex
	BookDir   string
	CoverDir  string
	Addr      string
	Verbose   bool
	router    *httprouter.Router
}

// NewServer creates a new BookBrowser server. It will not index the books automatically.
func NewServer(addr, bookdir, coverdir string, verbose bool) *Server {
	s := &Server{
		Books:     &BookList{},
		booksLock: &sync.RWMutex{},
		BookDir:   bookdir,
		Addr:      addr,
		CoverDir:  coverdir,
		Verbose:   verbose,
		router:    httprouter.New(),
	}

	s.initRouter()

	return s
}

// printLog runs log.Printf if verbose is true.
func (s *Server) printLog(format string, v ...interface{}) {
	if s.Verbose {
		log.Printf(format, v...)
	}
}

// RefreshBookIndex refreshes the book index
func (s *Server) RefreshBookIndex() error {
	s.printLog("Locking book index\n")
	s.booksLock.Lock()
	defer s.printLog("Unlocking book index\n")
	defer s.booksLock.Unlock()

	books, err := NewBookListFromDir(s.BookDir, s.CoverDir, s.Verbose)
	if err != nil {
		debug.FreeOSMemory()
		return err
	}

	s.Books = books
	debug.FreeOSMemory()
	return nil
}

// Serve starts the BookBrowser server. It does not return unless there is an error.
func (s *Server) Serve() error {
	s.printLog("Serving on %s\n", s.Addr)
	err := http.ListenAndServe(s.Addr, s.router)
	if err != nil {
		return err
	}
	return nil
}

// initRouter initializes the router for the BookBrowser server.
func (s *Server) initRouter() {
	s.router = httprouter.New()

	s.router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		http.Redirect(w, r, "/books/", http.StatusTemporaryRedirect)
	})

	s.router.GET("/random", s.handleRandom)

	s.router.GET("/search", s.handleSearch)

	s.router.GET("/books.json", s.handleBooksJSON)

	s.router.GET("/books", s.handleBookList)
	s.router.GET("/books/:id", s.handleBook)

	s.router.GET("/authors", s.handleAuthorList)
	s.router.GET("/authors/:id", s.handleAuthor)

	s.router.GET("/series", s.handleSeriesList)
	s.router.GET("/series/:id", s.handleSeries)

	s.router.GET("/download", s.handleDownloadList)
	s.router.GET("/download/:filename", s.handleDownload)

	s.router.ServeFiles("/static/*filepath", assetFS())
	s.router.ServeFiles("/covers/*filepath", http.Dir(s.CoverDir))
}

func (s *Server) handleDownloadList(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	s.booksLock.RLock()
	defer s.booksLock.RUnlock()

	w.Header().Set("Content-Type", "text/html")
	var buf bytes.Buffer
	buf.WriteString(`
<!DOCTYPE html>
<html>
<head>
<title>BookBrowser</title>
<style>
a,
a:link,
a:visited {
display:  block;
white-space: nowrap;
text-overflow: ellipsis;
color: inherit;
text-decoration: none;
font-family: sans-serif;
padding: 5px 7px;
background:  #FAFAFA;
border-bottom: 1px solid #DDDDDD;
cursor: pointer;
}

a:hover,
a:active {
background: #EEEEEE;
}

html, body {
background: #FAFAFA;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
	`)
	sbl := s.Books.Sorted(func(a Book, b Book) bool {
		return a.Title < b.Title
	})
	for _, b := range sbl {
		buf.WriteString(fmt.Sprintf("<a href=\"/download/%s.%s\">%s - %s - %s (%v)</a>", b.ID, b.FileType, b.Title, b.Author.Name, b.Series.Name, b.Series.Index))
	}
	buf.WriteString(`
</body>
</html>
	`)
	io.WriteString(w, buf.String())
}

func (s *Server) handleDownload(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	s.booksLock.RLock()
	defer s.booksLock.RUnlock()

	bid := p.ByName("filename")
{'fraction_non_alphanumeric': 0.16343273863784347, 'fraction_numerical': 0.0023440552155228546, 'mean_word_length': 4.032765399737877, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 15, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4752312', 'n_tokens_mistral': 2981, 'n_tokens_neox': 2896, 'n_words': 661}