1
0
Fork 0

Compare commits

...

76 Commits

Author SHA1 Message Date
Alexander Andreev df3ea75ca9
Added a benchmark for ScanDirectory(). 2023-09-20 04:56:08 +04:00
Alexander Andreev cbe0292e4c
Before allocation of these three vars try to read a dir first. 2023-09-20 04:55:38 +04:00
Alexander Andreev d6463e81e8
A struct DirStats was renamed to DirStat. 2023-09-20 04:07:24 +04:00
Alexander Andreev 99e39fc6c1
Added FLAGS, reorganised targets ${TARGET} and web/*.jade.go. Removed all target. Added clean .PHONY target, removed ${TARGET} from .PHONY. In web/*.jade.go added check for jade executable existence. 2023-09-20 04:06:54 +04:00
Alexander Andreev 5cc9b12030
Updated httpr to v0.3.2. 2023-09-20 04:04:09 +04:00
Alexander Andreev 8b39e60a01
Reorganised CSS. Set min-height for body to 100vh. 2023-08-23 04:49:30 +04:00
Alexander Andreev c06eaecbfc
Removed unused ids. 2023-08-23 04:40:39 +04:00
Alexander Andreev f23e4d713b
Moved error styling to main.css. 2023-08-23 04:40:23 +04:00
Alexander Andreev 1a5d32f1b9
Put my full name in a footer. 2023-08-23 04:33:45 +04:00
Alexander Andreev 775eef657e
Removed id and classes from a logo. 2023-08-23 04:31:57 +04:00
Alexander Andreev 06961fc7c4
Return an error text message if file handling is turned off. 2023-08-23 04:23:51 +04:00
Alexander Andreev 391f589a90
Version set to 23.32.0. 2023-08-13 04:06:34 +04:00
Alexander Andreev 404f88c099
Fix for double slashes. 2023-08-13 03:18:17 +04:00
Alexander Andreev 54bc8d744d
Updated HttpServer to the latest implementation. 2023-08-13 02:25:04 +04:00
Alexander Andreev 6cf74599cc
A separate base.jade template was made. An index.jade rewritten to use it. 2023-08-13 02:24:36 +04:00
Alexander Andreev 4657319d52
Added an error page. 2023-08-13 02:23:47 +04:00
Alexander Andreev c5ffe37c52
Updated httpr to 0.3.1. 2023-08-13 01:58:06 +04:00
Alexander Andreev 0e54693b4a
Added v to archive's name. 2023-06-13 00:15:23 +04:00
Alexander Andreev cc8634fdb5
Oh, remove this test code. 2023-06-13 00:08:12 +04:00
Alexander Andreev 8038a0d551
In a Copyright a year was changed to 2023. 2023-06-13 00:07:34 +04:00
Alexander Andreev aa64903161
Remove jade.go from a repo. 2023-06-13 00:05:05 +04:00
Alexander Andreev 15af164462
Moved to my httpr router. 2023-06-13 00:02:41 +04:00
Alexander Andreev ed62b37dbc
Version was set to 23.24.0. And Makefile and PKGBUILD structure was updated to a recently used one across my other services. 2023-06-12 23:32:54 +04:00
Alexander Andreev 80647144b5
Removed -stdlib that prevented jade.go file's creation. 2023-06-12 23:32:09 +04:00
Alexander Andreev 7d1da65f38
Set Go version to 1.16 as a minimal. 2023-06-12 23:31:34 +04:00
Alexander Andreev eaa49744af
Ignore jade.go and *.jade.go files. 2023-06-12 23:31:06 +04:00
Alexander Andreev 24e46c79e7
In footer 2022 was replaced by 2023. 2023-06-12 23:16:46 +04:00
Alexander Andreev 56399e85bb
Removed Privacy statements section and added a link to it on a main site in a footer. 2023-06-12 23:13:41 +04:00
Alexander Andreev def915607b
ssl is excessive here in nginx.conf. 2023-06-12 23:13:14 +04:00
Alexander Andreev 85e5095120
Do not keep bin directory. 2023-05-14 02:18:15 +04:00
Alexander Andreev e1107e94eb
Added go generate to Makefile. 2023-02-23 18:03:57 +04:00
Alexander Andreev 2b5c26b2db
Remove compiled template. 2023-02-23 18:02:52 +04:00
Alexander Andreev 277333b1cb
Changed arav.top to arav.su. Version up to 23.8.0. 2023-02-23 17:56:26 +04:00
Alexander Andreev bdcdecb612
HTTP server was reorganised in internal/http. 2023-01-12 04:58:54 +04:00
Alexander Andreev 3d8ee35053
In make run enable file handling for testing purposes. 2022-12-17 22:52:15 +04:00
Alexander Andreev d430b09178
Updated version of a program to 22.50.0. 2022-12-17 22:50:54 +04:00
Alexander Andreev 920f9f08ee
Updated use of convertFileSize() in a ScanDirectory() function. 2022-12-17 22:46:17 +04:00
Alexander Andreev 0798ba6602
convertFileSize() now returns only a concatination of a converted size and a size unit. 2022-12-17 22:45:30 +04:00
Alexander Andreev a567a27c69
A deprecated ioutil was replaced with os.ReadDir. And a bit mask compare was used instead of a symbol compare. 2022-12-17 22:44:29 +04:00
Alexander Andreev 71e7765ba8
Updated Go to 1.19. 2022-12-17 22:42:09 +04:00
Alexander Andreev e03a0e320e
Removed unused methods from HTTP server. 2022-12-17 21:50:18 +04:00
Alexander Andreev 007ab59c81
Moved robots.txt handling to Index(). 2022-12-17 21:46:54 +04:00
Alexander Andreev b793da40d4
Check a passed listen address first. 2022-12-17 21:35:33 +04:00
Alexander Andreev 760d08dcde
A -no-file-handling argument was inversed to -file-handling. So, by default no file handling occure from now. 2022-12-17 21:33:10 +04:00
Alexander Andreev 2c583201d7
f.sock was renamed to just sock also in a default for a -listen argument. 2022-12-17 21:30:07 +04:00
Alexander Andreev 38b5e6ae6e
f.sock socket file was renamed to just sock. 2022-12-17 21:29:07 +04:00
Alexander Andreev 17e346f3b6
Removed unnecessary Schema header from nginx.conf. 2022-12-17 21:28:23 +04:00
Alexander Andreev 5678d3e65a
Also change version in a Makefile. 2022-10-31 03:26:04 +04:00
Alexander Andreev f04c6424a1
For me it is a week number 44 already, so a new version is 22.44.0. 2022-10-31 03:24:15 +04:00
Alexander Andreev 50d2c6a8bd
Reassign first and last row global var after sorting. 2022-10-31 03:23:08 +04:00
Alexander Andreev 182726f3e3
Added Yggdrasil address. 2022-10-31 02:08:02 +04:00
Alexander Andreev b63e3ecbf5
http.go server moved in an internal dir. 2022-10-31 01:46:55 +04:00
Alexander Andreev 714d0816b5
keydown event listener moved in its own section KEYBOARD HANDLING. 2022-10-31 01:44:27 +04:00
Alexander Andreev 1adda7d437
Version increased to 22.43.0. 2022-10-31 01:42:15 +04:00
Alexander Andreev 3fa12d8950
Added :focus-within for tr. 2022-10-31 01:41:34 +04:00
Alexander Andreev 7da359ddda
Added tabindex for tr elements. Filter is hidden by default. If JS is enabled, then it will be displayed. 2022-10-31 01:40:59 +04:00
Alexander Andreev 23737319d2
Code was reorginised. Added ability to navigate through directory list. 2022-10-31 01:39:55 +04:00
Alexander Andreev 1021945108
Download source tar instead of git clone in PKGBUILD. 2022-10-18 22:26:37 +04:00
Alexander Andreev d29eb84c3f
Make prev an next buttons to ignore hidden rows. 2022-10-18 22:22:04 +04:00
Alexander Andreev ed17b83aae
Added table row filtering based on value of filter input text field. 2022-10-18 22:21:31 +04:00
Alexander Andreev 4ef2a77c25
Style for an input text field. Added class .hidden to hide table rows. 2022-10-18 22:20:28 +04:00
Alexander Andreev f6dd6b76d0
Added a text input filed to filter directory's content. 2022-10-18 22:19:29 +04:00
Alexander Andreev 3830588748
Version updated to 22.42.0. 2022-10-18 22:18:00 +04:00
Alexander Andreev ca35aa6019
This z-index only messing. 2022-09-20 04:38:10 +04:00
Alexander Andreev 151cb86df0
Updated usage for an overlay. 2022-09-20 04:35:33 +04:00
Alexander Andreev 2667dfd07e
Oh, it has to be a separate function. 2022-09-20 04:28:29 +04:00
Alexander Andreev 9827c5f731
Overlay was reworked from ground up. Now you can use prev and next buttons to go through media. Or use left and right arrow keys. Also ESC to close an overlay. 2022-09-20 03:39:42 +04:00
Alexander Andreev 5a47a0a5e1
Styled a new overlay. 2022-09-20 03:38:35 +04:00
Alexander Andreev fa8f439dec
Added prev and next buttons and content is placed inside other div, blah-blah. 2022-09-20 03:37:43 +04:00
Alexander Andreev 71733ec429
Updated version. 2022-09-20 03:36:17 +04:00
Alexander Andreev f52ffc74ce
Removed '-git' from package name. 2022-08-24 21:42:10 +04:00
Alexander Andreev 8f60fa1d77
Updated Go version to 1.18. 2022-08-24 21:41:48 +04:00
Alexander Andreev 3aa9186f9e
Now version is being set from a Makefile. 2022-08-02 01:12:47 +04:00
Alexander Andreev f15cf88ad9
Updated version. 2022-08-01 01:16:34 +04:00
Alexander Andreev 7fc6daa50f
To MainSite() added an Yggdrasil address. 2022-08-01 01:16:26 +04:00
Alexander Andreev b6aa765183
Added .mov format to video_formats. 2022-07-25 21:10:35 +04:00
26 changed files with 630 additions and 643 deletions

4
.gitignore vendored
View File

@ -1,3 +1,5 @@
bin/*
!bin/.keep
.vscode
.vscode
web/*.jade.go
web/jade.go

View File

@ -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:

View File

@ -1,30 +1,40 @@
TARGET=dwelling-files
SYSCTL=${shell which systemctl}
SYSDDIR_=${shell pkg-config systemd --variable=systemdsystemunitdir}
SYSDDIR=${SYSDDIR_:/%=%}
DESTDIR=/
LDFLAGS=-ldflags "-s -w" -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} -path ~ -listen 127.0.0.1:19135
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

View File

View File

@ -1,30 +1,24 @@
# Maintainer: Alexander "Arav" Andreev <me@arav.top>
pkgname=dwelling-files-git
pkgver=22.27.0
# Maintainer: Alexander "Arav" Andreev <me@arav.su>
pkgname=dwelling-files
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=('git' 'go')
makedepends=('go>=1.16')
provides=('dwelling-files')
conflicts=('dwelling-files')
replaces=()
backup=()
options=()
install=
source=('dwelling-files-git::git+https://git.arav.top/Arav/dwelling-files.git')
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
}

View File

@ -1,22 +1,24 @@
package main
import (
"dwelling-files/internal/handlers"
"dwelling-files/pkg/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 listenAddress *string = flag.String("listen", "/var/run/dwelling-files/f.sock", "listen address (ip:port|unix_path)")
var version string
var listenAddress *string = flag.String("listen", "/var/run/dwelling-files/sock", "listen address (ip:port|unix_path)")
var directoryPath *string = flag.String("path", "/srv/ftp", "path to file share")
var disableFileHandler *bool = flag.Bool("no-file-handling", false, "disable file handling if it is handled by something else (e.g. NGiNX)")
var enableFileHandler *bool = flag.Bool("file-handling", false, "enable file handling if it is handled by something else (e.g. NGiNX)")
var showVersion *bool = flag.Bool("v", false, "show version")
func main() {
@ -24,33 +26,20 @@ func main() {
log.SetFlags(0)
if *showVersion {
fmt.Println("dwelling-files ver. 22.27.0\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
}
hand := handlers.New(directoryPath, web.Assets(), *disableFileHandler)
srv := server.NewHttpServer()
hand := dwhttp.New(directoryPath, !*enableFileHandler)
r := httpr.New()
srv.GET("/*filepath", hand.Index)
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)
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)
}
if ap.Addr().Is4() {
network = "tcp4"
} else if ap.Addr().Is6() {
network = "tcp6"
}
}
if err := srv.Start(network, *listenAddress); err != nil {
srv := dwhttp.NewHttpServer(r)
if err := srv.Start(*listenAddress); err != nil {
log.Fatalln(err)
}

View File

@ -1,17 +1,17 @@
server {
listen 443 ssl http2;
listen 8092; # Tor
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'";
@ -23,11 +23,10 @@ server {
location / {
proxy_pass http://unix:/var/run/dwelling-files/f.sock;
proxy_pass http://unix:/var/run/dwelling-files/sock;
proxy_set_header X-Client-Timezone "$gi2_location_tz";
proxy_set_header Host $host;
proxy_set_header Schema "https";
proxy_set_header X-Real-IP $remote_addr;
}

4
go.mod
View File

@ -1,5 +1,5 @@
module dwelling-files
go 1.17
go 1.16
require github.com/julienschmidt/httprouter v1.3.0
require git.arav.su/Arav/httpr v0.3.2

4
go.sum
View File

@ -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=

View File

@ -6,7 +6,7 @@ After=network-online.target
Type=simple
Restart=on-failure
DynamicUser=yes
ExecStart=/usr/bin/dwelling-files -path /srv/ftp -listen /var/run/dwelling-files/f.sock
ExecStart=/usr/bin/dwelling-files -path /srv/ftp -listen /var/run/dwelling-files/sock
ReadOnlyPaths=/
NoExecPaths=/

View File

@ -1,72 +0,0 @@
package handlers
import (
"dwelling-files/pkg/files"
"dwelling-files/pkg/server"
"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 (FilesHandlers) Robots(w http.ResponseWriter, r *http.Request) {
fc, _ := web.AssetsGetFile("robots.txt")
w.Write(fc)
}
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, "/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)
}

68
internal/http/handlers.go Normal file
View File

@ -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)
}

76
internal/http/http.go Normal file
View File

@ -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
}

View File

@ -1,7 +1,6 @@
package files
import (
"io/ioutil"
"net/url"
"os"
"path/filepath"
@ -10,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
@ -23,16 +22,21 @@ type DirEntry struct {
Size string
}
func ScanDirectory(path, urlBase string) (entries []DirEntry, stats DirStats, err error) {
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
dir, err := ioutil.ReadDir(path)
for _, entry := range dir {
for _, ent := range dir {
entry, _ := ent.Info()
var isDirLink bool
if entry.Mode().Type().String()[0] == 'L' {
if entry.Mode().Type()&os.ModeSymlink != 0 {
if slp, err := filepath.EvalSymlinks(filepath.Join(path, entry.Name())); err == nil {
lStat, _ := os.Lstat(slp)
isDirLink = lStat.IsDir()
@ -48,12 +52,11 @@ func ScanDirectory(path, urlBase string) (entries []DirEntry, stats DirStats, er
})
stats.Directories++
} else {
_, _, sz := convertFileSize(entry.Size())
fileEntries = append(fileEntries, DirEntry{
Name: entry.Name(),
Link: "/file" + urlBase + url.PathEscape(entry.Name()),
Datetime: entry.ModTime(),
Size: sz,
Size: convertFileSize(entry.Size()),
})
totalFilesSize += entry.Size()
@ -61,8 +64,7 @@ func ScanDirectory(path, urlBase string) (entries []DirEntry, stats DirStats, er
}
}
_, _, sz := convertFileSize(totalFilesSize)
stats.FilesSize = sz
stats.FilesSize = convertFileSize(totalFilesSize)
entries = append(entries, dirEntries...)
entries = append(entries, fileEntries...)

13
pkg/files/files_test.go Normal file
View File

@ -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))
}
}

View File

@ -8,8 +8,8 @@ import (
var sizeSuffixes = [...]string{"B", "KiB", "MiB", "GiB", "TiB"}
// convertFileSize converts size in bytes down to biggest units it represents.
// Returns converted size, unit and, a concatenation of size and unit
func convertFileSize(size int64) (float64, string, string) {
// Returns a concatenation of a converted size and a unit
func convertFileSize(size int64) string {
var idx int
var fSize float64 = float64(size)
for idx = 0; fSize >= 1024; fSize /= 1024 {
@ -18,5 +18,5 @@ func convertFileSize(size int64) (float64, string, string) {
fSizeStr := strconv.FormatFloat(fSize, 'f', 3, 64)
fSizeStr = strings.TrimRight(fSizeStr, "0")
fSizeStr = strings.TrimSuffix(fSizeStr, ".")
return fSize, sizeSuffixes[idx], fSizeStr + " " + sizeSuffixes[idx]
return fSizeStr + " " + sizeSuffixes[idx]
}

View File

@ -1,80 +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)
}
func (s *HttpServer) POST(path string, handler http.HandlerFunc) {
s.router.Handler(http.MethodPost, path, handler)
}
func (s *HttpServer) ServeStatic(path string, fsys http.FileSystem) {
s.router.ServeFiles(path, fsys)
}
func (s *HttpServer) SetNotFoundHandler(handler http.HandlerFunc) {
s.router.NotFound = 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
}

View File

@ -12,9 +12,11 @@ func MainSite(host string) string {
return "http://arav.i2p"
} else if strings.Contains(host, "onion") {
return "http://moq7aejnf4xk5k2bkaltli3ftkhusy2mbrd3pj23nrca343ku2mgk4yd.onion"
} else if strings.HasPrefix(host, "[300") {
return "http://[300:a98d:d6d0:8a08::f]"
}
return "https://arav.top"
return "https://arav.su"
}
// ToClientTimezone converts given time to timezone set in a

View File

@ -28,6 +28,10 @@
background-color: var(--secondary-color);
color: var(--background-color); }
::placeholder { color: var(--primary-color); }
.hidden { display: none; }
a {
color: var(--primary-color);
text-decoration: none; }
@ -65,6 +69,7 @@ body {
font-size: 1.1rem;
margin: 0 auto;
max-width: 960px;
min-height: 100vh;
width: 98%; }
header {
@ -72,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; }
@ -101,44 +104,34 @@ nav h1 {
section { margin-top: 1rem; }
#overlay {
align-items: center;
background-color: var(--overlay-background-color);
display: flex;
flex-direction: column;
height: 100%;
left: 0;
max-height: 100%;
position: fixed;
top: 0;
visibility: hidden;
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%; }
#overlay video,
#overlay audio,
#overlay img {
margin: auto;
max-height: 100%;
max-width: 86%;
}
#overlay span {
color: var(--background-color);
text-shadow: 0 0 .3rem var(--secondary-color);
}
table { overflow-y: scroll; width: 100%; }
table {
overflow-y: scroll;
width: 100%; }
tr { vertical-align: top; }
tr:hover { background-color: var(--primary-color); color: white; }
tr:hover,
tr:focus-within {
background-color: var(--primary-color);
color: white; }
tr:hover a { 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:last-child {
width: 1%;
white-space: nowrap; }
th,
td { line-break: strict; }
@ -146,32 +139,79 @@ td { line-break: strict; }
th:nth-child(2),
td:nth-child(2) { padding: 0 1rem; }
td a { display: block; width: 100%; }
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 { cursor: pointer; }
thead tr th.clickable:hover {
color: var(--secondary-color);
}
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:not(.sort-up):not(.sort-down)::after { content: '⇅'; }
thead tr th.clickable.sort-up::after {
content: '↑';
}
thead tr th.clickable.sort-up::after { content: '↑'; }
thead tr th.clickable.sort-down::after {
content: '↓';
}
thead tr th.clickable.sort-down::after { content: '↓'; }
#overlay {
align-items: center;
background-color: var(--overlay-background-color);
display: flex;
flex-direction: row;
height: 100%;
justify-content: space-between;
left: 0;
max-height: 100%;
position: fixed;
top: 0;
visibility: hidden;
width: 100%; }
#overlay div {
align-items: center;
display: flex;
height: 100%;
width: 100%; }
#overlay :is(video, audio, img) {
margin: auto;
max-height: 100%;
max-width: 86%; }
#overlay span {
bottom: 0;
color: var(--background-color);
left: 0;
position: fixed;
text-shadow: 0 0 .3rem var(--secondary-color);
z-index: 999; }
#overlay button {
background: none;
border: none;
color: var(--background-color);
font: inherit;
font-size: 2rem;
padding: 0 1rem;
height: 100%;
z-index: 999; }
#overlay button:hover {
background-color: var(--background-color);
color: var(--primary-color); }
#error {
font-size: 3.5rem;
line-height: 5rem;
text-align: center;
margin: 6rem 0; }
#error h1 { font-size: 8rem; }
footer {
font-size: .8rem;
@ -181,7 +221,7 @@ footer {
@media screen and (max-width: 640px) {
header { display: block; }
#logo {
header svg {
margin: 0 auto;
width: 100%; }

View File

@ -1,147 +1,260 @@
//// OVERLAY FOR VIEWING MEDIA FILES
const video_formats = ["webm", "mp4"];
const video_formats = ["webm", "mp4", "mov"];
const audio_formats = ["mp3", "flac", "opus", "ogg", "m4a"];
const image_formats = ["jpg", "jpeg", "gif", "png", "bmp", "webp"];
const overlay = document.getElementById("overlay");
let g_scale = 1;
const overlay_content = overlay.children[1].firstChild;
const overlay_label = overlay.children[1].lastChild;
const g_tbody = document.getElementsByTagName('tbody')[0];
let g_first_row = g_tbody.firstChild;
let g_last_row = g_tbody.lastChild;
const g_back_row = document.getElementsByTagName("tr")[1];
let g_scale = 1;
let g_current_row = g_back_row;
const file_links = Array.from(g_tbody.children)
.filter(e => e.lastChild.innerHTML != "DIR").map(l => l.firstChild.firstChild);
if (localStorage.getItem('audio_volume') == null)
localStorage['audio_volume'] = 0.5;
localStorage['audio_volume'] = 0.5;
function mousescroll(e) {
e.preventDefault();
g_scale = Math.min(Math.max(0.25, g_scale + (e.deltaY * -0.001)), 4);
e.target.style.transform = `scale(${g_scale})`;
function overlay_close() {
overlay_content.children[0].remove();
overlay.style.visibility = "hidden";
g_scale = 1;
}
function onvolumechange(e) {
localStorage['audio_volume'] = e.target.volume;
overlay.addEventListener("mouseup", e => {
if (e.target.tagName !== "DIV") return;
if (e.button === 0) overlay_close(); });
function determine_media_element(path) {
path = path.toLowerCase();
if (video_formats.some(ext => path.endsWith(ext)))
return "video";
else if (audio_formats.some(ext => path.endsWith(ext)))
return "audio";
else if (image_formats.some(ext => path.endsWith(ext)))
return "img";
return undefined;
}
const ext_filter = (ext, pathname) => pathname.toLowerCase().endsWith(ext);
function send_to_overlay(pathname, media_type_element) {
if (media_type_element === undefined)
return false;
if (overlay_content.children.length != 0)
overlay_content.children[0].remove();
function to_overlay(eltyp, pathname) {
const el = document.createElement(eltyp);
const el_label = document.createElement("span");
el_label.textContent = decodeURI(pathname.substr(pathname.lastIndexOf("/") + 1));
if (eltyp !== "audio") el.addEventListener('wheel', mousescroll);
if (eltyp !== "img") {
el.autoplay = el.controls = true;
el.addEventListener("volumechange", onvolumechange);
el.volume = localStorage['audio_volume'];
}
el.src = pathname;
overlay.appendChild(el);
overlay.appendChild(el_label);
overlay.style.visibility = "visible";
const media_element = document.createElement(media_type_element);
media_element.src = pathname;
overlay_label.textContent = decodeURI(pathname.substr(pathname.lastIndexOf("/") + 1));
if (media_type_element !== "audio") {
media_element.addEventListener("wheel", e => {
e.preventDefault();
g_scale = Math.min(Math.max(0.25, g_scale + (e.deltaY * -0.001)), 4);
e.target.style.transform = `scale(${g_scale})`; });
}
if (media_type_element !== "img") {
media_element.autoplay = media_element.controls = true;
media_element.addEventListener("volumechange", e => {
localStorage['audio_volume'] = e.target.volume; });
media_element.volume = localStorage["audio_volume"];
}
overlay_content.appendChild(media_element);
overlay.style.visibility = "visible";
return true;
}
document.getElementById("overlay").addEventListener("click", e => {
e.target.firstChild.remove();
e.target.firstChild.remove();
e.target.style.visibility = "hidden";
g_scale = 1;
function getSibling(isNext = true, upDown = false) {
if (upDown && g_current_row == g_back_row)
g_current_row = isNext ? g_first_row : g_last_row;
else
g_current_row = isNext ?
( (g_current_row.nextSibling === null) ?
(upDown ? g_back_row : g_first_row) : g_current_row.nextSibling )
: ( (g_current_row.previousSibling === null) ?
(upDown ? g_back_row : g_last_row) : g_current_row.previousSibling );
return g_current_row;
}
const [b_prev, b_next] = overlay.getElementsByTagName("button");
b_prev.addEventListener("click", e => {
do {
getSibling(false, false);
} while (g_current_row.classList.contains("hidden")
|| !send_to_overlay(g_current_row.firstChild.firstChild.pathname,
determine_media_element(g_current_row.firstChild.firstChild.pathname)));
g_current_row.firstChild.firstChild.focus();
e.preventDefault();
});
const file_links = Array.from(document.getElementsByTagName('tr')).slice(2)
.filter(e => e.lastChild.innerHTML != "DIR").map(l => l.firstChild.firstChild);
b_next.addEventListener("click", e => {
do {
getSibling(true, false);
} while (g_current_row.classList.contains("hidden")
|| !send_to_overlay(g_current_row.firstChild.firstChild.pathname,
determine_media_element(g_current_row.firstChild.firstChild.pathname)));
g_current_row.firstChild.firstChild.focus();
e.preventDefault();
});
file_links.forEach(f => f.addEventListener('click', e => {
const pathname = e.target.pathname;
if (video_formats.some(ext => ext_filter(ext, pathname)))
to_overlay("video", pathname);
else if (audio_formats.some(ext => ext_filter(ext, pathname)))
to_overlay("audio", pathname);
else if (image_formats.some(ext => ext_filter(ext, pathname)))
to_overlay("img", pathname);
if (overlay.firstChild != null)
e.preventDefault();
}));
for (let i = 0; i < file_links.length; ++i)
file_links[i].addEventListener("click", e => {
g_current_row = e.target.parentNode.parentNode;
if (send_to_overlay(e.target.pathname, determine_media_element(e.target.pathname)))
e.preventDefault();
});
//// KEYBOARD HANDLING
window.addEventListener("keydown", e => {
if (e.isComposing)
return;
if (overlay.style.visibility === "hidden" || overlay.style.visibility === "") {
switch (e.code) {
case "Backspace": if (e.ctrlKey) window.location = "../"; break;
case "Home": g_current_row = g_back_row; g_back_row.firstChild.firstChild.focus(); break;
case "End": g_current_row = g_last_row; g_last_row.firstChild.firstChild.focus(); break;
case "ArrowUp":
e.preventDefault();
getSibling(false, true);
g_current_row.firstChild.firstChild.focus();
break;
case "ArrowDown":
e.preventDefault();
getSibling(true, true);
g_current_row.firstChild.firstChild.focus();
break;
}
return;
}
switch (e.code) {
case "ArrowLeft": b_prev.click(); break;
case "ArrowRight": b_next.click(); break;
case "Escape": overlay_close(); break;
case "Space":
e.preventDefault();
const el = overlay_content.firstChild;
if (el.paused !== undefined)
el.paused ? el.play() : el.pause();
}
});
//// FILTERING
document.getElementsByName("filter")[0].classList.remove("hidden");
function filter(sub) {
const table = g_tbody.children;
for (let j = 0; j < table.length; ++j)
table[j].classList.toggle("hidden",
!(sub === "" || table[j].firstChild.firstChild.innerText.toLowerCase().indexOf(sub) != -1));
}
document.getElementsByName("filter")[0].addEventListener("input", e => filter(e.target.value.toLowerCase()));
//// SORT BY COLUMN
const units = {"B": 0, "KiB": 1, "MiB": 2, "GiB": 3, "TiB": 4};
const [thead_name, thead_date, thead_size] = document.getElementsByTagName('thead')[0]
.children[0].children;
const tbody = document.getElementsByTagName('tbody')[0];
.children[0].children;
let g_sort_reverse = false;
thead_name.classList.toggle("clickable");
thead_name.addEventListener('click', e => {
e.preventDefault();
sortTable((a,b) => {
const a_name = a.children[0].textContent.toLowerCase();
const b_name = b.children[0].textContent.toLowerCase();
return a_name < b_name ? -1 : a_name > b_name ? 1 : 0;
}, null, null, thead_name, [thead_date, thead_size]);
e.preventDefault();
sortTable((a,b) => {
const a_name = a.children[0].textContent.toLowerCase();
const b_name = b.children[0].textContent.toLowerCase();
return a_name < b_name ? -1 : a_name > b_name ? 1 : 0;
}, null, null, thead_name, [thead_date, thead_size]);
g_first_row = g_tbody.firstChild;
g_last_row = g_tbody.lastChild;
});
thead_date.classList.toggle("clickable");
thead_date.addEventListener('click', e => {
e.preventDefault();
sortTable((a,b) => {
const a_date = new Date(a.children[1].textContent.slice(0, -4));
const b_date = new Date(b.children[1].textContent.slice(0, -4));
return a_date - b_date;
}, null, null, thead_date, [thead_name, thead_size]);
e.preventDefault();
sortTable((a,b) => {
const a_date = new Date(a.children[1].textContent.slice(0, -4));
const b_date = new Date(b.children[1].textContent.slice(0, -4));
return a_date - b_date;
}, null, null, thead_date, [thead_name, thead_size]);
g_first_row = g_tbody.firstChild;
g_last_row = g_tbody.lastChild;
});
const units = {"B": 0, "KiB": 1, "MiB": 2, "GiB": 3, "TiB": 4};
function sizeToBytes(size, unit) {
if (units[unit] == 0) return size;
for (let i = 0; i <= units[unit]; ++i) size *= 1024;
return size;
if (units[unit] == 0) return size;
for (let i = 0; i <= units[unit]; ++i) size *= 1024;
return size;
}
thead_size.classList.toggle("clickable");
thead_size.addEventListener('click', e => {
e.preventDefault();
sortTable(
(a,b) => {
if (a.textContent == "DIR")
return 1;
let [a_size, a_unit] = a.children[2].textContent.split(" ");
let [b_size, b_unit] = b.children[2].textContent.split(" ");
return sizeToBytes(+a_size, a_unit) - sizeToBytes(+b_size, b_unit);
},
e => e.children[2].textContent == "DIR",
e => e.children[2].textContent != "DIR",
thead_size, [thead_name, thead_date]);
e.preventDefault();
sortTable(
(a,b) => {
if (a.textContent == "DIR")
return 1;
let [a_size, a_unit] = a.children[2].textContent.split(" ");
let [b_size, b_unit] = b.children[2].textContent.split(" ");
return sizeToBytes(+a_size, a_unit) - sizeToBytes(+b_size, b_unit);
},
e => e.children[2].textContent == "DIR",
e => e.children[2].textContent != "DIR",
thead_size, [thead_name, thead_date]);
g_first_row = g_tbody.firstChild;
g_last_row = g_tbody.lastChild;
});
function sortTable(compareFn, filterFn, filterNegFn, target, other) {
let records = Array.from(document.getElementsByTagName('tbody')[0].children);
let records = Array.from(g_tbody.children);
let dirs = [];
if (filterFn != null) {
dirs = records.filter(filterFn);
records = records.filter(filterNegFn);
}
let dirs = [];
if (filterFn != null) {
dirs = records.filter(filterFn);
records = records.filter(filterNegFn);
}
records.sort(compareFn);
records.sort(compareFn);
tbody.textContent = "";
g_tbody.textContent = "";
other.forEach(v => {
v.classList.remove("sort-up");
v.classList.remove("sort-down");
});
other.forEach(v => {
v.classList.remove("sort-up");
v.classList.remove("sort-down");
});
if (filterFn != null)
tbody.append(...dirs);
if (filterFn != null)
g_tbody.append(...dirs);
if (g_sort_reverse) {
tbody.append(...records.reverse());
target.classList.add("sort-up");
target.classList.remove("sort-down");
} else {
tbody.append(...records);
target.classList.add("sort-down");
target.classList.remove("sort-up");
}
g_sort_reverse = !g_sort_reverse;
if (g_sort_reverse) {
g_tbody.append(...records.reverse());
target.classList.add("sort-up");
target.classList.remove("sort-down");
} else {
g_tbody.append(...records);
target.classList.add("sort-down");
target.classList.remove("sort-up");
}
g_sort_reverse = !g_sort_reverse;
}

View File

@ -1,61 +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"
)
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><table><thead><tr><th>Name</th><th>Date</th><th>Size</th></tr><tr><td><a href="../">../</a></td></tr></thead><tbody>`
index__6 = `</tbody></table></section><section><span>`
index__7 = `</span></section><section id="usage"><p>In an overlay you can use your mouse wheel to change scale of a video or a picture. 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&mdash;2022 Arav &lt;<a href="mailto:me@arav.top">me@arav.top</a>&gt;</footer><div id="overlay"></div></body></html>`
index__8 = `<tr><td><a href="`
index__9 = `">`
index__10 = `</a></td><td>`
index__11 = `</td><td>`
index__12 = `</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)
for _, item := range *items {
buffer.WriteString(index__8)
buffer.WriteString(html.EscapeString(fmt.Sprintf("%v", item.Link)))
buffer.WriteString(index__9)
buffer.WriteString(html.EscapeString(fmt.Sprintf("%v", item.Name)))
buffer.WriteString(index__10)
buffer.WriteString(utils.ToClientTimezone(item.Datetime, r).Format(files.FileDateFormat))
buffer.WriteString(index__11)
buffer.WriteString(html.EscapeString(fmt.Sprintf("%v", item.Size)))
buffer.WriteString(index__12)
}
buffer.WriteString(index__6)
buffer.WriteString(currentPath)
buffer.WriteString(index__7)
}

View File

@ -1,141 +0,0 @@
// Code generated by "jade.go"; DO NOT EDIT.
package web
import (
"bytes"
"io"
"strconv"
)
var (
escaped = []byte{'<', '>', '"', '\'', '&'}
replacing = []string{"&lt;", "&gt;", "&#34;", "&#39;", "&amp;"}
)
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")
}

23
web/templates/base.jade Normal file
View File

@ -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&mdash;2023 Alexander &quot;Arav&quot; Andreev &lt;#[a(href='mailto:me@arav.su') me@arav.su]&gt; #[a(href='/privacy') Privacy statements]

View File

@ -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]

View File

@ -1,53 +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}.
table
thead
tr
th Name
th Date
th Size
tr
td #[a(href="../") ../]
tbody
each item in *items
tr
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 In an overlay you can use your mouse wheel to change scale of a video or a picture. 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&mdash;2022 Arav &lt;#[a(href='mailto:me@arav.top') me@arav.top]&gt;
div#overlay
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') &#10096;
div
div
span
button(name='next') &#10097;

View File

@ -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) {