Compare commits
34 Commits
Author | SHA1 | Date |
---|---|---|
Alexander Andreev | df3ea75ca9 | |
Alexander Andreev | cbe0292e4c | |
Alexander Andreev | d6463e81e8 | |
Alexander Andreev | 99e39fc6c1 | |
Alexander Andreev | 5cc9b12030 | |
Alexander Andreev | 8b39e60a01 | |
Alexander Andreev | c06eaecbfc | |
Alexander Andreev | f23e4d713b | |
Alexander Andreev | 1a5d32f1b9 | |
Alexander Andreev | 775eef657e | |
Alexander Andreev | 06961fc7c4 | |
Alexander Andreev | 391f589a90 | |
Alexander Andreev | 404f88c099 | |
Alexander Andreev | 54bc8d744d | |
Alexander Andreev | 6cf74599cc | |
Alexander Andreev | 4657319d52 | |
Alexander Andreev | c5ffe37c52 | |
Alexander Andreev | 0e54693b4a | |
Alexander Andreev | cc8634fdb5 | |
Alexander Andreev | 8038a0d551 | |
Alexander Andreev | aa64903161 | |
Alexander Andreev | 15af164462 | |
Alexander Andreev | ed62b37dbc | |
Alexander Andreev | 80647144b5 | |
Alexander Andreev | 7d1da65f38 | |
Alexander Andreev | eaa49744af | |
Alexander Andreev | 24e46c79e7 | |
Alexander Andreev | 56399e85bb | |
Alexander Andreev | def915607b | |
Alexander Andreev | 85e5095120 | |
Alexander Andreev | e1107e94eb | |
Alexander Andreev | 2b5c26b2db | |
Alexander Andreev | 277333b1cb | |
Alexander Andreev | bdcdecb612 |
|
@ -1,3 +1,5 @@
|
|||
bin/*
|
||||
!bin/.keep
|
||||
.vscode
|
||||
.vscode
|
||||
web/*.jade.go
|
||||
web/jade.go
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2022 Alexander "Arav" Andreev <me@arav.top>
|
||||
Copyright (c) 2022,2023 Alexander "Arav" Andreev <me@arav.su>
|
||||
|
||||
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:
|
||||
|
||||
|
|
35
Makefile
35
Makefile
|
@ -2,28 +2,39 @@ TARGET=dwelling-files
|
|||
|
||||
SYSDDIR_=${shell pkg-config systemd --variable=systemdsystemunitdir}
|
||||
SYSDDIR=${SYSDDIR_:/%=%}
|
||||
DESTDIR=/
|
||||
|
||||
LDFLAGS=-ldflags "-s -w -X main.version=22.50.0" -tags osusergo,netgo
|
||||
DESTDIR:=
|
||||
PREFIX:=/usr/local
|
||||
|
||||
all: ${TARGET}
|
||||
VERSION=23.32.0
|
||||
|
||||
.PHONY: ${TARGET}
|
||||
FLAGS=-buildmode=pie -modcacherw -mod=readonly -trimpath
|
||||
LDFLAGS=-ldflags "-s -w -X main.version=${VERSION}" -tags osusergo,netgo
|
||||
|
||||
${TARGET}:
|
||||
go build -o bin/$@ ${LDFLAGS} cmd/$@/main.go
|
||||
.PHONY: run install uninstall clean
|
||||
|
||||
${TARGET}: web/*.jade.go
|
||||
go build -o bin/$@ ${LDFLAGS} ${FLAGS} cmd/$@/main.go
|
||||
|
||||
web/*.jade.go: web/templates/*.jade
|
||||
ifeq (,$(wildcard $(shell go env GOPATH)/bin/jade))
|
||||
go install github.com/Joker/jade/cmd/jade@latest
|
||||
endif
|
||||
go generate web/web.go
|
||||
|
||||
run:
|
||||
bin/${TARGET} -file-handling -path /mnt/data -listen 127.0.0.1:19135
|
||||
|
||||
install:
|
||||
install -Dm 0755 bin/${TARGET} ${DESTDIR}usr/bin/${TARGET}
|
||||
install -Dm 0644 LICENSE ${DESTDIR}usr/share/licenses/${TARGET}/LICENSE
|
||||
install -Dm 0755 bin/${TARGET} ${DESTDIR}${PREFIX}/bin/${TARGET}
|
||||
|
||||
install -Dm 0644 init/systemd/${TARGET}.service ${DESTDIR}${SYSDDIR}/${TARGET}.service
|
||||
install -Dm 0644 init/systemd/${TARGET}.service ${DESTDIR}/${SYSDDIR}/${TARGET}.service
|
||||
|
||||
uninstall:
|
||||
rm ${DESTDIR}usr/bin/${TARGET}
|
||||
rm ${DESTDIR}usr/share/licenses/${TARGET}/LICENSE
|
||||
rm ${DESTDIR}${PREFIX}/bin/${TARGET}
|
||||
|
||||
rm ${DESTDIR}${SYSDDIR}/${TARGET}.service
|
||||
rm ${DESTDIR}/${SYSDDIR}/${TARGET}.service
|
||||
|
||||
clean:
|
||||
rm -f web/*.jade.go
|
||||
go clean
|
|
@ -1,30 +1,24 @@
|
|||
# Maintainer: Alexander "Arav" Andreev <me@arav.top>
|
||||
# Maintainer: Alexander "Arav" Andreev <me@arav.su>
|
||||
pkgname=dwelling-files
|
||||
pkgver=22.50.0
|
||||
pkgver=23.32.0
|
||||
pkgrel=1
|
||||
pkgdesc="Arav's dwelling / Files"
|
||||
arch=('i686' 'x86_64' 'arm' 'armv6h' 'armv7h' 'aarch64')
|
||||
url="https://git.arav.top/Arav/dwelling-files"
|
||||
url="https://git.arav.su/Arav/dwelling-files"
|
||||
license=('MIT')
|
||||
groups=()
|
||||
depends=()
|
||||
makedepends=('go')
|
||||
makedepends=('go>=1.16')
|
||||
provides=('dwelling-files')
|
||||
conflicts=('dwelling-files')
|
||||
replaces=()
|
||||
backup=()
|
||||
options=()
|
||||
install=
|
||||
source=('https://git.arav.top/Arav/dwelling-files/archive/22.50.0.tar.gz')
|
||||
noextract=()
|
||||
source=("${pkgver}.tar.gz::https://git.arav.su/Arav/dwelling-files/archive/v${pkgver}.tar.gz")
|
||||
md5sums=('SKIP')
|
||||
|
||||
build() {
|
||||
cd "$srcdir/$pkgname"
|
||||
make DESTDIR="$pkgdir/"
|
||||
export GOPATH="$srcdir"/gopath
|
||||
make VERSION=$pkgver DESTDIR="$pkgdir" PREFIX="/usr"
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "$srcdir/$pkgname"
|
||||
make DESTDIR="$pkgdir/" install
|
||||
make DESTDIR="$pkgdir" PREFIX="/usr" install
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"dwelling-files/internal/handlers"
|
||||
"dwelling-files/internal/server"
|
||||
dwhttp "dwelling-files/internal/http"
|
||||
"dwelling-files/web"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/netip"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"git.arav.su/Arav/httpr"
|
||||
)
|
||||
|
||||
var version string
|
||||
|
@ -26,33 +26,20 @@ func main() {
|
|||
log.SetFlags(0)
|
||||
|
||||
if *showVersion {
|
||||
fmt.Println("dwelling-files ver.", version, "\nCopyright (c) 2022 Alexander \"Arav\" Andreev <me@arav.top>")
|
||||
fmt.Println("dwelling-files ver.", version, "\nCopyright (c) 2023 Alexander \"Arav\" Andreev <me@arav.su>")
|
||||
return
|
||||
}
|
||||
|
||||
var network string
|
||||
if !strings.ContainsRune(*listenAddress, ':') {
|
||||
network = "unix"
|
||||
defer os.Remove(*listenAddress)
|
||||
} else {
|
||||
ap, err := netip.ParseAddrPort(*listenAddress)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
hand := dwhttp.New(directoryPath, !*enableFileHandler)
|
||||
r := httpr.New()
|
||||
|
||||
if ap.Addr().Is4() {
|
||||
network = "tcp4"
|
||||
} else if ap.Addr().Is6() {
|
||||
network = "tcp6"
|
||||
}
|
||||
}
|
||||
r.ServeStatic("/assets/*filepath", web.Assets())
|
||||
r.Handler(http.MethodGet, "/file/*filepath", hand.File)
|
||||
r.Handler(http.MethodGet, "/*filepath", hand.Index)
|
||||
r.Handler(http.MethodGet, "/", hand.Index)
|
||||
|
||||
hand := handlers.New(directoryPath, web.Assets(), !*enableFileHandler)
|
||||
srv := server.NewHttpServer()
|
||||
|
||||
srv.GET("/*filepath", hand.Index)
|
||||
|
||||
if err := srv.Start(network, *listenAddress); err != nil {
|
||||
srv := dwhttp.NewHttpServer(r)
|
||||
if err := srv.Start(*listenAddress); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
server {
|
||||
listen 443 ssl http2;
|
||||
listen 443 http2;
|
||||
listen 127.0.0.1:8112; # I2P
|
||||
listen [300:a98d:d6d0:8a08::d]:80; # Yggdrasil
|
||||
|
||||
|
||||
server_name files.arav.top files.arav.i2p qf5e43nlhvnrutmikuvbdfj3cmtthokpbaxtkm6mjlslttzvtgm4fxid.onion;
|
||||
server_name files.arav.su files.arav.i2p qf5e43nlhvnrutmikuvbdfj3cmtthokpbaxtkm6mjlslttzvtgm4fxid.onion;
|
||||
|
||||
|
||||
access_log /var/log/nginx/dwelling/files.log main if=$nolog;
|
||||
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/arav.top/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/arav.top/privkey.pem;
|
||||
ssl_certificate /etc/letsencrypt/live/arav.su/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/arav.su/privkey.pem;
|
||||
|
||||
|
||||
add_header Content-Security-Policy "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; media-src 'self'; object-src 'none'; frame-src 'none'; frame-ancestors 'none'; font-src 'self'; form-action 'self'";
|
||||
|
|
4
go.mod
4
go.mod
|
@ -1,5 +1,5 @@
|
|||
module dwelling-files
|
||||
|
||||
go 1.19
|
||||
go 1.16
|
||||
|
||||
require github.com/julienschmidt/httprouter v1.3.0
|
||||
require git.arav.su/Arav/httpr v0.3.2
|
||||
|
|
4
go.sum
4
go.sum
|
@ -1,2 +1,2 @@
|
|||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
git.arav.su/Arav/httpr v0.3.2 h1:a+ifu+9+FnQe6p/Kd4kgTDKAFN6zBOJjBTMjbAuHxVk=
|
||||
git.arav.su/Arav/httpr v0.3.2/go.mod h1:z0SVYwe5dBReeVuFU9QH2PmBxICJwchxqY5OfZbeVzU=
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"dwelling-files/internal/server"
|
||||
"dwelling-files/pkg/files"
|
||||
"dwelling-files/pkg/utils"
|
||||
"dwelling-files/web"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
type FilesHandlers struct {
|
||||
directoryPath string
|
||||
assetsServer http.Handler
|
||||
fileServer http.Handler
|
||||
noFileHandling bool
|
||||
}
|
||||
|
||||
func New(directoryPath *string, assetsFS http.FileSystem, noFileHandling bool) *FilesHandlers {
|
||||
var fSrv http.Handler
|
||||
if noFileHandling {
|
||||
fSrv = nil
|
||||
} else {
|
||||
fSrv = http.FileServer(http.Dir(*directoryPath))
|
||||
}
|
||||
return &FilesHandlers{
|
||||
directoryPath: *directoryPath,
|
||||
assetsServer: http.FileServer(assetsFS),
|
||||
fileServer: fSrv,
|
||||
noFileHandling: noFileHandling}
|
||||
}
|
||||
|
||||
func (FilesHandlers) AssetsFS() http.FileSystem {
|
||||
return web.Assets()
|
||||
}
|
||||
|
||||
func (h *FilesHandlers) Index(w http.ResponseWriter, r *http.Request) {
|
||||
path := httprouter.CleanPath(server.GetURLParam(r, "filepath"))
|
||||
|
||||
if strings.HasPrefix(path, "/assets") {
|
||||
h.assetsServer.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(path, "/robots.txt") {
|
||||
fc, _ := web.AssetsGetFile("robots.txt")
|
||||
w.Write(fc)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(path, "/file") {
|
||||
if h.noFileHandling {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
r.URL.Path = path[5:]
|
||||
h.fileServer.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
currentPath := files.CurrentPath(path)
|
||||
|
||||
entries, stats, err := files.ScanDirectory(h.directoryPath+path, path)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
web.Index(utils.MainSite(r.Host), currentPath, &stats, &entries, r, w)
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"dwelling-files/pkg/files"
|
||||
"dwelling-files/pkg/utils"
|
||||
"dwelling-files/web"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.arav.su/Arav/httpr"
|
||||
)
|
||||
|
||||
type FilesHandlers struct {
|
||||
directoryPath string
|
||||
fileServer http.Handler
|
||||
noFileHandling bool
|
||||
}
|
||||
|
||||
func New(directoryPath *string, noFileHandling bool) *FilesHandlers {
|
||||
var fSrv http.Handler
|
||||
if noFileHandling {
|
||||
fSrv = nil
|
||||
} else {
|
||||
fSrv = http.FileServer(http.Dir(*directoryPath))
|
||||
}
|
||||
return &FilesHandlers{
|
||||
directoryPath: *directoryPath,
|
||||
fileServer: fSrv,
|
||||
noFileHandling: noFileHandling}
|
||||
}
|
||||
|
||||
func (h *FilesHandlers) Index(w http.ResponseWriter, r *http.Request) {
|
||||
path := "/" + httpr.Param(r, "filepath")
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path += "/"
|
||||
}
|
||||
|
||||
currentPath := files.CurrentPath(path)
|
||||
|
||||
entries, stats, err := files.ScanDirectory(h.directoryPath+path, path)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
Error(w, http.StatusNotFound, "", "", r.Referer())
|
||||
return
|
||||
}
|
||||
|
||||
web.Index("Files", utils.MainSite(r.Host), currentPath, &stats, &entries, r, w)
|
||||
}
|
||||
|
||||
func (h *FilesHandlers) File(w http.ResponseWriter, r *http.Request) {
|
||||
if h.noFileHandling {
|
||||
Error(w, http.StatusServiceUnavailable, "File handling is turned off.", "", r.Referer())
|
||||
return
|
||||
}
|
||||
r.URL.Path = httpr.Param(r, "filepath")
|
||||
h.fileServer.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func RobotsTxt(w http.ResponseWriter, r *http.Request) {
|
||||
fc, _ := web.AssetsGetFile("robots.txt")
|
||||
w.Write(fc)
|
||||
}
|
||||
|
||||
func Error(w http.ResponseWriter, code int, reason, message, referer string) {
|
||||
w.WriteHeader(code)
|
||||
web.ErrorXXX("/ "+http.StatusText(code), reason, message, referer, code, w)
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HttpServer struct {
|
||||
s http.Server
|
||||
addr net.Addr
|
||||
}
|
||||
|
||||
func NewHttpServer(r http.Handler) *HttpServer {
|
||||
return &HttpServer{s: http.Server{
|
||||
ReadTimeout: 3 * time.Second,
|
||||
WriteTimeout: 3 * time.Second,
|
||||
Handler: r}}
|
||||
}
|
||||
|
||||
func (s *HttpServer) Start(address string) error {
|
||||
var network string
|
||||
if !strings.ContainsRune(address, ':') {
|
||||
network = "unix"
|
||||
} else {
|
||||
ap, err := netip.ParseAddrPort(address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ap.Addr().Is4() {
|
||||
network = "tcp4"
|
||||
} else if ap.Addr().Is6() {
|
||||
network = "tcp6"
|
||||
}
|
||||
}
|
||||
|
||||
listener, err := net.Listen(network, address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if listener.Addr().Network() == "unix" {
|
||||
os.Chmod(address, 0777)
|
||||
}
|
||||
|
||||
s.addr = listener.Addr()
|
||||
|
||||
go func() {
|
||||
if err = s.s.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *HttpServer) Stop() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if s.addr.Network() == "unix" {
|
||||
defer os.Remove(s.addr.String())
|
||||
}
|
||||
|
||||
if err := s.s.Shutdown(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
type HttpServer struct {
|
||||
server *http.Server
|
||||
router *httprouter.Router
|
||||
}
|
||||
|
||||
func NewHttpServer() *HttpServer {
|
||||
r := httprouter.New()
|
||||
return &HttpServer{
|
||||
server: &http.Server{
|
||||
ReadTimeout: 3 * time.Second,
|
||||
WriteTimeout: 3 * time.Second,
|
||||
Handler: r,
|
||||
},
|
||||
router: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HttpServer) GET(path string, handler http.HandlerFunc) {
|
||||
s.router.Handler(http.MethodGet, path, handler)
|
||||
}
|
||||
|
||||
// GetURLParam wrapper around underlying router for getting URL parameters.
|
||||
func GetURLParam(r *http.Request, param string) string {
|
||||
return httprouter.ParamsFromContext(r.Context()).ByName(param)
|
||||
}
|
||||
|
||||
func (s *HttpServer) Start(network, address string) error {
|
||||
listener, err := net.Listen(network, address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if listener.Addr().Network() == "unix" {
|
||||
os.Chmod(address, 0777)
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err = s.server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *HttpServer) Stop() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := s.server.Shutdown(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
const FileDateFormat = "2006-01-02 15:04:05 MST"
|
||||
|
||||
type DirStats struct {
|
||||
type DirStat struct {
|
||||
Files int64
|
||||
FilesSize string
|
||||
Directories int64
|
||||
|
@ -22,16 +22,16 @@ type DirEntry struct {
|
|||
Size string
|
||||
}
|
||||
|
||||
func ScanDirectory(path, urlBase string) (entries []DirEntry, stats DirStats, err error) {
|
||||
var dirEntries []DirEntry = make([]DirEntry, 0)
|
||||
var fileEntries []DirEntry = make([]DirEntry, 0)
|
||||
var totalFilesSize int64 = 0
|
||||
|
||||
func ScanDirectory(path, urlBase string) (entries []DirEntry, stats DirStat, err error) {
|
||||
dir, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var dirEntries []DirEntry = make([]DirEntry, 0)
|
||||
var fileEntries []DirEntry = make([]DirEntry, 0)
|
||||
var totalFilesSize int64 = 0
|
||||
|
||||
for _, ent := range dir {
|
||||
entry, _ := ent.Info()
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package files
|
||||
|
||||
import "testing"
|
||||
|
||||
const path = "/mnt/data/music/Various"
|
||||
const urlBase = "/srv/ftp/"
|
||||
|
||||
func BenchmarkScanDirectory(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
/*e, _, _ :=*/ ScanDirectory(path, urlBase)
|
||||
// b.Log(e[len(e)-1], len(e))
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ func MainSite(host string) string {
|
|||
return "http://[300:a98d:d6d0:8a08::f]"
|
||||
}
|
||||
|
||||
return "https://arav.top"
|
||||
return "https://arav.su"
|
||||
}
|
||||
|
||||
// ToClientTimezone converts given time to timezone set in a
|
||||
|
|
|
@ -30,6 +30,8 @@
|
|||
|
||||
::placeholder { color: var(--primary-color); }
|
||||
|
||||
.hidden { display: none; }
|
||||
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none; }
|
||||
|
@ -58,8 +60,6 @@ h2 {
|
|||
font-size: 1.4rem;
|
||||
margin: 1rem 0; }
|
||||
|
||||
.hidden { display: none; }
|
||||
|
||||
html { margin-left: calc(100vw - 100%); }
|
||||
|
||||
body {
|
||||
|
@ -69,6 +69,7 @@ body {
|
|||
font-size: 1.1rem;
|
||||
margin: 0 auto;
|
||||
max-width: 960px;
|
||||
min-height: 100vh;
|
||||
width: 98%; }
|
||||
|
||||
header {
|
||||
|
@ -76,24 +77,22 @@ header {
|
|||
flex-wrap: wrap;
|
||||
justify-content: space-between; }
|
||||
|
||||
#logo {
|
||||
display: block;
|
||||
width: 360px; }
|
||||
header svg { width: 360px; }
|
||||
|
||||
#logo text { fill: var(--text-color); }
|
||||
header svg text { fill: var(--text-color); }
|
||||
|
||||
#logo .logo {
|
||||
header svg text:first-child {
|
||||
font-size: 2rem;
|
||||
font-variant-caps: small-caps;
|
||||
font-weight: bold; }
|
||||
|
||||
header svg text:last-child { font-size: .88rem; }
|
||||
|
||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||
#logo .logo { font-size: 2.082rem; } }
|
||||
header svg text:first-child { font-size: 2.082rem; } }
|
||||
|
||||
@-moz-document url-prefix() {
|
||||
#logo .logo { font-size: 2rem; } }
|
||||
|
||||
#logo .under { font-size: .88rem; }
|
||||
header svg text:first-child { font-size: 2rem; } }
|
||||
|
||||
nav { margin-top: .5rem; }
|
||||
|
||||
|
@ -105,6 +104,60 @@ nav h1 {
|
|||
|
||||
section { margin-top: 1rem; }
|
||||
|
||||
input[name="filter"] {
|
||||
background-color: var(--background-color);
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--primary-color);
|
||||
color: var(--text-color);
|
||||
font: inherit;
|
||||
width: 100%; }
|
||||
|
||||
table {
|
||||
overflow-y: scroll;
|
||||
width: 100%; }
|
||||
|
||||
tr { vertical-align: top; }
|
||||
|
||||
tr:hover,
|
||||
tr:focus-within {
|
||||
background-color: var(--primary-color);
|
||||
color: white; }
|
||||
|
||||
tr:hover a,
|
||||
tr:focus-within a { color: white; }
|
||||
|
||||
th { text-align: left; }
|
||||
|
||||
th:nth-child(2),
|
||||
th:last-child {
|
||||
width: 1%;
|
||||
white-space: nowrap; }
|
||||
|
||||
th,
|
||||
td { line-break: strict; }
|
||||
|
||||
th:nth-child(2),
|
||||
td:nth-child(2) { padding: 0 1rem; }
|
||||
|
||||
td a {
|
||||
display: block;
|
||||
width: 100%; }
|
||||
|
||||
td a:hover { transition: none; }
|
||||
|
||||
td:nth-child(2),
|
||||
td:last-child { white-space: nowrap; }
|
||||
|
||||
thead tr th.clickable { cursor: pointer; }
|
||||
|
||||
thead tr th.clickable:hover { color: var(--secondary-color); }
|
||||
|
||||
thead tr th.clickable:not(.sort-up):not(.sort-down)::after { content: '⇅'; }
|
||||
|
||||
thead tr th.clickable.sort-up::after { content: '↑'; }
|
||||
|
||||
thead tr th.clickable.sort-down::after { content: '↓'; }
|
||||
|
||||
#overlay {
|
||||
align-items: center;
|
||||
background-color: var(--overlay-background-color);
|
||||
|
@ -152,51 +205,13 @@ section { margin-top: 1rem; }
|
|||
background-color: var(--background-color);
|
||||
color: var(--primary-color); }
|
||||
|
||||
input[name="filter"] {
|
||||
background-color: var(--background-color);
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--primary-color);
|
||||
color: var(--text-color);
|
||||
font: inherit;
|
||||
width: 100%; }
|
||||
#error {
|
||||
font-size: 3.5rem;
|
||||
line-height: 5rem;
|
||||
text-align: center;
|
||||
margin: 6rem 0; }
|
||||
|
||||
table { overflow-y: scroll; width: 100%; }
|
||||
|
||||
tr { vertical-align: top; }
|
||||
|
||||
tr:hover,
|
||||
tr:focus-within { background-color: var(--primary-color); color: white; }
|
||||
|
||||
tr:hover a,
|
||||
tr:focus-within a { color: white; }
|
||||
|
||||
th { text-align: left; }
|
||||
|
||||
th:nth-child(2),
|
||||
th:last-child { width: 1%; white-space: nowrap; }
|
||||
|
||||
th,
|
||||
td { line-break: strict; }
|
||||
|
||||
th:nth-child(2),
|
||||
td:nth-child(2) { padding: 0 1rem; }
|
||||
|
||||
td a { display: block; width: 100%; }
|
||||
|
||||
td a:hover { transition: none; }
|
||||
|
||||
td:nth-child(2),
|
||||
td:last-child { white-space: nowrap; }
|
||||
|
||||
thead tr th.clickable { cursor: pointer; }
|
||||
|
||||
thead tr th.clickable:hover { color: var(--secondary-color); }
|
||||
|
||||
thead tr th.clickable:not(.sort-up):not(.sort-down)::after { content: '⇅'; }
|
||||
|
||||
thead tr th.clickable.sort-up::after { content: '↑'; }
|
||||
|
||||
thead tr th.clickable.sort-down::after { content: '↓'; }
|
||||
#error h1 { font-size: 8rem; }
|
||||
|
||||
footer {
|
||||
font-size: .8rem;
|
||||
|
@ -206,7 +221,7 @@ footer {
|
|||
@media screen and (max-width: 640px) {
|
||||
header { display: block; }
|
||||
|
||||
#logo {
|
||||
header svg {
|
||||
margin: 0 auto;
|
||||
width: 100%; }
|
||||
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
// Code generated by "jade.go"; DO NOT EDIT.
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
"dwelling-files/pkg/files"
|
||||
"dwelling-files/pkg/utils"
|
||||
"fmt"
|
||||
"html"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
index__0 = `<!DOCTYPE html><html lang="en"><head><title>Arav's dwelling / Files</title><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><meta name="theme-color" content="#cd2682"/><meta name="description" content="My file share."/><link rel="icon" type="image/svg+xml" href="/assets/img/favicon.svg" sizes="any"/><link href="/assets/css/main.css" rel="stylesheet"/><script src="/assets/js/main.js" defer=""></script></head><body><header><svg id="logo" viewBox="0 -25 216 40"><text class="logo">Arav's dwelling</text><text class="under" y="11">Welcome to my sacred place, wanderer</text></svg><nav><a href="`
|
||||
index__1 = `">Back to main website</a><h1>Files</h1></nav></header><section id="files"><span>`
|
||||
index__2 = `</span><p>Files: `
|
||||
index__3 = ` (`
|
||||
index__4 = `); Directories: `
|
||||
index__5 = `.</p><input class="hidden" type="text" name="filter" placeholder="Type in to filter this directory (case insensitive)"/><table><thead><tr><th>Name</th><th>Date</th><th>Size</th></tr><tr tabindex="`
|
||||
index__6 = `"><td><a href="../">../</a></td></tr></thead><tbody>`
|
||||
index__7 = `</tbody></table></section><section><span>`
|
||||
index__8 = `</span></section><section id="usage"><p>On a page use up and down arrow keys to navigate through list. Use home and end keys to go to the start and end of a list. Use Ctrl+Backspace to return to a parent directory.</p><p>In an overlay use a mouse wheel to change a scale of a video or a picture. Use left and right arrow keys to go through media. Use space key to toggle pause. Use escape key to close an overlay, or click outside a media. An audio volume is being kept across site using LocalStorage API.</p></section><section id="privacy"><h2>Privacy statements</h2><p>I collect access logs that include access date and time, IP-address, User-Agent, referer URL that tells me where have you came from, request that you sent to me. In addition there are GeoIP information added based on your IP-address that includes country, region, and city for my convenience.</p><p>This site makes use of JavaScript purely for convenient functionality, like being able to watch video, listen to music, and look images in an overlay without the need to open a file in a new tab or return back.</p></section><footer>2017—2022 Arav <<a href="mailto:me@arav.top">me@arav.top</a>></footer><div id="overlay"><button name="prev">❰</button><div><div></div><span></span></div><button name="next">❱</button></div></body></html>`
|
||||
index__9 = `<tr tabindex="`
|
||||
index__10 = `"><td><a href="`
|
||||
index__11 = `">`
|
||||
index__12 = `</a></td><td>`
|
||||
index__13 = `</td><td>`
|
||||
index__14 = `</td></tr>`
|
||||
)
|
||||
|
||||
func Index(mainSite, currentPath string, stats *files.DirStats, items *[]files.DirEntry, r *http.Request, wr io.Writer) {
|
||||
buffer := &WriterAsBuffer{wr}
|
||||
|
||||
buffer.WriteString(index__0)
|
||||
buffer.WriteString(html.EscapeString(mainSite))
|
||||
buffer.WriteString(index__1)
|
||||
buffer.WriteString(currentPath)
|
||||
buffer.WriteString(index__2)
|
||||
buffer.WriteString(html.EscapeString(fmt.Sprintf("%v", stats.Files)))
|
||||
buffer.WriteString(index__3)
|
||||
buffer.WriteString(html.EscapeString(fmt.Sprintf("%v", stats.FilesSize)))
|
||||
buffer.WriteString(index__4)
|
||||
buffer.WriteString(html.EscapeString(fmt.Sprintf("%v", stats.Directories)))
|
||||
buffer.WriteString(index__5)
|
||||
buffer.WriteString(html.EscapeString(strconv.FormatInt(int64(0), 10)))
|
||||
buffer.WriteString(index__6)
|
||||
|
||||
for i, item := range *items {
|
||||
buffer.WriteString(index__9)
|
||||
buffer.WriteString(html.EscapeString(strconv.FormatInt(int64(i+1), 10)))
|
||||
buffer.WriteString(index__10)
|
||||
buffer.WriteString(html.EscapeString(fmt.Sprintf("%v", item.Link)))
|
||||
buffer.WriteString(index__11)
|
||||
buffer.WriteString(html.EscapeString(fmt.Sprintf("%v", item.Name)))
|
||||
buffer.WriteString(index__12)
|
||||
buffer.WriteString(utils.ToClientTimezone(item.Datetime, r).Format(files.FileDateFormat))
|
||||
buffer.WriteString(index__13)
|
||||
buffer.WriteString(html.EscapeString(fmt.Sprintf("%v", item.Size)))
|
||||
buffer.WriteString(index__14)
|
||||
|
||||
}
|
||||
buffer.WriteString(index__7)
|
||||
buffer.WriteString(currentPath)
|
||||
buffer.WriteString(index__8)
|
||||
|
||||
}
|
141
web/jade.go
141
web/jade.go
|
@ -1,141 +0,0 @@
|
|||
// Code generated by "jade.go"; DO NOT EDIT.
|
||||
package web
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
escaped = []byte{'<', '>', '"', '\'', '&'}
|
||||
replacing = []string{"<", ">", """, "'", "&"}
|
||||
)
|
||||
|
||||
func WriteEscString(st string, buffer *WriterAsBuffer) {
|
||||
for i := 0; i < len(st); i++ {
|
||||
if n := bytes.IndexByte(escaped, st[i]); n >= 0 {
|
||||
buffer.WriteString(replacing[n])
|
||||
} else {
|
||||
buffer.WriteByte(st[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type WriterAsBuffer struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (w *WriterAsBuffer) WriteString(s string) (n int, err error) {
|
||||
n, err = w.Write([]byte(s))
|
||||
return
|
||||
}
|
||||
|
||||
func (w *WriterAsBuffer) WriteByte(b byte) (err error) {
|
||||
_, err = w.Write([]byte{b})
|
||||
return
|
||||
}
|
||||
|
||||
type stringer interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
func WriteAll(a interface{}, escape bool, buffer *WriterAsBuffer) {
|
||||
switch v := a.(type) {
|
||||
case string:
|
||||
if escape {
|
||||
WriteEscString(v, buffer)
|
||||
} else {
|
||||
buffer.WriteString(v)
|
||||
}
|
||||
case int:
|
||||
WriteInt(int64(v), buffer)
|
||||
case int8:
|
||||
WriteInt(int64(v), buffer)
|
||||
case int16:
|
||||
WriteInt(int64(v), buffer)
|
||||
case int32:
|
||||
WriteInt(int64(v), buffer)
|
||||
case int64:
|
||||
WriteInt(v, buffer)
|
||||
case uint:
|
||||
WriteUint(uint64(v), buffer)
|
||||
case uint8:
|
||||
WriteUint(uint64(v), buffer)
|
||||
case uint16:
|
||||
WriteUint(uint64(v), buffer)
|
||||
case uint32:
|
||||
WriteUint(uint64(v), buffer)
|
||||
case uint64:
|
||||
WriteUint(v, buffer)
|
||||
case float32:
|
||||
buffer.WriteString(strconv.FormatFloat(float64(v), 'f', -1, 64))
|
||||
case float64:
|
||||
buffer.WriteString(strconv.FormatFloat(v, 'f', -1, 64))
|
||||
case bool:
|
||||
WriteBool(v, buffer)
|
||||
case stringer:
|
||||
if escape {
|
||||
WriteEscString(v.String(), buffer)
|
||||
} else {
|
||||
buffer.WriteString(v.String())
|
||||
}
|
||||
default:
|
||||
buffer.WriteString("\n<<< unprinted type, fmt.Stringer implementation needed >>>\n")
|
||||
}
|
||||
}
|
||||
|
||||
func ternary(condition bool, iftrue, iffalse interface{}) interface{} {
|
||||
if condition {
|
||||
return iftrue
|
||||
} else {
|
||||
return iffalse
|
||||
}
|
||||
}
|
||||
|
||||
// Used part of go source:
|
||||
// https://github.com/golang/go/blob/master/src/strconv/itoa.go
|
||||
func WriteUint(u uint64, buffer *WriterAsBuffer) {
|
||||
var a [64 + 1]byte
|
||||
i := len(a)
|
||||
|
||||
if ^uintptr(0)>>32 == 0 {
|
||||
for u > uint64(^uintptr(0)) {
|
||||
q := u / 1e9
|
||||
us := uintptr(u - q*1e9)
|
||||
for j := 9; j > 0; j-- {
|
||||
i--
|
||||
qs := us / 10
|
||||
a[i] = byte(us - qs*10 + '0')
|
||||
us = qs
|
||||
}
|
||||
u = q
|
||||
}
|
||||
}
|
||||
|
||||
us := uintptr(u)
|
||||
for us >= 10 {
|
||||
i--
|
||||
q := us / 10
|
||||
a[i] = byte(us - q*10 + '0')
|
||||
us = q
|
||||
}
|
||||
|
||||
i--
|
||||
a[i] = byte(us + '0')
|
||||
buffer.Write(a[i:])
|
||||
}
|
||||
func WriteInt(i int64, buffer *WriterAsBuffer) {
|
||||
if i < 0 {
|
||||
buffer.WriteByte('-')
|
||||
i = -i
|
||||
}
|
||||
WriteUint(uint64(i), buffer)
|
||||
}
|
||||
func WriteBool(b bool, buffer *WriterAsBuffer) {
|
||||
if b {
|
||||
buffer.WriteString("true")
|
||||
return
|
||||
}
|
||||
buffer.WriteString("false")
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
doctype html
|
||||
html(lang='en')
|
||||
head
|
||||
block head
|
||||
title Arav's dwelling / #{title}
|
||||
meta(charset='utf-8')
|
||||
meta(http-equiv='X-UA-Compatible' content='IE=edge')
|
||||
meta(name='viewport' content='width=device-width, initial-scale=1.0')
|
||||
meta(name='theme-color' content='#cd2682')
|
||||
meta(name='description' content='My file share.')
|
||||
link(rel='icon' type='image/svg+xml' href='/assets/img/favicon.svg' sizes='any')
|
||||
link(href='/assets/css/main.css' rel='stylesheet')
|
||||
script(src='/assets/js/main.js' defer='')
|
||||
body
|
||||
header
|
||||
svg(viewBox='0 -25 216 40')
|
||||
text Arav's dwelling
|
||||
text(y='11') Welcome to my sacred place, wanderer
|
||||
nav
|
||||
block nav
|
||||
block content
|
||||
footer
|
||||
| 2017—2023 Alexander "Arav" Andreev <#[a(href='mailto:me@arav.su') me@arav.su]> #[a(href='/privacy') Privacy statements]
|
|
@ -0,0 +1,21 @@
|
|||
extends base.jade
|
||||
|
||||
block meta_description
|
||||
meta(name='description' content=http.StatusText(code))
|
||||
|
||||
block nav
|
||||
a(href='/') Back to index page
|
||||
h1 #{http.StatusText(code)}
|
||||
|
||||
block content
|
||||
:go:func ErrorXXX(title, reason, message, referer string, code int)
|
||||
section#error
|
||||
h1 #{code}
|
||||
| #{http.StatusText(code)}
|
||||
if reason != ""
|
||||
p #{reason}
|
||||
if message != ""
|
||||
p #{message}
|
||||
if referer != ""
|
||||
section
|
||||
h2 #[a(href=referer) Go back]
|
|
@ -1,60 +1,39 @@
|
|||
:go:func Index(mainSite, currentPath string, stats *files.DirStats, items *[]files.DirEntry, r *http.Request)
|
||||
extends base.jade
|
||||
|
||||
:go:import "dwelling-files/pkg/files"
|
||||
:go:import "dwelling-files/pkg/utils"
|
||||
block nav
|
||||
a(href=mainSite) Back to main website
|
||||
h1 Files
|
||||
|
||||
doctype html
|
||||
html(lang='en')
|
||||
head
|
||||
title Arav's dwelling / Files
|
||||
meta(charset='utf-8')
|
||||
meta(http-equiv='X-UA-Compatible' content='IE=edge')
|
||||
meta(name='viewport' content='width=device-width, initial-scale=1.0')
|
||||
meta(name='theme-color' content='#cd2682')
|
||||
meta(name='description' content='My file share.')
|
||||
link(rel='icon' type='image/svg+xml' href='/assets/img/favicon.svg' sizes='any')
|
||||
link(href='/assets/css/main.css' rel='stylesheet')
|
||||
script(src='/assets/js/main.js' defer='')
|
||||
body
|
||||
header
|
||||
svg#logo(viewBox='0 -25 216 40')
|
||||
text.logo Arav's dwelling
|
||||
text.under(y='11') Welcome to my sacred place, wanderer
|
||||
nav
|
||||
a(href=mainSite) Back to main website
|
||||
h1 Files
|
||||
section#files
|
||||
span!= currentPath
|
||||
p Files: #{stats.Files} (#{stats.FilesSize}); Directories: #{stats.Directories}.
|
||||
input.hidden(type="text", name="filter" placeholder="Type in to filter this directory (case insensitive)")
|
||||
table
|
||||
thead
|
||||
tr
|
||||
th Name
|
||||
th Date
|
||||
th Size
|
||||
tr(tabindex=0)
|
||||
td #[a(href="../") ../]
|
||||
tbody
|
||||
each item, i in *items
|
||||
tr(tabindex=i+1)
|
||||
td #[a(href=item.Link) #{item.Name}]
|
||||
td!= utils.ToClientTimezone(item.Datetime, r).Format(files.FileDateFormat)
|
||||
td= item.Size
|
||||
section
|
||||
span!= currentPath
|
||||
section#usage
|
||||
p On a page use up and down arrow keys to navigate through list. Use home and end keys to go to the start and end of a list. Use Ctrl+Backspace to return to a parent directory.
|
||||
p In an overlay use a mouse wheel to change a scale of a video or a picture. Use left and right arrow keys to go through media. Use space key to toggle pause. Use escape key to close an overlay, or click outside a media. An audio volume is being kept across site using LocalStorage API.
|
||||
section#privacy
|
||||
h2 Privacy statements
|
||||
p I collect access logs that include access date and time, IP-address, User-Agent, referer URL that tells me where have you came from, request that you sent to me. In addition there are GeoIP information added based on your IP-address that includes country, region, and city for my convenience.
|
||||
p This site makes use of JavaScript purely for convenient functionality, like being able to watch video, listen to music, and look images in an overlay without the need to open a file in a new tab or return back.
|
||||
footer
|
||||
| 2017—2022 Arav <#[a(href='mailto:me@arav.top') me@arav.top]>
|
||||
div#overlay
|
||||
button(name='prev') ❰
|
||||
block content
|
||||
:go:func Index(title, mainSite, currentPath string, stats *files.DirStat, items *[]files.DirEntry, r *http.Request)
|
||||
:go:import "dwelling-files/pkg/files"
|
||||
:go:import "dwelling-files/pkg/utils"
|
||||
section
|
||||
span!= currentPath
|
||||
p Files: #{stats.Files} (#{stats.FilesSize}); Directories: #{stats.Directories}.
|
||||
input.hidden(type="text", name="filter" placeholder="Type in to filter this directory (case insensitive)")
|
||||
table
|
||||
thead
|
||||
tr
|
||||
th Name
|
||||
th Date
|
||||
th Size
|
||||
tr(tabindex=0)
|
||||
td #[a(href="../") ../]
|
||||
tbody
|
||||
each item, i in *items
|
||||
tr(tabindex=i+1)
|
||||
td #[a(href=item.Link) #{item.Name}]
|
||||
td!= utils.ToClientTimezone(item.Datetime, r).Format(files.FileDateFormat)
|
||||
td= item.Size
|
||||
section
|
||||
span!= currentPath
|
||||
section
|
||||
p On a page use up and down arrow keys to navigate through list. Use home and end keys to go to the start and end of a list. Use Ctrl+Backspace to return to a parent directory.
|
||||
p In an overlay use a mouse wheel to change a scale of a video or a picture. Use left and right arrow keys to go through media. Use space key to toggle pause. Use escape key to close an overlay, or click outside a media. An audio volume is being kept across site using LocalStorage API.
|
||||
div#overlay
|
||||
button(name='prev') ❰
|
||||
div
|
||||
div
|
||||
div
|
||||
span
|
||||
button(name='next') ❱
|
||||
span
|
||||
button(name='next') ❱
|
|
@ -2,16 +2,19 @@ package web
|
|||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//go:generate $GOPATH/bin/jade -pkg=web -stdlib -stdbuf -writer templates/index.jade
|
||||
//go:generate $GOPATH/bin/jade -pkg=web -stdbuf -writer templates/index.jade
|
||||
//go:generate $GOPATH/bin/jade -pkg=web -stdbuf -writer templates/errorXXX.jade
|
||||
|
||||
//go:embed assets
|
||||
var assetsDir embed.FS
|
||||
|
||||
func Assets() http.FileSystem {
|
||||
return http.FS(assetsDir)
|
||||
f, _ := fs.Sub(assetsDir, "assets")
|
||||
return http.FS(f)
|
||||
}
|
||||
|
||||
func AssetsGetFile(path string) ([]byte, error) {
|
||||
|
|
Loading…
Reference in New Issue