Last active
January 9, 2024 16:32
-
-
Save jjkavalam/cf07e51ac6e5b309e468bfccb4aae541 to your computer and use it in GitHub Desktop.
TiddlyWiki Saver Server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// From a directory that contains your TiddlyWiki file (or download https://tiddlywiki.com/empty.html if you don't have one) | |
// `go run server.go` | |
// Navigate to the printed URL and open your wiki file | |
// Enjoy your changes getting saved ! (A backup of the current state is made to the `bak` directory before overwriting) | |
// You may configure the number of backups to keep below (See `numBackups`) | |
package main | |
import ( | |
"fmt" | |
"io" | |
"io/fs" | |
"log" | |
"net/http" | |
"os" | |
"path/filepath" | |
"sort" | |
"strconv" | |
"strings" | |
) | |
const backupBaseDir = "bak" | |
const numBackups = 5 | |
func main() { | |
var root string | |
if len(os.Args) != 1 { | |
root = os.Args[1] | |
} else { | |
root = "." | |
} | |
fileServerHandler := http.FileServer(http.Dir(root)) | |
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | |
switch r.Method { | |
case "GET": | |
fileServerHandler.ServeHTTP(w, r) | |
case "PUT": | |
handlePut(w, r, root) | |
case "OPTIONS": | |
handleOptions(w, r) | |
default: | |
w.WriteHeader(200) | |
} | |
}) | |
server := &http.Server{ | |
Addr: "127.0.0.1:8000", | |
Handler: http.DefaultServeMux, | |
} | |
go func() { | |
if err := server.ListenAndServe(); err != nil { | |
fmt.Println(err) | |
} | |
}() | |
fmt.Println("TiddlyWiki Backup Server is running at http://127.0.0.1:8000/") | |
fmt.Println("Press Ctrl+C to shutdown...") | |
select {} | |
} | |
func handlePut(w http.ResponseWriter, r *http.Request, root string) { | |
backupDir := filepath.Join(root, backupBaseDir, r.URL.Path) | |
filePath := filepath.Join(root, r.URL.Path) | |
if strings.HasPrefix(r.URL.Path, "/"+backupBaseDir+"/") { | |
// the file being saved is itself inside the backup dir | |
w.WriteHeader(http.StatusBadRequest) | |
return | |
} | |
writeErr := func(err error) { | |
log.Printf("error: %s", err) | |
http.Error(w, "Internal Server Error", http.StatusInternalServerError) | |
} | |
if err := os.MkdirAll(backupDir, os.ModePerm); err != nil { | |
writeErr(err) | |
return | |
} | |
// List existing backup files | |
files, err := os.ReadDir(backupDir) | |
if err != nil { | |
writeErr(err) | |
return | |
} | |
var fInfo1, fInfo2 fs.FileInfo | |
var sErr error | |
// Sort files by modification time (oldest first) | |
sort.Slice(files, func(i, j int) bool { | |
if sErr != nil { | |
return false | |
} | |
fInfo1, sErr = files[i].Info() | |
if sErr != nil { | |
return false | |
} | |
fInfo2, sErr = files[j].Info() | |
if sErr != nil { | |
return false | |
} | |
return fInfo1.ModTime().Before(fInfo2.ModTime()) | |
}) | |
if sErr != nil { | |
writeErr(sErr) | |
return | |
} | |
var backupPath string | |
if len(files) < numBackups { | |
backupPath = filepath.Join(backupDir, strconv.Itoa(len(files)+1)) | |
} else { | |
backupPath = filepath.Join(backupDir, files[0].Name()) | |
} | |
fmt.Print(".") | |
src, err := os.Open(filePath) | |
if err != nil { | |
http.Error(w, "Not Found", http.StatusNotFound) | |
return | |
} | |
defer src.Close() | |
dst, err := os.Create(backupPath) | |
if err != nil { | |
writeErr(err) | |
return | |
} | |
defer dst.Close() | |
if _, err := io.Copy(dst, src); err != nil { | |
writeErr(err) | |
return | |
} | |
body, err := io.ReadAll(r.Body) | |
if err != nil { | |
writeErr(err) | |
return | |
} | |
if err := os.WriteFile(filePath, body, os.ModePerm); err != nil { | |
writeErr(err) | |
return | |
} | |
w.WriteHeader(http.StatusOK) | |
} | |
func handleOptions(w http.ResponseWriter, r *http.Request) { | |
w.Header().Set("Allow", "GET,HEAD,OPTIONS,PUT") | |
// TiddlyWiki looks for a 'dav' header, and ignores any value | |
w.Header().Set("dav", "anything") | |
} |
# run tiddly server at the current path
alias tiddly='go run path/to/server.go `pwd`'
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is largely a Go rewrite of https://gist.github.com/jimfoltz/ee791c1bdd30ce137bc23cce826096da