From 1537da9b16dfda1ecb2730b5d1de3b9005397618 Mon Sep 17 00:00:00 2001 From: lost+skunk Date: Tue, 13 Aug 2024 15:59:52 +0300 Subject: темплейты в бинарнике и улучшенная система кеша MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/cache.go | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++ app/cli.go | 63 +++++++++++------------- app/config.go | 16 ++++--- app/parsers.go | 8 ++-- app/router.go | 33 ++++++++++--- app/stat-freebsd.go | 13 +++++ app/stat.go | 13 +++++ app/util.go | 136 +++++++++++++--------------------------------------- app/wrapper.go | 40 +++++++++------- 9 files changed, 283 insertions(+), 171 deletions(-) create mode 100644 app/cache.go create mode 100644 app/stat-freebsd.go create mode 100644 app/stat.go (limited to 'app') diff --git a/app/cache.go b/app/cache.go new file mode 100644 index 0000000..dedba83 --- /dev/null +++ b/app/cache.go @@ -0,0 +1,132 @@ +// TODO: реализовать кеширование JSON и почистить код +package app + +import ( + "crypto/sha1" + "encoding/hex" + "io" + "os" + "strings" + "sync" + "syscall" + "time" +) + +type file struct { + Score int + Content []byte +} + +var tempFS = make(map[[20]byte]*file) +var mx = &sync.RWMutex{} + +func (s skunkyart) DownloadAndSendMedia(subdomain, path string) { + var url strings.Builder + url.WriteString("https://images-wixmp-") + url.WriteString(subdomain) + url.WriteString(".wixmp.com/") + url.WriteString(path) + url.WriteString("?token=") + url.WriteString(s.Args.Get("token")) + + var response []byte + + switch { + case CFG.Cache.Enabled: + fileName := sha1.Sum([]byte(subdomain + path)) + filePath := CFG.Cache.Path + "/" + hex.EncodeToString(fileName[:]) + + mx.Lock() + if tempFS[fileName] == nil { + tempFS[fileName] = &file{} + } + f := *tempFS[fileName] + mx.Unlock() + + if f.Content != nil { + f.Score += 2 + } else { + file, err := os.Open(filePath) + if err != nil { + if dwnld := Download(url.String()); dwnld.Status == 200 && dwnld.Headers["Content-Type"][0][:5] == "image" { + f.Content = dwnld.Body + try(os.WriteFile(filePath, f.Content, 0700)) + } else { + s.ReturnHTTPError(dwnld.Status) + return + } + } else { + file, e := io.ReadAll(file) + try(e) + f.Content = file + } + + go func() { + defer restore() + for { + time.Sleep(1 * time.Minute) + + mx.Lock() + if tempFS[fileName].Score <= 0 { + delete(tempFS, fileName) + mx.Unlock() + return + } + tempFS[fileName].Score-- + mx.Unlock() + } + }() + } + + mx.Lock() + tempFS[fileName] = &f + mx.Unlock() + response = f.Content + case CFG.Proxy: + dwnld := Download(url.String()) + if dwnld.Status != 200 { + s.ReturnHTTPError(dwnld.Status) + return + } + response = dwnld.Body + default: + s.Writer.WriteHeader(403) + response = []byte("Sorry, butt proxy on this instance are disabled.") + } + + s.Writer.Write(response) +} + +func InitCacheSystem() { + c := &CFG.Cache + os.Mkdir(c.Path, 0700) + for { + dir, e := os.Open(c.Path) + try(e) + stat, e := dir.Stat() + try(e) + + dirnames, e := dir.Readdirnames(-1) + try(e) + for _, a := range dirnames { + a = c.Path + "/" + a + if c.Lifetime != "" { + now := time.Now().UnixMilli() + + f, _ := os.Stat(a) + stat := f.Sys().(*syscall.Stat_t) + time := statTime(stat) + + if time+lifetimeParsed <= now { + try(os.RemoveAll(a)) + } + } + if c.MaxSize != 0 && stat.Size() > c.MaxSize { + try(os.RemoveAll(a)) + } + } + + dir.Close() + time.Sleep(time.Second * time.Duration(c.UpdateInterval)) + } +} diff --git a/app/cli.go b/app/cli.go index 2247223..261e103 100644 --- a/app/cli.go +++ b/app/cli.go @@ -78,16 +78,16 @@ func addInstance() { try(err) defer instancesJson.Close() - instances, err := os.OpenFile("INSTANCES.md", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + instancesFile, err := os.OpenFile("INSTANCES.md", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) try(err) - defer instances.Close() + defer instancesFile.Close() for { - if Templates["instances.json"] == "" { + if string(instances) == "" { print("\rDownloading instance list...") } else { println("\r\033[2KDownloaded!") - try(json.Unmarshal([]byte(Templates["instances.json"]), &settingsVar)) + try(json.Unmarshal(instances, &settingsVar)) settingsVar.Instances = append(settingsVar.Instances, settings{ Title: prompt("Title", true), @@ -113,51 +113,46 @@ func addInstance() { settingsVar := &settingsVar.Instances[len(settingsVar.Instances)-1] var mdstr bytes.Buffer - mdstr.WriteString("\n|") - if settingsVar.Urls.Clearnet != "" { - mdstr.WriteString("[") - mdstr.WriteString(settingsVar.Title) - mdstr.WriteString("](") - mdstr.WriteString(settingsVar.Urls.Clearnet) - mdstr.WriteString(")") - } else { - mdstr.WriteString(settingsVar.Title) + mdbuilder := func(yes bool, link string, title string) { + switch { + case yes && (title != "" && link != ""): + mdstr.WriteString("[") + mdstr.WriteString(title) + mdstr.WriteString("](") + mdstr.WriteString(link) + mdstr.WriteString(")") + case yes && link != "": + mdstr.WriteString("[Yes](") + mdstr.WriteString(link) + mdstr.WriteString(")") + case yes: + mdstr.WriteString("Yes") + default: + mdstr.WriteString("No") + } + mdstr.WriteString("|") } - mdstr.WriteString("|") + + mdstr.WriteString("\n|") + mdbuilder(settingsVar.Urls.Clearnet != "", settingsVar.Urls.Clearnet, settingsVar.Title) urls := []string{settingsVar.Urls.Ygg, settingsVar.Urls.I2P, settingsVar.Urls.Tor} for i, l := 0, len(urls); i < l; i++ { url := urls[i] - if url != "" { - mdstr.WriteString("[Yes](") - mdstr.WriteString(url) - mdstr.WriteString(")|") - } else { - mdstr.WriteString("No|") - } + mdbuilder(url != "", url, "") } settings := []bool{settingsVar.Settings.Nsfw, settingsVar.Settings.Proxy} for i, l := 0, len(settings); i < l; i++ { - if settings[i] { - mdstr.WriteString("Yes|") - } else { - mdstr.WriteString("No|") - } + mdbuilder(settings[i], "", "") } - if settingsVar.ModifiedSrc != "" { - mdstr.WriteString("[Yes](") - mdstr.WriteString(settingsVar.ModifiedSrc) - mdstr.WriteString(")|") - } else { - mdstr.WriteString("No|") - } + mdbuilder(settingsVar.ModifiedSrc != "", settingsVar.ModifiedSrc, "") mdstr.WriteString(settingsVar.Country) mdstr.WriteString("|") - instances.Write(mdstr.Bytes()) + instancesFile.Write(mdstr.Bytes()) break } time.Sleep(500 * time.Millisecond) diff --git a/app/config.go b/app/config.go index a6ac756..7d159e7 100644 --- a/app/config.go +++ b/app/config.go @@ -4,6 +4,7 @@ import ( "encoding/json" "os" "regexp" + "skunkyart/static" "strconv" "time" @@ -24,9 +25,9 @@ type config struct { URI string `json:"uri"` Cache cache_config Proxy, Nsfw bool - UserAgent string `json:"user-agent"` - DownloadProxy string `json:"download-proxy"` - Dirs []string `json:"dirs-to-memory"` + UserAgent string `json:"user-agent"` + DownloadProxy string `json:"download-proxy"` + StaticPath string `json:"static-path"` } var CFG = config{ @@ -38,10 +39,10 @@ var CFG = config{ Path: "cache", UpdateInterval: 1, }, - Dirs: []string{"html", "css", "misc"}, - UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36", - Proxy: true, - Nsfw: true, + StaticPath: "static", + UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36", + Proxy: true, + Nsfw: true, } var lifetimeParsed int64 @@ -56,6 +57,7 @@ func ExecuteConfig() { exit("Incompatible settings detected: cannot use caching media content without proxy", 1) } + static.StaticPath = CFG.StaticPath if CFG.Cache.Enabled { if CFG.Cache.Lifetime != "" { var duration int64 diff --git a/app/parsers.go b/app/parsers.go index b39e172..f93c18d 100644 --- a/app/parsers.go +++ b/app/parsers.go @@ -41,7 +41,9 @@ func (s skunkyart) ParseComments(c devianter.Comments) string { cmmts.WriteString(" ") if x.Parent > 0 { - cmmts.WriteString(` In reply to `) if replied[x.Parent] == "" { @@ -80,7 +82,7 @@ func (s skunkyart) DeviationList(devs []devianter.Deviation, allowAtom bool, con for i, l := 0, len(devs); i < l; i++ { data := &devs[i] - if preview, fullview := ParseMedia(data.Media, 320), ParseMedia(data.Media); !(data.NSFW && !CFG.Nsfw) { + if preview, fullview := ParseMedia(data.Media, data.Title, 320), ParseMedia(data.Media, data.Title); !(data.NSFW && !CFG.Nsfw) { if allowAtom && s.Atom { id := strconv.Itoa(data.ID) listContent.WriteString(``) @@ -284,7 +286,7 @@ func ParseDescription(dscr devianter.Text) string { parsedDescription.WriteString(` c.MaxSize { - try(os.RemoveAll(a)) - } - } - - dir.Close() - time.Sleep(time.Second * time.Duration(CFG.Cache.UpdateInterval)) - } -} - -func CopyTemplatesToMemory() { - for _, dirname := range CFG.Dirs { - dir, e := os.ReadDir(dirname) - tryWithExitStatus(e, 1) - - for _, x := range dir { - file, e := os.ReadFile(dirname + "/" + x.Name()) - tryWithExitStatus(e, 1) - Templates[x.Name()] = string(file) - } - } -} - /* PARSING HELPERS */ -func ParseMedia(media devianter.Media, thumb ...int) string { - url := devianter.UrlFromMedia(media, thumb...) - if len(url) != 0 && CFG.Proxy { - url = url[21:] - dot := strings.Index(url, ".") +func ParseMedia(media devianter.Media, filename string, thumb ...int) string { + mediaUrl := devianter.UrlFromMedia(media, thumb...) + if len(mediaUrl) != 0 && CFG.Proxy { + mediaUrl = mediaUrl[21:] + dot := strings.Index(mediaUrl, ".") - return UrlBuilder("media", "file", url[:dot], url[dot+11:]) + return UrlBuilder("media", "file", mediaUrl[:dot], mediaUrl[dot+11:], "&filename=", url.QueryEscape(filename)) } - return url + return mediaUrl } func ConvertDeviantArtUrlToSkunkyArt(url string) (output string) { @@ -255,7 +181,9 @@ func (s skunkyart) NavBase(c DeviationList) string { list.WriteString("
") prevrev := func(msg string, page int, onpage bool) { if !onpage { - list.WriteString(`
`) @@ -180,7 +180,7 @@ func (s skunkyart) GRUser() { } folders.WriteString("
") - folders.WriteString(`