diff options
| author | Xe Iaso <me@xeiaso.net> | 2024-06-08 13:39:52 -0700 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2024-06-08 13:41:05 -0700 |
| commit | bb8a5a886c993fc943277e56b4c9065c1f3a82b2 (patch) | |
| tree | 14de5be85b83b17553092f1f1c78b2b0d0b4cba3 /internal | |
| parent | 2f9125d0cbe232dbb0dcd64eab33af356d5d615c (diff) | |
| download | x-bb8a5a886c993fc943277e56b4c9065c1f3a82b2.tar.xz x-bb8a5a886c993fc943277e56b4c9065c1f3a82b2.zip | |
cmd: add command future-sight for preview deployments
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/xesite/xesite.go | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/internal/xesite/xesite.go b/internal/xesite/xesite.go new file mode 100644 index 0000000..7ef525b --- /dev/null +++ b/internal/xesite/xesite.go @@ -0,0 +1,210 @@ +package xesite + +import ( + "archive/zip" + "compress/gzip" + "encoding/json" + "fmt" + "io" + "log/slog" + "net/http" + "os" + "path/filepath" + "sync" + "time" +) + +const ( + compressionGZIP = 0x69 +) + +func init() { + zip.RegisterCompressor(compressionGZIP, func(w io.Writer) (io.WriteCloser, error) { + return gzip.NewWriterLevel(w, gzip.BestCompression) + }) + zip.RegisterDecompressor(compressionGZIP, func(r io.Reader) io.ReadCloser { + rdr, err := gzip.NewReader(r) + if err != nil { + slog.Error("can't read from gzip stream", "err", err) + panic(err) + } + return rdr + }) +} + +type ZipServer struct { + dir string + lock sync.RWMutex + zip *zip.ReadCloser +} + +func NewZipServer(zipPath, dir string) (*ZipServer, error) { + result := &ZipServer{dir: dir} + + if _, err := os.Stat(zipPath); !os.IsNotExist(err) { + file, err := zip.OpenReader(zipPath) + if err != nil { + return nil, err + } + + result.zip = file + } + + return result, nil +} + +func (zs *ZipServer) NukeGeneration(w http.ResponseWriter, r *http.Request) { + gen := r.URL.Query().Get("gen") + + os.Remove(filepath.Join(zs.dir, "xesite", gen)) + + fmt.Fprintln(w, "removed", gen) +} + +func (zs *ZipServer) ListGenerations(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + // list files in zs.dir/xesite + var files []string + + dirEntries, err := os.ReadDir(filepath.Join(zs.dir, "xesite")) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + slog.Error("can't read dir", "err", err, "dir", filepath.Join(zs.dir, "xesite")) + return + } + + for _, dirEntry := range dirEntries { + if dirEntry.IsDir() { + continue + } + + if dirEntry.Name() == "latest.zip" { + continue + } + + files = append(files, dirEntry.Name()) + } + + json.NewEncoder(w).Encode(files) +} + +func (zs *ZipServer) UploadNewZip(w http.ResponseWriter, r *http.Request) { + fname := fmt.Sprintf("xesite-%s.zip", time.Now().Format("2006-01-02T15-04-05")) + fpath := filepath.Join(zs.dir, "xesite", fname) + fout, err := os.Create(fpath) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + slog.Error("can't create file", "err", err, "fpath", fpath) + return + } + defer fout.Close() + + if _, err := io.Copy(fout, r.Body); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + slog.Error("can't write file", "err", err, "fpath", fpath) + return + } + + if err := fout.Close(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + slog.Error("can't close file", "err", err, "fpath", fpath) + return + } + + if err := zs.Update(fpath); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + slog.Error("can't update zip", "err", err, "fpath", fpath) + return + } + + os.Link(fpath, filepath.Join(zs.dir, "xesite", "latest.zip")) + if err := deleteOldestFileInFolder(filepath.Join(zs.dir, "xesite")); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + slog.Error("can't delete oldest file", "err", err) + return + } +} + +func (zs *ZipServer) Update(fname string) error { + zs.lock.Lock() + defer zs.lock.Unlock() + + old := zs.zip + + file, err := zip.OpenReader(fname) + if err != nil { + return err + } + + zs.zip = file + + if old != nil { + old.Close() + } + + slog.Info("activated new generation", "fname", fname) + return nil +} + +func (zs *ZipServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + zs.lock.RLock() + defer zs.lock.RUnlock() + + if zs.zip == nil { + http.Error(w, "no zip file", http.StatusNotFound) + return + } + + http.FileServer(http.FS(zs.zip)).ServeHTTP(w, r) +} + +func deleteOldestFileInFolder(folderPath string) error { + // Read the files in the folder + files, err := os.ReadDir(folderPath) + if err != nil { + return err + } + + if len(files) == 0 { + slog.Debug("no files in the folder") + return nil + } + + if len(files) < 5 { + slog.Debug("less than 5 files in the folder") + return nil + } + + // Initialize variables to keep track of the oldest file + var oldestFile os.DirEntry + var oldestTime time.Time + + // Find the oldest file in the folder + for _, file := range files { + fileInfo, err := file.Info() + if err != nil { + return err + } + + if fileInfo.Name() == "latest.zip" { + continue + } + + if oldestFile == nil || fileInfo.ModTime().Before(oldestTime) { + oldestFile = file + oldestTime = fileInfo.ModTime() + } + } + + // Delete the oldest file + oldestFilePath := filepath.Join(folderPath, oldestFile.Name()) + err = os.Remove(oldestFilePath) + if err != nil { + return err + } + + slog.Info("deleted oldest generation", "path", oldestFilePath) + + return nil +} |
