mirror of
https://git.seedno.de/seednode/roulette.git
synced 2025-02-14 09:17:08 +00:00
Added .swf support, fallback to filetype detection by extension if mime type detection fails
This commit is contained in:
parent
7942ea85b5
commit
a29390aa76
28 changed files with 717 additions and 354 deletions
188
cmd/debug.go
188
cmd/debug.go
|
@ -1,188 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright © 2023 Seednode <seednode@seedno.de>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
|
||||||
"github.com/yosssi/gohtml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func serveDebugHtml(args []string, index *Index, paginate bool) httprouter.Handle {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
|
||||||
|
|
||||||
startTime := time.Now()
|
|
||||||
|
|
||||||
indexDump := index.Index()
|
|
||||||
|
|
||||||
fileCount := len(indexDump)
|
|
||||||
|
|
||||||
var startIndex, stopIndex int
|
|
||||||
|
|
||||||
page, err := strconv.Atoi(p.ByName("page"))
|
|
||||||
if err != nil || page <= 0 {
|
|
||||||
startIndex = 0
|
|
||||||
stopIndex = fileCount
|
|
||||||
} else {
|
|
||||||
startIndex = ((page - 1) * int(pageLength))
|
|
||||||
stopIndex = (startIndex + int(pageLength))
|
|
||||||
}
|
|
||||||
|
|
||||||
if startIndex > (fileCount - 1) {
|
|
||||||
indexDump = []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if stopIndex > fileCount {
|
|
||||||
stopIndex = fileCount
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.SliceStable(indexDump, func(p, q int) bool {
|
|
||||||
return strings.ToLower(indexDump[p]) < strings.ToLower(indexDump[q])
|
|
||||||
})
|
|
||||||
|
|
||||||
var htmlBody strings.Builder
|
|
||||||
htmlBody.WriteString(`<!DOCTYPE html><html lang="en"><head>`)
|
|
||||||
htmlBody.WriteString(FaviconHtml)
|
|
||||||
htmlBody.WriteString(`<style>a{text-decoration:none;height:100%;width:100%;color:inherit;cursor:pointer}`)
|
|
||||||
htmlBody.WriteString(`table,td,tr{border:1px solid black;border-collapse:collapse}td{white-space:nowrap;padding:.5em}</style>`)
|
|
||||||
htmlBody.WriteString(fmt.Sprintf("<title>Index contains %d files</title></head><body><table>", fileCount))
|
|
||||||
if len(indexDump) > 0 {
|
|
||||||
for _, v := range indexDump[startIndex:stopIndex] {
|
|
||||||
var shouldSort = ""
|
|
||||||
|
|
||||||
if sorting {
|
|
||||||
shouldSort = "?sort=asc"
|
|
||||||
}
|
|
||||||
htmlBody.WriteString(fmt.Sprintf("<tr><td><a href=\"%s%s%s\">%s</a></td></tr>\n", MediaPrefix, v, shouldSort, v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pageLength != 0 {
|
|
||||||
var firstPage int = 1
|
|
||||||
var lastPage int
|
|
||||||
|
|
||||||
if fileCount%int(pageLength) == 0 {
|
|
||||||
lastPage = fileCount / int(pageLength)
|
|
||||||
} else {
|
|
||||||
lastPage = (fileCount / int(pageLength)) + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if paginate {
|
|
||||||
var prevStatus, nextStatus string = "", ""
|
|
||||||
|
|
||||||
if page <= 1 {
|
|
||||||
prevStatus = " disabled"
|
|
||||||
}
|
|
||||||
|
|
||||||
if page >= lastPage {
|
|
||||||
nextStatus = " disabled"
|
|
||||||
}
|
|
||||||
|
|
||||||
prevPage := page - 1
|
|
||||||
if prevPage < 1 {
|
|
||||||
prevPage = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
nextPage := page + 1
|
|
||||||
if nextPage > lastPage {
|
|
||||||
nextPage = fileCount / int(pageLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\">First</button>",
|
|
||||||
firstPage))
|
|
||||||
|
|
||||||
htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\"%s>Prev</button>",
|
|
||||||
prevPage,
|
|
||||||
prevStatus))
|
|
||||||
|
|
||||||
htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\"%s>Next</button>",
|
|
||||||
nextPage,
|
|
||||||
nextStatus))
|
|
||||||
|
|
||||||
htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\">Last</button>",
|
|
||||||
lastPage))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
htmlBody.WriteString(`</table></body></html>`)
|
|
||||||
|
|
||||||
b, err := io.WriteString(w, gohtml.Format(htmlBody.String()))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if verbose {
|
|
||||||
fmt.Printf("%s | Served HTML debug page (%s) to %s in %s\n",
|
|
||||||
startTime.Format(LogDate),
|
|
||||||
humanReadableSize(b),
|
|
||||||
realIP(r),
|
|
||||||
time.Since(startTime).Round(time.Microsecond),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveDebugJson(args []string, index *Index) httprouter.Handle {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
startTime := time.Now()
|
|
||||||
|
|
||||||
indexDump := index.Index()
|
|
||||||
|
|
||||||
fileCount := len(indexDump)
|
|
||||||
|
|
||||||
sort.SliceStable(indexDump, func(p, q int) bool {
|
|
||||||
return strings.ToLower(indexDump[p]) < strings.ToLower(indexDump[q])
|
|
||||||
})
|
|
||||||
|
|
||||||
var startIndex, stopIndex int
|
|
||||||
|
|
||||||
page, err := strconv.Atoi(p.ByName("page"))
|
|
||||||
if err != nil || page <= 0 {
|
|
||||||
startIndex = 0
|
|
||||||
stopIndex = fileCount
|
|
||||||
} else {
|
|
||||||
startIndex = ((page - 1) * int(pageLength))
|
|
||||||
stopIndex = (startIndex + int(pageLength))
|
|
||||||
}
|
|
||||||
|
|
||||||
if startIndex > (fileCount - 1) {
|
|
||||||
indexDump = []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if stopIndex > fileCount {
|
|
||||||
stopIndex = fileCount
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := json.MarshalIndent(indexDump[startIndex:stopIndex], "", " ")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
|
|
||||||
serverError(w, r, nil)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write(response)
|
|
||||||
|
|
||||||
if verbose {
|
|
||||||
fmt.Printf("%s | Served JSON debug page (%s) to %s in %s\n",
|
|
||||||
startTime.Format(LogDate),
|
|
||||||
humanReadableSize(len(response)),
|
|
||||||
realIP(r),
|
|
||||||
time.Since(startTime).Round(time.Microsecond),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -35,7 +35,7 @@ func newErrorPage(title, body string) string {
|
||||||
func notFound(w http.ResponseWriter, r *http.Request, filePath string) error {
|
func notFound(w http.ResponseWriter, r *http.Request, filePath string) error {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
if verbose {
|
if Verbose {
|
||||||
fmt.Printf("%s | Unavailable file %s requested by %s\n",
|
fmt.Printf("%s | Unavailable file %s requested by %s\n",
|
||||||
startTime.Format(LogDate),
|
startTime.Format(LogDate),
|
||||||
filePath,
|
filePath,
|
||||||
|
@ -57,7 +57,7 @@ func notFound(w http.ResponseWriter, r *http.Request, filePath string) error {
|
||||||
func serverError(w http.ResponseWriter, r *http.Request, i interface{}) {
|
func serverError(w http.ResponseWriter, r *http.Request, i interface{}) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
if verbose {
|
if Verbose {
|
||||||
fmt.Printf("%s | Invalid request for %s from %s\n",
|
fmt.Printf("%s | Invalid request for %s from %s\n",
|
||||||
startTime.Format(LogDate),
|
startTime.Format(LogDate),
|
||||||
r.URL.Path,
|
r.URL.Path,
|
||||||
|
|
26
cmd/files.go
26
cmd/files.go
|
@ -128,7 +128,7 @@ func appendPath(directory, path string, files *Files, stats *ScanStats, register
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats, types *formats.SupportedFormats) error {
|
func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats, types *formats.SupportedFormats) error {
|
||||||
shouldCache := cache && filters.IsEmpty()
|
shouldCache := Cache && filters.IsEmpty()
|
||||||
|
|
||||||
absolutePath, err := filepath.Abs(path)
|
absolutePath, err := filepath.Abs(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -180,7 +180,7 @@ func appendPaths(path string, files *Files, filters *Filters, stats *ScanStats,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFile(paths []string, filters *Filters, sortOrder string, Regexes *Regexes, index *Index, registeredFormats *formats.SupportedFormats) (string, error) {
|
func newFile(paths []string, filters *Filters, sortOrder string, Regexes *Regexes, index *FileIndex, registeredFormats *formats.SupportedFormats) (string, error) {
|
||||||
filePath, err := pickFile(paths, filters, sortOrder, index, registeredFormats)
|
filePath, err := pickFile(paths, filters, sortOrder, index, registeredFormats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil
|
return "", nil
|
||||||
|
@ -272,8 +272,10 @@ func splitPath(path string, Regexes *Regexes) (*Path, error) {
|
||||||
func tryExtensions(p *Path, registeredFormats *formats.SupportedFormats) (string, error) {
|
func tryExtensions(p *Path, registeredFormats *formats.SupportedFormats) (string, error) {
|
||||||
var fileName string
|
var fileName string
|
||||||
|
|
||||||
for _, extension := range registeredFormats.Extensions() {
|
for _, format := range registeredFormats.Extensions {
|
||||||
|
for _, extension := range format.Extensions {
|
||||||
fileName = fmt.Sprintf("%s%.3d%s", p.base, p.number, extension)
|
fileName = fmt.Sprintf("%s%.3d%s", p.base, p.number, extension)
|
||||||
|
}
|
||||||
|
|
||||||
exists, err := fileExists(fileName)
|
exists, err := fileExists(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -310,7 +312,7 @@ func pathIsValid(filePath string, paths []string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case verbose && !matchesPrefix:
|
case Verbose && !matchesPrefix:
|
||||||
fmt.Printf("%s | Error: Failed to serve file outside specified path(s): %s\n",
|
fmt.Printf("%s | Error: Failed to serve file outside specified path(s): %s\n",
|
||||||
time.Now().Format(LogDate),
|
time.Now().Format(LogDate),
|
||||||
filePath,
|
filePath,
|
||||||
|
@ -333,7 +335,7 @@ func pathHasSupportedFiles(path string, registeredFormats *formats.SupportedForm
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case !recursive && info.IsDir() && p != path:
|
case !Recursive && info.IsDir() && p != path:
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
case !info.IsDir():
|
case !info.IsDir():
|
||||||
registered, _, _, err := formats.FileType(p, registeredFormats)
|
registered, _, _, err := formats.FileType(p, registeredFormats)
|
||||||
|
@ -390,7 +392,7 @@ func scanPath(path string, files *Files, filters *Filters, stats *ScanStats, con
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case !recursive && info.IsDir() && p != path:
|
case !Recursive && info.IsDir() && p != path:
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
case !info.IsDir():
|
case !info.IsDir():
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
@ -419,7 +421,7 @@ func scanPath(path string, files *Files, filters *Filters, stats *ScanStats, con
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if files > 0 && (files < minimumFileCount) || (files > maximumFileCount) {
|
if files > 0 && (files < MinimumFileCount) || (files > MaximumFileCount) {
|
||||||
// This count will not otherwise include the parent directory itself, so increment by one
|
// This count will not otherwise include the parent directory itself, so increment by one
|
||||||
stats.directoriesSkipped.Add(directories + 1)
|
stats.directoriesSkipped.Add(directories + 1)
|
||||||
stats.filesSkipped.Add(files)
|
stats.filesSkipped.Add(files)
|
||||||
|
@ -442,8 +444,8 @@ func scanPath(path string, files *Files, filters *Filters, stats *ScanStats, con
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fileList(paths []string, filters *Filters, sort string, index *Index, types *formats.SupportedFormats) ([]string, bool) {
|
func fileList(paths []string, filters *Filters, sort string, index *FileIndex, types *formats.SupportedFormats) ([]string, bool) {
|
||||||
if cache && filters.IsEmpty() && !index.IsEmpty() {
|
if Cache && filters.IsEmpty() && !index.IsEmpty() {
|
||||||
return index.Index(), true
|
return index.Index(), true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -496,7 +498,7 @@ func fileList(paths []string, filters *Filters, sort string, index *Index, types
|
||||||
return []string{}, false
|
return []string{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if verbose {
|
if Verbose {
|
||||||
fmt.Printf("%s | Indexed %d/%d files across %d/%d directories in %s\n",
|
fmt.Printf("%s | Indexed %d/%d files across %d/%d directories in %s\n",
|
||||||
time.Now().Format(LogDate),
|
time.Now().Format(LogDate),
|
||||||
stats.filesMatched.Load(),
|
stats.filesMatched.Load(),
|
||||||
|
@ -507,7 +509,7 @@ func fileList(paths []string, filters *Filters, sort string, index *Index, types
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cache && filters.IsEmpty() {
|
if Cache && filters.IsEmpty() {
|
||||||
index.setIndex(fileList)
|
index.setIndex(fileList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -556,7 +558,7 @@ func prepareDirectories(files *Files, sort string) []string {
|
||||||
return directories
|
return directories
|
||||||
}
|
}
|
||||||
|
|
||||||
func pickFile(args []string, filters *Filters, sort string, index *Index, registeredFormats *formats.SupportedFormats) (string, error) {
|
func pickFile(args []string, filters *Filters, sort string, index *FileIndex, registeredFormats *formats.SupportedFormats) (string, error) {
|
||||||
fileList, fromCache := fileList(args, filters, sort, index, registeredFormats)
|
fileList, fromCache := fileList(args, filters, sort, index, registeredFormats)
|
||||||
|
|
||||||
fileCount := len(fileList)
|
fileCount := len(fileList)
|
||||||
|
|
199
cmd/index.go
199
cmd/index.go
|
@ -6,21 +6,29 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"github.com/klauspost/compress/zstd"
|
"github.com/klauspost/compress/zstd"
|
||||||
|
"github.com/yosssi/gohtml"
|
||||||
"seedno.de/seednode/roulette/formats"
|
"seedno.de/seednode/roulette/formats"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Index struct {
|
type FileIndex struct {
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
list []string
|
list []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Index) Index() []string {
|
func (i *FileIndex) Index() []string {
|
||||||
i.mutex.RLock()
|
i.mutex.RLock()
|
||||||
val := i.list
|
val := i.list
|
||||||
i.mutex.RUnlock()
|
i.mutex.RUnlock()
|
||||||
|
@ -28,7 +36,7 @@ func (i *Index) Index() []string {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Index) Remove(path string) {
|
func (i *FileIndex) Remove(path string) {
|
||||||
i.mutex.RLock()
|
i.mutex.RLock()
|
||||||
tempIndex := make([]string, len(i.list))
|
tempIndex := make([]string, len(i.list))
|
||||||
copy(tempIndex, i.list)
|
copy(tempIndex, i.list)
|
||||||
|
@ -52,25 +60,25 @@ func (i *Index) Remove(path string) {
|
||||||
i.mutex.Unlock()
|
i.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Index) setIndex(val []string) {
|
func (i *FileIndex) setIndex(val []string) {
|
||||||
i.mutex.Lock()
|
i.mutex.Lock()
|
||||||
i.list = val
|
i.list = val
|
||||||
i.mutex.Unlock()
|
i.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Index) generateCache(args []string, registeredFormats *formats.SupportedFormats) {
|
func (i *FileIndex) generateCache(args []string, registeredFormats *formats.SupportedFormats) {
|
||||||
i.mutex.Lock()
|
i.mutex.Lock()
|
||||||
i.list = []string{}
|
i.list = []string{}
|
||||||
i.mutex.Unlock()
|
i.mutex.Unlock()
|
||||||
|
|
||||||
fileList(args, &Filters{}, "", i, registeredFormats)
|
fileList(args, &Filters{}, "", i, registeredFormats)
|
||||||
|
|
||||||
if cache && cacheFile != "" {
|
if Cache && CacheFile != "" {
|
||||||
i.Export(cacheFile)
|
i.Export(CacheFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Index) IsEmpty() bool {
|
func (i *FileIndex) IsEmpty() bool {
|
||||||
i.mutex.RLock()
|
i.mutex.RLock()
|
||||||
length := len(i.list)
|
length := len(i.list)
|
||||||
i.mutex.RUnlock()
|
i.mutex.RUnlock()
|
||||||
|
@ -78,7 +86,7 @@ func (i *Index) IsEmpty() bool {
|
||||||
return length == 0
|
return length == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Index) Export(path string) error {
|
func (i *FileIndex) Export(path string) error {
|
||||||
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -102,7 +110,7 @@ func (i *Index) Export(path string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Index) Import(path string) error {
|
func (i *FileIndex) Import(path string) error {
|
||||||
file, err := os.OpenFile(path, os.O_RDONLY, 0600)
|
file, err := os.OpenFile(path, os.O_RDONLY, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -130,7 +138,7 @@ func (i *Index) Import(path string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveCacheClear(args []string, index *Index, registeredFormats *formats.SupportedFormats) httprouter.Handle {
|
func serveCacheClear(args []string, index *FileIndex, registeredFormats *formats.SupportedFormats) httprouter.Handle {
|
||||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
index.generateCache(args, registeredFormats)
|
index.generateCache(args, registeredFormats)
|
||||||
|
|
||||||
|
@ -139,3 +147,172 @@ func serveCacheClear(args []string, index *Index, registeredFormats *formats.Sup
|
||||||
w.Write([]byte("Ok"))
|
w.Write([]byte("Ok"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func serveIndexHtml(args []string, index *FileIndex, paginate bool) httprouter.Handle {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
indexDump := index.Index()
|
||||||
|
|
||||||
|
fileCount := len(indexDump)
|
||||||
|
|
||||||
|
var startIndex, stopIndex int
|
||||||
|
|
||||||
|
page, err := strconv.Atoi(p.ByName("page"))
|
||||||
|
if err != nil || page <= 0 {
|
||||||
|
startIndex = 0
|
||||||
|
stopIndex = fileCount
|
||||||
|
} else {
|
||||||
|
startIndex = ((page - 1) * int(PageLength))
|
||||||
|
stopIndex = (startIndex + int(PageLength))
|
||||||
|
}
|
||||||
|
|
||||||
|
if startIndex > (fileCount - 1) {
|
||||||
|
indexDump = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if stopIndex > fileCount {
|
||||||
|
stopIndex = fileCount
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(indexDump, func(p, q int) bool {
|
||||||
|
return strings.ToLower(indexDump[p]) < strings.ToLower(indexDump[q])
|
||||||
|
})
|
||||||
|
|
||||||
|
var htmlBody strings.Builder
|
||||||
|
htmlBody.WriteString(`<!DOCTYPE html><html lang="en"><head>`)
|
||||||
|
htmlBody.WriteString(FaviconHtml)
|
||||||
|
htmlBody.WriteString(`<style>a{text-decoration:none;height:100%;width:100%;color:inherit;cursor:pointer}`)
|
||||||
|
htmlBody.WriteString(`table,td,tr{border:1px solid black;border-collapse:collapse}td{white-space:nowrap;padding:.5em}</style>`)
|
||||||
|
htmlBody.WriteString(fmt.Sprintf("<title>Index contains %d files</title></head><body><table>", fileCount))
|
||||||
|
if len(indexDump) > 0 {
|
||||||
|
for _, v := range indexDump[startIndex:stopIndex] {
|
||||||
|
var shouldSort = ""
|
||||||
|
|
||||||
|
if Sorting {
|
||||||
|
shouldSort = "?sort=asc"
|
||||||
|
}
|
||||||
|
htmlBody.WriteString(fmt.Sprintf("<tr><td><a href=\"%s%s%s\">%s</a></td></tr>\n", MediaPrefix, v, shouldSort, v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if PageLength != 0 {
|
||||||
|
var firstPage int = 1
|
||||||
|
var lastPage int
|
||||||
|
|
||||||
|
if fileCount%int(PageLength) == 0 {
|
||||||
|
lastPage = fileCount / int(PageLength)
|
||||||
|
} else {
|
||||||
|
lastPage = (fileCount / int(PageLength)) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if paginate {
|
||||||
|
var prevStatus, nextStatus string = "", ""
|
||||||
|
|
||||||
|
if page <= 1 {
|
||||||
|
prevStatus = " disabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
if page >= lastPage {
|
||||||
|
nextStatus = " disabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
prevPage := page - 1
|
||||||
|
if prevPage < 1 {
|
||||||
|
prevPage = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPage := page + 1
|
||||||
|
if nextPage > lastPage {
|
||||||
|
nextPage = fileCount / int(PageLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\">First</button>",
|
||||||
|
firstPage))
|
||||||
|
|
||||||
|
htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\"%s>Prev</button>",
|
||||||
|
prevPage,
|
||||||
|
prevStatus))
|
||||||
|
|
||||||
|
htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\"%s>Next</button>",
|
||||||
|
nextPage,
|
||||||
|
nextStatus))
|
||||||
|
|
||||||
|
htmlBody.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '/html/%d';\">Last</button>",
|
||||||
|
lastPage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlBody.WriteString(`</table></body></html>`)
|
||||||
|
|
||||||
|
b, err := io.WriteString(w, gohtml.Format(htmlBody.String()))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if Verbose {
|
||||||
|
fmt.Printf("%s | Served HTML index page (%s) to %s in %s\n",
|
||||||
|
startTime.Format(LogDate),
|
||||||
|
humanReadableSize(b),
|
||||||
|
realIP(r),
|
||||||
|
time.Since(startTime).Round(time.Microsecond),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveIndexJson(args []string, index *FileIndex) httprouter.Handle {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
indexDump := index.Index()
|
||||||
|
|
||||||
|
fileCount := len(indexDump)
|
||||||
|
|
||||||
|
sort.SliceStable(indexDump, func(p, q int) bool {
|
||||||
|
return strings.ToLower(indexDump[p]) < strings.ToLower(indexDump[q])
|
||||||
|
})
|
||||||
|
|
||||||
|
var startIndex, stopIndex int
|
||||||
|
|
||||||
|
page, err := strconv.Atoi(p.ByName("page"))
|
||||||
|
if err != nil || page <= 0 {
|
||||||
|
startIndex = 0
|
||||||
|
stopIndex = fileCount
|
||||||
|
} else {
|
||||||
|
startIndex = ((page - 1) * int(PageLength))
|
||||||
|
stopIndex = (startIndex + int(PageLength))
|
||||||
|
}
|
||||||
|
|
||||||
|
if startIndex > (fileCount - 1) {
|
||||||
|
indexDump = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if stopIndex > fileCount {
|
||||||
|
stopIndex = fileCount
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := json.MarshalIndent(indexDump[startIndex:stopIndex], "", " ")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
serverError(w, r, nil)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(response)
|
||||||
|
|
||||||
|
if Verbose {
|
||||||
|
fmt.Printf("%s | Served JSON index page (%s) to %s in %s\n",
|
||||||
|
startTime.Format(LogDate),
|
||||||
|
humanReadableSize(len(response)),
|
||||||
|
realIP(r),
|
||||||
|
time.Since(startTime).Round(time.Microsecond),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
104
cmd/root.go
104
cmd/root.go
|
@ -12,45 +12,46 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version string = "0.69.4"
|
ReleaseVersion string = "0.70.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
all bool
|
All bool
|
||||||
audio bool
|
Audio bool
|
||||||
bind string
|
Bind string
|
||||||
cache bool
|
Cache bool
|
||||||
cacheFile string
|
CacheFile string
|
||||||
debug bool
|
Filtering bool
|
||||||
filtering bool
|
Flash bool
|
||||||
images bool
|
Images bool
|
||||||
maximumFileCount uint64
|
Index bool
|
||||||
minimumFileCount uint64
|
MaximumFileCount uint64
|
||||||
pageLength uint64
|
MinimumFileCount uint64
|
||||||
port uint16
|
PageLength uint64
|
||||||
profile bool
|
Port uint16
|
||||||
recursive bool
|
Profile bool
|
||||||
refreshInterval string
|
Recursive bool
|
||||||
russian bool
|
RefreshInterval string
|
||||||
sorting bool
|
Russian bool
|
||||||
statistics bool
|
Sorting bool
|
||||||
statisticsFile string
|
Statistics bool
|
||||||
text bool
|
StatisticsFile string
|
||||||
verbose bool
|
Text bool
|
||||||
version bool
|
Verbose bool
|
||||||
videos bool
|
Version bool
|
||||||
|
Videos bool
|
||||||
|
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
Use: "roulette <path> [path]...",
|
Use: "roulette <path> [path]...",
|
||||||
Short: "Serves random media from the specified directories.",
|
Short: "Serves random media from the specified directories.",
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
PreRun: func(cmd *cobra.Command, args []string) {
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
if debug {
|
if Index {
|
||||||
cmd.MarkFlagRequired("cache")
|
cmd.MarkFlagRequired("cache")
|
||||||
}
|
}
|
||||||
|
|
||||||
if refreshInterval != "" {
|
if RefreshInterval != "" {
|
||||||
interval, err := time.ParseDuration(refreshInterval)
|
interval, err := time.ParseDuration(RefreshInterval)
|
||||||
if err != nil || interval < 500*time.Millisecond {
|
if err != nil || interval < 500*time.Millisecond {
|
||||||
log.Fatal(ErrIncorrectRefreshInterval)
|
log.Fatal(ErrIncorrectRefreshInterval)
|
||||||
}
|
}
|
||||||
|
@ -75,29 +76,30 @@ func Execute() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.Flags().BoolVar(&all, "all", false, "enable all supported file types")
|
rootCmd.Flags().BoolVar(&All, "all", false, "enable all supported file types")
|
||||||
rootCmd.Flags().BoolVar(&audio, "audio", false, "enable support for audio files")
|
rootCmd.Flags().BoolVar(&Audio, "audio", false, "enable support for audio files")
|
||||||
rootCmd.Flags().StringVarP(&bind, "bind", "b", "0.0.0.0", "address to bind to")
|
rootCmd.Flags().StringVarP(&Bind, "bind", "b", "0.0.0.0", "address to bind to")
|
||||||
rootCmd.Flags().BoolVarP(&cache, "cache", "c", false, "generate directory cache at startup")
|
rootCmd.Flags().BoolVarP(&Cache, "cache", "c", false, "generate directory cache at startup")
|
||||||
rootCmd.Flags().StringVar(&cacheFile, "cache-file", "", "path to optional persistent cache file")
|
rootCmd.Flags().StringVar(&CacheFile, "cache-file", "", "path to optional persistent cache file")
|
||||||
rootCmd.Flags().BoolVarP(&debug, "debug", "d", false, "expose debug endpoint")
|
rootCmd.Flags().BoolVarP(&Filtering, "filter", "f", false, "enable filtering")
|
||||||
rootCmd.Flags().BoolVarP(&filtering, "filter", "f", false, "enable filtering")
|
rootCmd.Flags().BoolVar(&Flash, "flash", true, "enable support for shockwave flash files (via ruffle)")
|
||||||
rootCmd.Flags().BoolVar(&images, "images", true, "enable support for image files")
|
rootCmd.Flags().BoolVar(&Images, "images", true, "enable support for image files")
|
||||||
rootCmd.Flags().Uint64Var(&maximumFileCount, "maximum-files", 1<<64-1, "skip directories with file counts above this value")
|
rootCmd.Flags().BoolVarP(&Index, "index", "i", false, "expose index endpoints")
|
||||||
rootCmd.Flags().Uint64Var(&minimumFileCount, "minimum-files", 1, "skip directories with file counts below this value")
|
rootCmd.Flags().Uint64Var(&MaximumFileCount, "maximum-files", 1<<64-1, "skip directories with file counts above this value")
|
||||||
rootCmd.Flags().Uint64Var(&pageLength, "page-length", 0, "pagination length for statistics and debug pages")
|
rootCmd.Flags().Uint64Var(&MinimumFileCount, "minimum-files", 1, "skip directories with file counts below this value")
|
||||||
rootCmd.Flags().Uint16VarP(&port, "port", "p", 8080, "port to listen on")
|
rootCmd.Flags().Uint64Var(&PageLength, "page-length", 0, "pagination length for statistics and debug pages")
|
||||||
rootCmd.Flags().BoolVar(&profile, "profile", false, "register net/http/pprof handlers")
|
rootCmd.Flags().Uint16VarP(&Port, "port", "p", 8080, "port to listen on")
|
||||||
rootCmd.Flags().BoolVarP(&recursive, "recursive", "r", false, "recurse into subdirectories")
|
rootCmd.Flags().BoolVar(&Profile, "profile", false, "register net/http/pprof handlers")
|
||||||
rootCmd.Flags().StringVar(&refreshInterval, "refresh-interval", "", "force refresh interval equal to this duration (minimum 500ms)")
|
rootCmd.Flags().BoolVarP(&Recursive, "recursive", "r", false, "recurse into subdirectories")
|
||||||
rootCmd.Flags().BoolVar(&russian, "russian", false, "remove selected images after serving")
|
rootCmd.Flags().StringVar(&RefreshInterval, "refresh-interval", "", "force refresh interval equal to this duration (minimum 500ms)")
|
||||||
rootCmd.Flags().BoolVarP(&sorting, "sort", "s", false, "enable sorting")
|
rootCmd.Flags().BoolVar(&Russian, "russian", false, "remove selected images after serving")
|
||||||
rootCmd.Flags().BoolVar(&statistics, "stats", false, "expose stats endpoint")
|
rootCmd.Flags().BoolVarP(&Sorting, "sort", "s", false, "enable sorting")
|
||||||
rootCmd.Flags().StringVar(&statisticsFile, "stats-file", "", "path to optional persistent stats file")
|
rootCmd.Flags().BoolVar(&Statistics, "stats", false, "expose stats endpoint")
|
||||||
rootCmd.Flags().BoolVar(&text, "text", false, "enable support for text files")
|
rootCmd.Flags().StringVar(&StatisticsFile, "stats-file", "", "path to optional persistent stats file")
|
||||||
rootCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "log accessed files to stdout")
|
rootCmd.Flags().BoolVar(&Text, "text", false, "enable support for text files")
|
||||||
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "display version and exit")
|
rootCmd.Flags().BoolVarP(&Verbose, "verbose", "v", false, "log accessed files to stdout")
|
||||||
rootCmd.Flags().BoolVar(&videos, "video", false, "enable support for video files")
|
rootCmd.Flags().BoolVarP(&Version, "version", "V", false, "display version and exit")
|
||||||
|
rootCmd.Flags().BoolVar(&Videos, "video", false, "enable support for video files")
|
||||||
|
|
||||||
rootCmd.Flags().SetInterspersed(true)
|
rootCmd.Flags().SetInterspersed(true)
|
||||||
|
|
||||||
|
@ -109,5 +111,5 @@ func init() {
|
||||||
})
|
})
|
||||||
|
|
||||||
rootCmd.SetVersionTemplate("roulette v{{.Version}}\n")
|
rootCmd.SetVersionTemplate("roulette v{{.Version}}\n")
|
||||||
rootCmd.Version = Version
|
rootCmd.Version = ReleaseVersion
|
||||||
}
|
}
|
||||||
|
|
10
cmd/stats.go
10
cmd/stats.go
|
@ -118,8 +118,8 @@ func (s *ServeStats) ListFiles(page int) ([]byte, error) {
|
||||||
startIndex = 0
|
startIndex = 0
|
||||||
stopIndex = len(stats.List) - 1
|
stopIndex = len(stats.List) - 1
|
||||||
} else {
|
} else {
|
||||||
startIndex = ((page - 1) * int(pageLength))
|
startIndex = ((page - 1) * int(PageLength))
|
||||||
stopIndex = (startIndex + int(pageLength))
|
stopIndex = (startIndex + int(PageLength))
|
||||||
}
|
}
|
||||||
|
|
||||||
if startIndex > len(stats.List)-1 {
|
if startIndex > len(stats.List)-1 {
|
||||||
|
@ -231,7 +231,7 @@ func serveStats(args []string, stats *ServeStats) httprouter.Handle {
|
||||||
|
|
||||||
w.Write(response)
|
w.Write(response)
|
||||||
|
|
||||||
if verbose {
|
if Verbose {
|
||||||
fmt.Printf("%s | Served statistics page (%s) to %s in %s\n",
|
fmt.Printf("%s | Served statistics page (%s) to %s in %s\n",
|
||||||
startTime.Format(LogDate),
|
startTime.Format(LogDate),
|
||||||
humanReadableSize(len(response)),
|
humanReadableSize(len(response)),
|
||||||
|
@ -240,8 +240,8 @@ func serveStats(args []string, stats *ServeStats) httprouter.Handle {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if statisticsFile != "" {
|
if StatisticsFile != "" {
|
||||||
stats.Export(statisticsFile)
|
stats.Export(StatisticsFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
cmd/uri.go
10
cmd/uri.go
|
@ -13,13 +13,13 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RefreshInterval(r *http.Request) (int64, string) {
|
func refreshInterval(r *http.Request) (int64, string) {
|
||||||
var interval string
|
var interval string
|
||||||
|
|
||||||
if refreshInterval == "" {
|
if RefreshInterval == "" {
|
||||||
interval = r.URL.Query().Get("refresh")
|
interval = r.URL.Query().Get("refresh")
|
||||||
} else {
|
} else {
|
||||||
interval = refreshInterval
|
interval = RefreshInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
duration, err := time.ParseDuration(interval)
|
duration, err := time.ParseDuration(interval)
|
||||||
|
@ -68,7 +68,7 @@ func generateQueryParams(filters *Filters, sortOrder, refreshInterval string) st
|
||||||
|
|
||||||
queryParams.WriteString("?")
|
queryParams.WriteString("?")
|
||||||
|
|
||||||
if filtering {
|
if Filtering {
|
||||||
queryParams.WriteString("include=")
|
queryParams.WriteString("include=")
|
||||||
if filters.HasIncludes() {
|
if filters.HasIncludes() {
|
||||||
queryParams.WriteString(filters.Includes())
|
queryParams.WriteString(filters.Includes())
|
||||||
|
@ -82,7 +82,7 @@ func generateQueryParams(filters *Filters, sortOrder, refreshInterval string) st
|
||||||
hasParams = true
|
hasParams = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if sorting {
|
if Sorting {
|
||||||
if hasParams {
|
if hasParams {
|
||||||
queryParams.WriteString("&")
|
queryParams.WriteString("&")
|
||||||
}
|
}
|
||||||
|
|
83
cmd/web.go
83
cmd/web.go
|
@ -37,7 +37,7 @@ const (
|
||||||
Timeout time.Duration = 10 * time.Second
|
Timeout time.Duration = 10 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
func serveStaticFile(paths []string, stats *ServeStats, index *Index) httprouter.Handle {
|
func serveStaticFile(paths []string, stats *ServeStats, index *FileIndex) httprouter.Handle {
|
||||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
path := strings.TrimPrefix(r.URL.Path, SourcePrefix)
|
path := strings.TrimPrefix(r.URL.Path, SourcePrefix)
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ func serveStaticFile(paths []string, stats *ServeStats, index *Index) httprouter
|
||||||
|
|
||||||
fileSize := humanReadableSize(len(buf))
|
fileSize := humanReadableSize(len(buf))
|
||||||
|
|
||||||
if russian {
|
if Russian {
|
||||||
err = os.Remove(filePath)
|
err = os.Remove(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
@ -105,12 +105,12 @@ func serveStaticFile(paths []string, stats *ServeStats, index *Index) httprouter
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if cache {
|
if Cache {
|
||||||
index.Remove(filePath)
|
index.Remove(filePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if verbose {
|
if Verbose {
|
||||||
fmt.Printf("%s | Served %s (%s) to %s in %s\n",
|
fmt.Printf("%s | Served %s (%s) to %s in %s\n",
|
||||||
startTime.Format(LogDate),
|
startTime.Format(LogDate),
|
||||||
filePath,
|
filePath,
|
||||||
|
@ -120,14 +120,14 @@ func serveStaticFile(paths []string, stats *ServeStats, index *Index) httprouter
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if statistics {
|
if Statistics {
|
||||||
stats.incrementCounter(filePath, startTime, fileSize)
|
stats.incrementCounter(filePath, startTime, fileSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveRoot(paths []string, Regexes *Regexes, index *Index, registeredFormats *formats.SupportedFormats) httprouter.Handle {
|
func serveRoot(paths []string, Regexes *Regexes, index *FileIndex, registeredFormats *formats.SupportedFormats) httprouter.Handle {
|
||||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
refererUri, err := stripQueryParams(refererToUri(r.Referer()))
|
refererUri, err := stripQueryParams(refererToUri(r.Referer()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -147,7 +147,7 @@ func serveRoot(paths []string, Regexes *Regexes, index *Index, registeredFormats
|
||||||
|
|
||||||
sortOrder := SortOrder(r)
|
sortOrder := SortOrder(r)
|
||||||
|
|
||||||
_, refreshInterval := RefreshInterval(r)
|
_, refreshInterval := refreshInterval(r)
|
||||||
|
|
||||||
var filePath string
|
var filePath string
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ func serveRoot(paths []string, Regexes *Regexes, index *Index, registeredFormats
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveMedia(paths []string, Regexes *Regexes, index *Index, registeredFormats *formats.SupportedFormats) httprouter.Handle {
|
func serveMedia(paths []string, Regexes *Regexes, index *FileIndex, registeredFormats *formats.SupportedFormats) httprouter.Handle {
|
||||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
filters := &Filters{
|
filters := &Filters{
|
||||||
includes: splitQueryParams(r.URL.Query().Get("include"), Regexes),
|
includes: splitQueryParams(r.URL.Query().Get("include"), Regexes),
|
||||||
|
@ -250,7 +250,7 @@ func serveMedia(paths []string, Regexes *Regexes, index *Index, registeredFormat
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "text/html")
|
w.Header().Add("Content-Type", "text/html")
|
||||||
|
|
||||||
refreshTimer, refreshInterval := RefreshInterval(r)
|
refreshTimer, refreshInterval := refreshInterval(r)
|
||||||
|
|
||||||
queryParams := generateQueryParams(filters, sortOrder, refreshInterval)
|
queryParams := generateQueryParams(filters, sortOrder, refreshInterval)
|
||||||
|
|
||||||
|
@ -286,7 +286,7 @@ func serveMedia(paths []string, Regexes *Regexes, index *Index, registeredFormat
|
||||||
|
|
||||||
func serveVersion() httprouter.Handle {
|
func serveVersion() httprouter.Handle {
|
||||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
data := []byte(fmt.Sprintf("roulette v%s\n", Version))
|
data := []byte(fmt.Sprintf("roulette v%s\n", ReleaseVersion))
|
||||||
|
|
||||||
w.Header().Write(bytes.NewBufferString("Content-Length: " + strconv.Itoa(len(data))))
|
w.Header().Write(bytes.NewBufferString("Content-Length: " + strconv.Itoa(len(data))))
|
||||||
|
|
||||||
|
@ -304,7 +304,7 @@ func ServePage(args []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bindHost, err := net.LookupHost(bind)
|
bindHost, err := net.LookupHost(Bind)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -314,21 +314,32 @@ func ServePage(args []string) error {
|
||||||
return errors.New("invalid bind address provided")
|
return errors.New("invalid bind address provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
registeredFormats := &formats.SupportedFormats{}
|
mux := httprouter.New()
|
||||||
|
|
||||||
if audio || all {
|
registeredFormats := &formats.SupportedFormats{
|
||||||
|
Extensions: make(map[string]*formats.SupportedFormat),
|
||||||
|
MimeTypes: make(map[string]*formats.SupportedFormat),
|
||||||
|
}
|
||||||
|
|
||||||
|
if Audio || All {
|
||||||
registeredFormats.Add(formats.RegisterAudioFormats())
|
registeredFormats.Add(formats.RegisterAudioFormats())
|
||||||
}
|
}
|
||||||
|
|
||||||
if images || all {
|
if Flash || All {
|
||||||
|
registeredFormats.Add(formats.RegisterFlashFormats())
|
||||||
|
|
||||||
|
mux.GET("/ruffle/*ruffle", formats.ServeRuffle())
|
||||||
|
}
|
||||||
|
|
||||||
|
if Images || All {
|
||||||
registeredFormats.Add(formats.RegisterImageFormats())
|
registeredFormats.Add(formats.RegisterImageFormats())
|
||||||
}
|
}
|
||||||
|
|
||||||
if text || all {
|
if Text || All {
|
||||||
registeredFormats.Add(formats.RegisterTextFormats())
|
registeredFormats.Add(formats.RegisterTextFormats())
|
||||||
}
|
}
|
||||||
|
|
||||||
if videos || all {
|
if Videos || All {
|
||||||
registeredFormats.Add(formats.RegisterVideoFormats())
|
registeredFormats.Add(formats.RegisterVideoFormats())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,13 +352,11 @@ func ServePage(args []string) error {
|
||||||
return ErrNoMediaFound
|
return ErrNoMediaFound
|
||||||
}
|
}
|
||||||
|
|
||||||
if russian {
|
if Russian {
|
||||||
fmt.Printf("WARNING! Files *will* be deleted after serving!\n\n")
|
fmt.Printf("WARNING! Files *will* be deleted after serving!\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := httprouter.New()
|
index := &FileIndex{
|
||||||
|
|
||||||
index := &Index{
|
|
||||||
mutex: sync.RWMutex{},
|
mutex: sync.RWMutex{},
|
||||||
list: []string{},
|
list: []string{},
|
||||||
}
|
}
|
||||||
|
@ -358,7 +367,7 @@ func ServePage(args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: net.JoinHostPort(bind, strconv.Itoa(int(port))),
|
Addr: net.JoinHostPort(Bind, strconv.Itoa(int(Port))),
|
||||||
Handler: mux,
|
Handler: mux,
|
||||||
IdleTimeout: 10 * time.Minute,
|
IdleTimeout: 10 * time.Minute,
|
||||||
ReadTimeout: 5 * time.Second,
|
ReadTimeout: 5 * time.Second,
|
||||||
|
@ -387,11 +396,11 @@ func ServePage(args []string) error {
|
||||||
|
|
||||||
mux.GET("/version", serveVersion())
|
mux.GET("/version", serveVersion())
|
||||||
|
|
||||||
if cache {
|
if Cache {
|
||||||
skipIndex := false
|
skipIndex := false
|
||||||
|
|
||||||
if cacheFile != "" {
|
if CacheFile != "" {
|
||||||
err := index.Import(cacheFile)
|
err := index.Import(CacheFile)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
skipIndex = true
|
skipIndex = true
|
||||||
}
|
}
|
||||||
|
@ -404,19 +413,19 @@ func ServePage(args []string) error {
|
||||||
mux.GET("/clear_cache", serveCacheClear(args, index, registeredFormats))
|
mux.GET("/clear_cache", serveCacheClear(args, index, registeredFormats))
|
||||||
}
|
}
|
||||||
|
|
||||||
if debug {
|
if Index {
|
||||||
mux.GET("/html/", serveDebugHtml(args, index, false))
|
mux.GET("/html/", serveIndexHtml(args, index, false))
|
||||||
if pageLength != 0 {
|
if PageLength != 0 {
|
||||||
mux.GET("/html/:page", serveDebugHtml(args, index, true))
|
mux.GET("/html/:page", serveIndexHtml(args, index, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
mux.GET("/json", serveDebugJson(args, index))
|
mux.GET("/json", serveIndexJson(args, index))
|
||||||
if pageLength != 0 {
|
if PageLength != 0 {
|
||||||
mux.GET("/json/:page", serveDebugJson(args, index))
|
mux.GET("/json/:page", serveIndexJson(args, index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if profile {
|
if Profile {
|
||||||
mux.HandlerFunc("GET", "/debug/pprof/", pprof.Index)
|
mux.HandlerFunc("GET", "/debug/pprof/", pprof.Index)
|
||||||
mux.HandlerFunc("GET", "/debug/pprof/cmdline", pprof.Cmdline)
|
mux.HandlerFunc("GET", "/debug/pprof/cmdline", pprof.Cmdline)
|
||||||
mux.HandlerFunc("GET", "/debug/pprof/profile", pprof.Profile)
|
mux.HandlerFunc("GET", "/debug/pprof/profile", pprof.Profile)
|
||||||
|
@ -424,9 +433,9 @@ func ServePage(args []string) error {
|
||||||
mux.HandlerFunc("GET", "/debug/pprof/trace", pprof.Trace)
|
mux.HandlerFunc("GET", "/debug/pprof/trace", pprof.Trace)
|
||||||
}
|
}
|
||||||
|
|
||||||
if statistics {
|
if Statistics {
|
||||||
if statisticsFile != "" {
|
if StatisticsFile != "" {
|
||||||
stats.Import(statisticsFile)
|
stats.Import(StatisticsFile)
|
||||||
|
|
||||||
gracefulShutdown := make(chan os.Signal, 1)
|
gracefulShutdown := make(chan os.Signal, 1)
|
||||||
signal.Notify(gracefulShutdown, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(gracefulShutdown, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
@ -434,14 +443,14 @@ func ServePage(args []string) error {
|
||||||
go func() {
|
go func() {
|
||||||
<-gracefulShutdown
|
<-gracefulShutdown
|
||||||
|
|
||||||
stats.Export(statisticsFile)
|
stats.Export(StatisticsFile)
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
mux.GET("/stats", serveStats(args, stats))
|
mux.GET("/stats", serveStats(args, stats))
|
||||||
if pageLength != 0 {
|
if PageLength != 0 {
|
||||||
mux.GET("/stats/:page", serveStats(args, stats))
|
mux.GET("/stats/:page", serveStats(args, stats))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
func RegisterAudioFormats() *SupportedFormat {
|
func RegisterAudioFormats() *SupportedFormat {
|
||||||
return &SupportedFormat{
|
return &SupportedFormat{
|
||||||
|
Name: "audio",
|
||||||
Css: ``,
|
Css: ``,
|
||||||
Title: func(queryParams, fileUri, filePath, fileName, mime string) string {
|
Title: func(queryParams, fileUri, filePath, fileName, mime string) string {
|
||||||
return fmt.Sprintf(`<title>%s</title>`, fileName)
|
return fmt.Sprintf(`<title>%s</title>`, fileName)
|
||||||
|
|
61
formats/flash.go
Normal file
61
formats/flash.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2023 Seednode <seednode@seedno.de>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package formats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed ruffle/*
|
||||||
|
var ruffle embed.FS
|
||||||
|
|
||||||
|
func RegisterFlashFormats() *SupportedFormat {
|
||||||
|
return &SupportedFormat{
|
||||||
|
Name: `flash`,
|
||||||
|
Css: ``,
|
||||||
|
Title: func(queryParams, fileUri, filePath, fileName, mime string) string {
|
||||||
|
return fmt.Sprintf(`<title>%s</title>`, fileName)
|
||||||
|
},
|
||||||
|
Body: func(queryParams, fileUri, filePath, fileName, mime string) string {
|
||||||
|
var html strings.Builder
|
||||||
|
|
||||||
|
html.WriteString(fmt.Sprintf(`<script src="/ruffle/ruffle.js"></script><script>window.RufflePlayer.config = {autoplay:"on"};</script><embed src="%s"></embed>`, fileUri))
|
||||||
|
html.WriteString(fmt.Sprintf(`<br /><button onclick=\"window.location.href = '/%s';\">Next</button>`, queryParams))
|
||||||
|
|
||||||
|
return html.String()
|
||||||
|
},
|
||||||
|
Extensions: []string{
|
||||||
|
`.swf`,
|
||||||
|
},
|
||||||
|
MimeTypes: []string{
|
||||||
|
`application/x-shockwave-flash`,
|
||||||
|
},
|
||||||
|
Validate: func(filePath string) bool {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ServeRuffle() httprouter.Handle {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
|
fname := strings.TrimPrefix(r.URL.Path, "/")
|
||||||
|
|
||||||
|
data, err := ruffle.ReadFile(fname)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Write(bytes.NewBufferString("Content-Length: " + strconv.Itoa(len(data))))
|
||||||
|
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ type Dimensions struct {
|
||||||
|
|
||||||
func RegisterImageFormats() *SupportedFormat {
|
func RegisterImageFormats() *SupportedFormat {
|
||||||
return &SupportedFormat{
|
return &SupportedFormat{
|
||||||
|
Name: "images",
|
||||||
Css: ``,
|
Css: ``,
|
||||||
Title: func(queryParams, fileUri, filePath, fileName, mime string) string {
|
Title: func(queryParams, fileUri, filePath, fileName, mime string) string {
|
||||||
dimensions, err := ImageDimensions(filePath)
|
dimensions, err := ImageDimensions(filePath)
|
||||||
|
|
BIN
formats/ruffle/93ed30949cdbafa5c732.wasm
Normal file
BIN
formats/ruffle/93ed30949cdbafa5c732.wasm
Normal file
Binary file not shown.
176
formats/ruffle/LICENSE_APACHE
Normal file
176
formats/ruffle/LICENSE_APACHE
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
25
formats/ruffle/LICENSE_MIT
Normal file
25
formats/ruffle/LICENSE_MIT
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
Copyright (c) 2018 Ruffle LLC <ruffle@ruffle.rs> and Ruffle contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
57
formats/ruffle/README.md
Normal file
57
formats/ruffle/README.md
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
# ruffle-selfhosted
|
||||||
|
|
||||||
|
ruffle-selfhosted is the intended way to get Ruffle onto your website.
|
||||||
|
|
||||||
|
You may either include it and forget about it, and we will polyfill existing Flash content,
|
||||||
|
or use our APIs for custom configurations or more advanced usages of the Ruffle player.
|
||||||
|
|
||||||
|
## Using ruffle-selfhosted
|
||||||
|
|
||||||
|
For more examples and in-depth documentation on how to use Ruffle on your website, please
|
||||||
|
[check out our wiki](https://github.com/ruffle-rs/ruffle/wiki/Using-Ruffle#web).
|
||||||
|
|
||||||
|
### Host Ruffle
|
||||||
|
|
||||||
|
The `selfhosted` package is configured for websites that do not use bundlers or npm and just want
|
||||||
|
to get up and running. If you'd prefer to use Ruffle through npm and a bundler, please
|
||||||
|
[refer to ruffle core](https://github.com/ruffle-rs/ruffle/tree/master/web/packages/core).
|
||||||
|
|
||||||
|
Before you can get started with using Ruffle on your website, you must host its files yourself.
|
||||||
|
Either take the [latest build](https://github.com/ruffle-rs/ruffle/releases)
|
||||||
|
or [build it yourself](https://github.com/ruffle-rs/ruffle/blob/master/web/README.md), and make these files accessible by your web server.
|
||||||
|
|
||||||
|
Please note that the `.wasm` file must be served properly, and some web servers may not do that
|
||||||
|
correctly out of the box. Please see [our wiki](https://github.com/ruffle-rs/ruffle/wiki/Using-Ruffle#configure-wasm-mime-type)
|
||||||
|
for instructions on how to configure this, if you encounter a `Incorrect response MIME type` error.
|
||||||
|
|
||||||
|
### "Plug and Play"
|
||||||
|
|
||||||
|
If you have an existing website with flash content, you can simply include Ruffle as a script and
|
||||||
|
our polyfill magic will replace everything for you. No fuss, no mess.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="path/to/ruffle/ruffle.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Javascript API
|
||||||
|
|
||||||
|
If you want to control the Ruffle player, you may use our Javascript API.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
window.RufflePlayer = window.RufflePlayer || {};
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
|
let ruffle = window.RufflePlayer.newest();
|
||||||
|
let player = ruffle.createPlayer();
|
||||||
|
let container = document.getElementById("container");
|
||||||
|
container.appendChild(player);
|
||||||
|
player.load("movie.swf");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script src="path/to/ruffle/ruffle.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building, testing or contributing
|
||||||
|
|
||||||
|
Please see [the ruffle-web README](https://github.com/ruffle-rs/ruffle/blob/master/web/README.md).
|
BIN
formats/ruffle/c90b7d9899c49daa5fc2.wasm
Normal file
BIN
formats/ruffle/c90b7d9899c49daa5fc2.wasm
Normal file
Binary file not shown.
2
formats/ruffle/core.ruffle.ca014f3bb2f19b3cfe1a.js
Normal file
2
formats/ruffle/core.ruffle.ca014f3bb2f19b3cfe1a.js
Normal file
File diff suppressed because one or more lines are too long
1
formats/ruffle/core.ruffle.ca014f3bb2f19b3cfe1a.js.map
Normal file
1
formats/ruffle/core.ruffle.ca014f3bb2f19b3cfe1a.js.map
Normal file
File diff suppressed because one or more lines are too long
2
formats/ruffle/core.ruffle.f5509746453337578120.js
Normal file
2
formats/ruffle/core.ruffle.f5509746453337578120.js
Normal file
File diff suppressed because one or more lines are too long
1
formats/ruffle/core.ruffle.f5509746453337578120.js.map
Normal file
1
formats/ruffle/core.ruffle.f5509746453337578120.js.map
Normal file
File diff suppressed because one or more lines are too long
1
formats/ruffle/package.json
Normal file
1
formats/ruffle/package.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"name":"@ruffle-rs/ruffle","version":"0.1.0-nightly.2023.09.12","description":"Putting Flash back on the web. Ruffle will polyfill all Flash content and replace it with the Ruffle flash player.","license":"(MIT OR Apache-2.0)","keywords":["flash","swf"],"homepage":"https://ruffle.rs","bugs":"https://github.com/ruffle-rs/ruffle/issues","repository":"github:ruffle-rs/ruffle","main":"ruffle.js"}
|
3
formats/ruffle/ruffle.js
Normal file
3
formats/ruffle/ruffle.js
Normal file
File diff suppressed because one or more lines are too long
11
formats/ruffle/ruffle.js.LICENSE.txt
Normal file
11
formats/ruffle/ruffle.js.LICENSE.txt
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
/*!
|
||||||
|
|
||||||
|
JSZip v3.10.1 - A JavaScript class for generating and reading zip files
|
||||||
|
<http://stuartk.com/jszip>
|
||||||
|
|
||||||
|
(c) 2009-2016 Stuart Knightley <stuart [at] stuartk.com>
|
||||||
|
Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown.
|
||||||
|
|
||||||
|
JSZip uses the library pako released under the MIT license :
|
||||||
|
https://github.com/nodeca/pako/blob/main/LICENSE
|
||||||
|
*/
|
1
formats/ruffle/ruffle.js.map
Normal file
1
formats/ruffle/ruffle.js.map
Normal file
File diff suppressed because one or more lines are too long
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
func RegisterTextFormats() *SupportedFormat {
|
func RegisterTextFormats() *SupportedFormat {
|
||||||
return &SupportedFormat{
|
return &SupportedFormat{
|
||||||
|
Name: "text",
|
||||||
Css: `pre{margin:.5rem;}`,
|
Css: `pre{margin:.5rem;}`,
|
||||||
Title: func(queryParams, fileUri, filePath, fileName, mime string) string {
|
Title: func(queryParams, fileUri, filePath, fileName, mime string) string {
|
||||||
return fmt.Sprintf(`<title>%s</title>`, fileName)
|
return fmt.Sprintf(`<title>%s</title>`, fileName)
|
||||||
|
@ -39,7 +40,6 @@ func RegisterTextFormats() *SupportedFormat {
|
||||||
},
|
},
|
||||||
MimeTypes: []string{
|
MimeTypes: []string{
|
||||||
`application/json`,
|
`application/json`,
|
||||||
`application/octet-stream`,
|
|
||||||
`application/xml`,
|
`application/xml`,
|
||||||
`text/css`,
|
`text/css`,
|
||||||
`text/csv`,
|
`text/csv`,
|
||||||
|
|
|
@ -8,9 +8,11 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SupportedFormat struct {
|
type SupportedFormat struct {
|
||||||
|
Name string
|
||||||
Css string
|
Css string
|
||||||
Title func(queryParams, fileUri, filePath, fileName, mime string) string
|
Title func(queryParams, fileUri, filePath, fileName, mime string) string
|
||||||
Body func(queryParams, fileUri, filePath, fileName, mime string) string
|
Body func(queryParams, fileUri, filePath, fileName, mime string) string
|
||||||
|
@ -19,47 +21,30 @@ type SupportedFormat struct {
|
||||||
Validate func(filePath string) bool
|
Validate func(filePath string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func (s *SupportedFormat) ListExtensions() []string
|
||||||
|
|
||||||
type SupportedFormats struct {
|
type SupportedFormats struct {
|
||||||
types []*SupportedFormat
|
Extensions map[string]*SupportedFormat
|
||||||
|
MimeTypes map[string]*SupportedFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SupportedFormats) Add(t *SupportedFormat) {
|
func (s *SupportedFormats) Add(t *SupportedFormat) {
|
||||||
s.types = append(s.types, t)
|
for _, v := range t.Extensions {
|
||||||
|
_, exists := s.Extensions[v]
|
||||||
|
if !exists {
|
||||||
|
s.Extensions[v] = t
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SupportedFormats) Extensions() []string {
|
for _, v := range t.MimeTypes {
|
||||||
var extensions []string
|
_, exists := s.Extensions[v]
|
||||||
|
if !exists {
|
||||||
for _, t := range s.types {
|
s.MimeTypes[v] = t
|
||||||
extensions = append(extensions, t.Extensions...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return extensions
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SupportedFormats) MimeTypes() []string {
|
|
||||||
var mimeTypes []string
|
|
||||||
|
|
||||||
for _, t := range s.types {
|
|
||||||
mimeTypes = append(mimeTypes, t.MimeTypes...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return mimeTypes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SupportedFormats) Type(mimeType string) *SupportedFormat {
|
|
||||||
for i := range s.types {
|
|
||||||
for _, m := range s.types[i].MimeTypes {
|
|
||||||
if mimeType == m {
|
|
||||||
return s.types[i]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
func FileType(path string, registeredFormats *SupportedFormats) (bool, *SupportedFormat, string, error) {
|
||||||
}
|
|
||||||
|
|
||||||
func FileType(path string, types *SupportedFormats) (bool, *SupportedFormat, string, error) {
|
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, os.ErrNotExist):
|
case errors.Is(err, os.ErrNotExist):
|
||||||
|
@ -74,12 +59,16 @@ func FileType(path string, types *SupportedFormats) (bool, *SupportedFormat, str
|
||||||
|
|
||||||
mimeType := http.DetectContentType(head)
|
mimeType := http.DetectContentType(head)
|
||||||
|
|
||||||
for _, v := range types.MimeTypes() {
|
// try identifying files by mime types first
|
||||||
if mimeType == v {
|
fileType, exists := registeredFormats.MimeTypes[mimeType]
|
||||||
fileType := types.Type(mimeType)
|
if exists {
|
||||||
|
|
||||||
return fileType.Validate(path), fileType, mimeType, nil
|
return fileType.Validate(path), fileType, mimeType, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if mime type detection fails, use the file extension
|
||||||
|
fileType, exists = registeredFormats.Extensions[filepath.Ext(path)]
|
||||||
|
if exists {
|
||||||
|
return fileType.Validate(path), fileType, mimeType, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil, "", nil
|
return false, nil, "", nil
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
func RegisterVideoFormats() *SupportedFormat {
|
func RegisterVideoFormats() *SupportedFormat {
|
||||||
return &SupportedFormat{
|
return &SupportedFormat{
|
||||||
|
Name: "video",
|
||||||
Css: ``,
|
Css: ``,
|
||||||
Title: func(queryParams, fileUri, filePath, fileName, mime string) string {
|
Title: func(queryParams, fileUri, filePath, fileName, mime string) string {
|
||||||
return fmt.Sprintf(`<title>%s</title>`, fileName)
|
return fmt.Sprintf(`<title>%s</title>`, fileName)
|
||||||
|
|
28
licenses/github.com/ruffle-rs/ruffle/LICENSE.md
Normal file
28
licenses/github.com/ruffle-rs/ruffle/LICENSE.md
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
## MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018-2022 Ruffle LLC <ruffle@ruffle.rs> and Ruffle contributors
|
||||||
|
(https://github.com/ruffle-rs/ruffle/graphs/contributors)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
Loading…
Add table
Reference in a new issue