Compare commits

...

27 Commits

Author SHA1 Message Date
76dbbacd98 Bump version 2024-04-30 22:22:13 +09:30
ba31f52ee2 Use zip for windows releases 2022-12-05 23:44:06 +10:30
779f9e1992 Goodbye perl build script. 2022-12-05 23:36:16 +10:30
553337bfab Migrate to goreleaser for next version 2022-12-05 23:24:27 +10:30
73c74cd37e Start to rework tests 2022-12-05 22:04:40 +10:30
2bd62a95a4 Add arm binaries for mac and linux 2022-12-05 22:03:11 +10:30
3497bfd194 Add state reasons 2022-12-05 22:01:52 +10:30
ada43b176b Refactor image processing for more flexibility. Add support for resizing images if they exceed the discord 8Mb limit. 2022-11-01 13:06:16 +10:30
326807b395 Fix documentation on watch intervals. Fixes #26 2022-09-15 22:14:59 +09:30
4dd166e65e Update changelog for release 2022-05-09 09:57:26 +09:30
9f7090a2f8 Bump version 2022-05-08 11:39:54 +09:30
1bf557c3eb Update changelog 2022-05-08 11:39:14 +09:30
df0c6d090d Fix race condition causing multiple uploads 2022-05-08 11:38:07 +09:30
b851a4f773 🙏🏼 2022-05-02 21:21:00 +09:30
e612f8ae59 Update README 2022-05-01 17:47:47 +09:30
05ddf07fc4 Update (and fix old typos) in changelog 2022-05-01 17:42:23 +09:30
9423bc32e9 Automate the population of the windows exe metadata 2022-05-01 17:39:00 +09:30
243c349366 Tidy up, add discord link 2022-05-01 17:01:02 +09:30
d8b16674dd Only do systray stuff on windows 2022-05-01 16:51:52 +09:30
9294ca9e33 Windows exe resource stuff 2022-05-01 15:50:27 +09:30
2a99541f58 go mod tidy 2022-05-01 14:40:40 +09:30
b04ed6aa62 Add systray with icon 2022-05-01 12:37:05 +09:30
06ac259e0a Open browser on startup automatically, with configuration option to disable. 2022-05-01 11:55:20 +09:30
0c2fafdc7a Bump version 2022-05-01 11:32:25 +09:30
f9433ae0cd Update changelog 2022-04-04 19:45:21 +09:30
ba7ae21248 Refactor how logs are handled. 2022-04-04 19:10:07 +09:30
fbd267e687 Show if a new version is available in the web interface 2022-04-03 20:33:20 +09:30
33 changed files with 973 additions and 387 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
github: tardisx

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ discord-auto-upload.exe
*.png
*.jpg
.DS_Store
versioninfo.json

45
.goreleaser.yaml Normal file
View File

@ -0,0 +1,45 @@
before:
hooks:
# clean up/install modules
- go mod tidy
- "go run tools/windows_metadata/release.go v{{- .Version }}"
builds:
- main:
hooks:
pre:
- "rm -f resource.syso"
- "go generate"
ignore:
- goos: windows
goarch: arm64
ldflags:
- '{{ if eq .Os "windows" }}-H=windowsgui{{ end }}'
env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
archives:
- replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
format_overrides:
- goos: windows
format: zip
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- '^Merge:'
- '^docs:'
- '^test:'
- '^[Bb]ump'
- '^[Cc]lean'

View File

@ -1,6 +1,10 @@
{
"cSpell.words": [
"daulog",
"inconsolata"
"Debugf",
"inconsolata",
"Infof",
"Markedup",
"skratchdot"
]
}

View File

@ -3,7 +3,33 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
## [v0.12.0] - 2020-04-03
## [v0.13.0] - 2022-11-01
- Resize images if needed to fit in discord 8Mb upload limits
## [v0.12.4] - 2022-09-15
- Document that watcher intervals are in seconds
## [v0.12.3] - 2022-05-09
- Fix a race condition occasionally causing multiple duplicate uploads
## [v0.12.2] - 2022-05-01
- Automatically open your web browser to the `dau` web interface
(can be disabled in configuration)
- Add system tray/menubar icon with menus to open web interface, quit and
other links
- Superfluous text console removed on windows
## [v0.12.1] - 2022-05-01
- Show if a new version is available in the web interface
- Rework logging and fix the log display in the web interface
## [v0.12.0] - 2022-04-03
- Break upload page into pending/current/complete sections
- Add preview thumbnails for each upload

View File

@ -29,8 +29,13 @@ You'll need to [download Go](https://golang.org/dl/), check the code out somewhe
## Using it
`dau` configuration is managed via its internal web interface. When the executable is run, you can visit
`http://localhost:9090` in your web browser to configure the it. Configuration persists across runs, it is
saved in a file called '.dau.json' in your home directory.
`http://localhost:9090` in your web browser to configure it. On Windows, a tray icon is created to provide
access to the web interface.
The web browser will be loaded automatically when you start the program, if possible. This option can be
disabled in the settings.
Configuration persists across runs, it is saved in a file called '.dau.json' in your home directory.
The first time you run it, you will need to configure at least the discord web hook and the watch path for
`dau` to be useful.
@ -49,7 +54,7 @@ no changes will take effect until the "Save All Configuration" button has been p
### Global options
* Server port - the port number the web server listens on. Requires restart
* Watch interval - how often each watcher will check the directory for new files
* Watch interval - how often each watcher will check the directory for new files, in seconds
### Watcher configuration

View File

@ -1,45 +0,0 @@
#!/usr/bin/env perl
use strict;
use warnings;
open my $fh, "<", "version/version.go" || die $!;
my $version;
while (<$fh>) {
$version = $1 if /^const\s+CurrentVersion.*?"(v[\d\.]+)"/;
}
close $fh;
die "no version?" unless defined $version;
# quit if tests fail
system("go test ./...") && die "not building release with failing tests";
# so lazy
system "rm", "-rf", "release", "dist";
system "mkdir", "release";
system "mkdir", "dist";
my %build = (
win => { env => { GOOS => 'windows', GOARCH => 'amd64' }, filename => 'dau.exe' },
linux => { env => { GOOS => 'linux', GOARCH => 'amd64' }, filename => 'dau' },
mac => { env => { GOOS => 'darwin', GOARCH => 'amd64' }, filename => 'dau' },
);
foreach my $type (keys %build) {
mkdir "release/$type";
}
add_extras();
foreach my $type (keys %build) {
local $ENV{GOOS} = $build{$type}->{env}->{GOOS};
local $ENV{GOARCH} = $build{$type}->{env}->{GOARCH};
system "go", "build", "-o", "release/$type/" . $build{$type}->{filename};
system "zip", "-j", "dist/dau-$type-$version.zip", ( glob "release/$type/*" );
}
sub add_extras {
# we used to have a .bat file here, but no longer needed
}

View File

@ -38,8 +38,16 @@ type ConfigV2 struct {
Watchers []Watcher
}
type ConfigV3 struct {
WatchInterval int
Version int
Port int
OpenBrowserOnStart bool
Watchers []Watcher
}
type ConfigService struct {
Config *ConfigV2
Config *ConfigV3
Changed chan bool
ConfigFilename string
}
@ -54,11 +62,11 @@ func DefaultConfigService() *ConfigService {
// LoadOrInit loads the current configuration from the config file, or creates
// a new config file if none exists.
func (c *ConfigService) LoadOrInit() error {
daulog.SendLog(fmt.Sprintf("Trying to load config from %s\n", c.ConfigFilename), daulog.LogTypeDebug)
daulog.Debugf("Trying to load config from %s\n", c.ConfigFilename)
_, err := os.Stat(c.ConfigFilename)
if os.IsNotExist(err) {
daulog.SendLog("NOTE: No config file, writing out sample configuration", daulog.LogTypeInfo)
daulog.SendLog("You need to set the configuration via the web interface", daulog.LogTypeInfo)
daulog.Info("NOTE: No config file, writing out sample configuration")
daulog.Info("You need to set the configuration via the web interface")
c.Config = DefaultConfig()
return c.Save()
} else {
@ -66,11 +74,12 @@ func (c *ConfigService) LoadOrInit() error {
}
}
func DefaultConfig() *ConfigV2 {
c := ConfigV2{}
c.Version = 2
func DefaultConfig() *ConfigV3 {
c := ConfigV3{}
c.Version = 3
c.WatchInterval = 10
c.Port = 9090
c.OpenBrowserOnStart = true
w := Watcher{
WebHookURL: "https://webhook.url.here",
Path: "/your/screenshot/dir/here",
@ -84,7 +93,7 @@ func DefaultConfig() *ConfigV2 {
// Load will load the configuration from a known-to-exist config file.
func (c *ConfigService) Load() error {
fmt.Printf("Loading from %s\n\n", c.ConfigFilename)
daulog.Debugf("Loading from %s", c.ConfigFilename)
data, err := ioutil.ReadFile(c.ConfigFilename)
if err != nil {
@ -98,7 +107,7 @@ func (c *ConfigService) Load() error {
// Version 0 predates config migrations
if c.Config.Version == 0 {
// need to migrate this
daulog.SendLog("Migrating config to V2", daulog.LogTypeInfo)
daulog.Info("Migrating config to V2")
configV1 := ConfigV1{}
err = json.Unmarshal([]byte(data), &configV1)
@ -122,11 +131,18 @@ func (c *ConfigService) Load() error {
c.Config.Watchers = []Watcher{onlyWatcher}
}
if c.Config.Version == 2 {
// need to migrate this
daulog.Info("Migrating config to V3")
c.Config.Version = 3
c.Config.OpenBrowserOnStart = true
}
return nil
}
func (c *ConfigService) Save() error {
daulog.SendLog("saving configuration", daulog.LogTypeInfo)
daulog.Info("saving configuration")
// sanity checks
for _, watcher := range c.Config.Watchers {

View File

@ -22,8 +22,8 @@ func TestNoConfig(t *testing.T) {
t.Errorf("unexpected failure from load: %s", err)
}
if c.Config.Version != 2 {
t.Error("not version 2 starting config")
if c.Config.Version != 3 {
t.Error("not version 3 starting config")
}
if fileSize(c.ConfigFilename) < 40 {
@ -45,7 +45,7 @@ func TestEmptyFileConfig(t *testing.T) {
}
func TestMigrateFromV1toV2(t *testing.T) {
func TestMigrateFromV1toV3(t *testing.T) {
c := ConfigService{}
c.ConfigFilename = v1Config()
@ -53,8 +53,12 @@ func TestMigrateFromV1toV2(t *testing.T) {
if err != nil {
t.Error("unexpected error from LoadOrInit()")
}
if c.Config.Version != 2 {
t.Errorf("Version %d not 2", c.Config.Version)
if c.Config.Version != 3 {
t.Errorf("Version %d not 3", c.Config.Version)
}
if c.Config.OpenBrowserOnStart != true {
t.Errorf("Open browser on start not true")
}
if len(c.Config.Watchers) != 1 {

103
dau.go
View File

@ -2,13 +2,10 @@ package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"io/fs"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"strings"
@ -18,12 +15,12 @@ import (
_ "image/jpeg"
_ "image/png"
// "github.com/skratchdot/open-golang/open"
"github.com/tardisx/discord-auto-upload/config"
daulog "github.com/tardisx/discord-auto-upload/log"
"github.com/tardisx/discord-auto-upload/upload"
"github.com/skratchdot/open-golang/open"
// "github.com/tardisx/discord-auto-upload/upload"
"github.com/tardisx/discord-auto-upload/version"
"github.com/tardisx/discord-auto-upload/web"
@ -40,41 +37,58 @@ func main() {
parseOptions()
// grab the config, register to notice changes
config := config.DefaultConfigService()
// grab the conf, register to notice changes
conf := config.DefaultConfigService()
configChanged := make(chan bool)
config.Changed = configChanged
config.LoadOrInit()
conf.Changed = configChanged
conf.LoadOrInit()
// create the uploader
up := upload.NewUploader()
// log.Print("Opening web browser")
// open.Start("http://localhost:9090")
web := web.WebService{Config: config, Uploader: up}
web := web.WebService{Config: conf, Uploader: up}
web.StartWebServer()
go func() { checkUpdates() }()
if conf.Config.OpenBrowserOnStart {
openWebBrowser(conf.Config.Port)
}
go func() {
version.GetOnlineVersion()
if version.UpdateAvailable() {
daulog.Info("*** NEW VERSION AVAILABLE ***")
daulog.Infof("You are currently on version %s, but version %s is available\n", version.CurrentVersion, version.LatestVersionInfo.TagName)
daulog.Info("----------- Release Info -----------")
daulog.Info(version.LatestVersionInfo.Body)
daulog.Info("------------------------------------")
daulog.Info("Upgrade at https://github.com/tardisx/discord-auto-upload/releases/latest")
}
}()
// create the watchers, restart them if config changes
// blocks forever
startWatchers(config, up, configChanged)
go func() {
startWatchers(conf, up, configChanged)
}()
mainloop(conf)
}
func startWatchers(config *config.ConfigService, up *upload.Uploader, configChange chan bool) {
for {
daulog.SendLog("Creating watchers", daulog.LogTypeInfo)
daulog.Debug("Creating watchers")
ctx, cancel := context.WithCancel(context.Background())
for _, c := range config.Config.Watchers {
log.Printf("Creating watcher for %s interval %d", c.Path, config.Config.WatchInterval)
daulog.Infof("Creating watcher for %s with interval %d", c.Path, config.Config.WatchInterval)
watcher := watch{uploader: up, lastCheck: time.Now(), newLastCheck: time.Now(), config: c}
go watcher.Watch(config.Config.WatchInterval, ctx)
}
// wait for single that the config changed
<-configChange
cancel()
daulog.SendLog("starting new watchers due to config change", daulog.LogTypeInfo)
daulog.Info("starting new watchers due to config change")
}
}
@ -83,7 +97,7 @@ func (w *watch) Watch(interval int, ctx context.Context) {
for {
select {
case <-ctx.Done():
daulog.SendLog("Killing old watcher", daulog.LogTypeInfo)
daulog.Info("Killing old watcher")
return
default:
newFiles := w.ProcessNewFiles()
@ -92,7 +106,7 @@ func (w *watch) Watch(interval int, ctx context.Context) {
}
// upload them
w.uploader.Upload()
daulog.SendLog(fmt.Sprintf("sleeping for %ds before next check of %s", interval, w.config.Path), daulog.LogTypeDebug)
daulog.Debugf("sleeping for %ds before next check of %s", interval, w.config.Path)
time.Sleep(time.Duration(interval) * time.Second)
}
}
@ -115,6 +129,7 @@ func (w *watch) ProcessNewFiles() []string {
}
w.lastCheck = w.newLastCheck
}
return newFiles
}
@ -123,11 +138,11 @@ func (w *watch) ProcessNewFiles() []string {
func (w *watch) checkPath() bool {
src, err := os.Stat(w.config.Path)
if err != nil {
daulog.SendLog(fmt.Sprintf("Problem with path '%s': %s", w.config.Path, err), daulog.LogTypeError)
daulog.Errorf("Problem with path '%s': %s", w.config.Path, err)
return false
}
if !src.IsDir() {
daulog.SendLog(fmt.Sprintf("Problem with path '%s': is not a directory", w.config.Path), daulog.LogTypeError)
daulog.Errorf("Problem with path '%s': is not a directory", w.config.Path)
return false
}
return true
@ -169,51 +184,6 @@ func (w *watch) checkFile(path string, found *[]string, exclusions []string) err
return nil
}
func checkUpdates() {
type GithubRelease struct {
HTMLURL string `json:"html_url"`
TagName string `json:"tag_name"`
Name string `json:"name"`
Body string `json:"body"`
}
daulog.SendLog("checking for new version", daulog.LogTypeInfo)
client := &http.Client{Timeout: time.Second * 5}
resp, err := client.Get("https://api.github.com/repos/tardisx/discord-auto-upload/releases/latest")
if err != nil {
daulog.SendLog(fmt.Sprintf("WARNING: Update check failed: %v", err), daulog.LogTypeError)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal("could not check read update response")
}
var latest GithubRelease
err = json.Unmarshal(body, &latest)
if err != nil {
log.Fatal("could not parse JSON: ", err)
}
// pre v0.11.0 version (ie before semver) did a simple string comparison,
// but since "0.10.0" < "v0.11.0" they should still get prompted to upgrade
// ok
if version.NewVersionAvailable(latest.TagName) {
fmt.Printf("You are currently on version %s, but version %s is available\n", version.CurrentVersion, latest.TagName)
fmt.Println("----------- Release Info -----------")
fmt.Println(latest.Body)
fmt.Println("------------------------------------")
fmt.Println("Upgrade at https://github.com/tardisx/discord-auto-upload/releases/latest")
daulog.SendLog(fmt.Sprintf("New version available: %s - download at https://github.com/tardisx/discord-auto-upload/releases/latest", latest.TagName), daulog.LogTypeInfo)
}
daulog.SendLog("already running latest version", daulog.LogTypeInfo)
}
func parseOptions() {
var versionFlag bool
flag.BoolVar(&versionFlag, "version", false, "show version")
@ -226,3 +196,8 @@ func parseOptions() {
}
}
func openWebBrowser(port int) {
address := fmt.Sprintf("http://localhost:%d", port)
open.Start(address)
}

BIN
dau.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

12
dau_nonwindows.go Normal file
View File

@ -0,0 +1,12 @@
//go:build darwin || linux
// +build darwin linux
package main
import "github.com/tardisx/discord-auto-upload/config"
func mainloop(c *config.ConfigService) {
ch := make(chan bool)
<-ch
}

60
dau_windows.go Normal file
View File

@ -0,0 +1,60 @@
package main
import (
_ "embed"
"fmt"
"github.com/getlantern/systray"
"github.com/skratchdot/open-golang/open"
"github.com/tardisx/discord-auto-upload/config"
daulog "github.com/tardisx/discord-auto-upload/log"
"github.com/tardisx/discord-auto-upload/version"
)
//go:generate goversioninfo
//go:embed dau.ico
var appIcon []byte
func mainloop(c *config.ConfigService) {
systray.Run(func() { onReady(c) }, onExit)
}
func onReady(c *config.ConfigService) {
systray.SetIcon(appIcon)
//systray.SetTitle("DAU")
systray.SetTooltip(fmt.Sprintf("discord-auto-upload %s", version.CurrentVersion))
openApp := systray.AddMenuItem("Open", "Open in web browser")
gh := systray.AddMenuItem("Github", "Open project page")
discord := systray.AddMenuItem("Discord", "Join us on discord")
ghr := systray.AddMenuItem("Release Notes", "Open project release notes")
quit := systray.AddMenuItem("Quit", "Quit")
go func() {
<-quit.ClickedCh
systray.Quit()
}()
go func() {
for {
select {
case <-openApp.ClickedCh:
openWebBrowser(c.Config.Port)
case <-gh.ClickedCh:
open.Start("https://github.com/tardisx/discord-auto-upload")
case <-ghr.ClickedCh:
open.Start(fmt.Sprintf("https://github.com/tardisx/discord-auto-upload/releases/tag/%s", version.CurrentVersion))
case <-discord.ClickedCh:
open.Start("https://discord.gg/eErG9sntbZ")
}
}
}()
// Sets the icon of a menu item. Only available on Mac and Windows.
// mQuit.SetIcon(icon.Data)
}
func onExit() {
// clean up here
daulog.Info("quitting on user request")
}

7
go.mod
View File

@ -4,9 +4,12 @@ go 1.16
require (
github.com/fogleman/gg v1.3.0
github.com/getlantern/systray v1.2.1
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/mux v1.8.0
github.com/mitchellh/go-homedir v1.1.0
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/stretchr/testify v1.6.1 // indirect
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e
golang.org/x/mod v0.4.2
golang.org/x/mod v0.7.0
)

57
go.sum
View File

@ -1,23 +1,68 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk=
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
github.com/getlantern/systray v1.2.1 h1:udsC2k98v2hN359VTFShuQW6GGprRprw6kD6539JikI=
github.com/getlantern/systray v1.2.1/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e h1:PzJMNfFQx+QO9hrC1GwZ4BoPGeNGhfeQEgcQFArEjPk=
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

188
image/image.go Normal file
View File

@ -0,0 +1,188 @@
// Package image is responsible for thumbnailing, resizing and watermarking
// images.
package image
import (
"fmt"
i "image"
"image/jpeg"
"image/png"
"io"
"os"
daulog "github.com/tardisx/discord-auto-upload/log"
"golang.org/x/image/draw"
)
// the filenames below are ordered in a specific way
// In the simplest case we only need the original filename.
// In more complex cases, we might have other files, created
// temporarily. These all need to be cleaned up.
// We upload the "final" file, depending on what actions have
// been taken.
type Store struct {
OriginalFilename string
OriginalFormat string // jpeg, png
ModifiedFilename string // if the user applied modifications
ResizedFilename string // if the file had to be resized to be uploaded
WatermarkedFilename string
MaxBytes int
Watermark bool
}
// ReadCloser returns an io.ReadCloser providing the imagedata
// with the manglings that have been requested
func (s *Store) ReadCloser() (io.ReadCloser, error) {
// determine format
s.determineFormat()
// check if we will fit the number of bytes, resize if necessary
err := s.resizeToUnder(int64(s.MaxBytes))
if err != nil {
return nil, err
}
// the conundrum here is that the watermarking could modify the file size again, maybe going over
// the MaxBytes size. That would mostly be about jpeg compression levels I guess...
if s.Watermark {
s.applyWatermark()
}
// return the reader
f, err := os.Open(s.uploadSourceFilename())
if err != nil {
return nil, err
}
return f, nil
}
func (s *Store) determineFormat() error {
file, err := os.Open(s.OriginalFilename)
if err != nil {
panic(fmt.Errorf("could not open file: %s", err))
}
defer file.Close()
_, format, err := i.Decode(file)
if err != nil {
panic(fmt.Errorf("could not decode file: %s", err))
}
s.OriginalFormat = format
return nil
}
// resizeToUnder resizes the image, if necessary
func (s *Store) resizeToUnder(size int64) error {
fileToResize := s.uploadSourceFilename()
fi, err := os.Stat(s.uploadSourceFilename())
if err != nil {
return err
}
currentSize := fi.Size()
if currentSize <= size {
return nil // nothing needs to be done
}
daulog.Infof("%s is %d bytes, need to resize to fit in %d", fileToResize, currentSize, size)
file, err := os.Open(fileToResize)
if err != nil {
panic(fmt.Errorf("could not open file: %s", err))
}
defer file.Close()
im, _, err := i.Decode(file)
if err != nil {
panic(fmt.Errorf("could not decode file: %s", err))
}
// if the size is 10% too big, we reduce X and Y by 10% - this is overkill but should
// get us across the line in most cases
fraction := float64(currentSize) / float64(size) // say 1.1 for 10%
newXY := i.Point{
X: int(float64(im.Bounds().Max.X) / fraction),
Y: int(float64(im.Bounds().Max.Y) / fraction),
}
daulog.Infof("fraction is %f, will resize to %dx%d", fraction, newXY.X, newXY.Y)
dst := i.NewRGBA(i.Rect(0, 0, newXY.X, newXY.Y))
draw.BiLinear.Scale(dst, dst.Rect, im, im.Bounds(), draw.Over, nil)
resizedFile, err := os.CreateTemp("", "dau_resize_file_*")
if err != nil {
return err
}
if s.OriginalFormat == "png" {
err = png.Encode(resizedFile, dst)
if err != nil {
return err
}
} else if s.OriginalFormat == "jpeg" {
err = jpeg.Encode(resizedFile, dst, nil)
if err != nil {
return err
}
} else {
panic("unknown format " + s.OriginalFormat)
}
s.ResizedFilename = resizedFile.Name()
resizedFile.Close()
fi, err = os.Stat(s.uploadSourceFilename())
if err != nil {
return err
}
newSize := fi.Size()
if newSize <= size {
daulog.Infof("File resized, now %d", newSize)
return nil // nothing needs to be done
} else {
return fmt.Errorf("failed to resize: was %d, now %d, needed %d", currentSize, newSize, size)
}
}
// uploadSourceFilename gives us the filename, which might be a watermarked, resized
// or markedup version, depending on what has happened to this file.
func (s Store) uploadSourceFilename() string {
if s.WatermarkedFilename != "" {
return s.WatermarkedFilename
}
if s.ResizedFilename != "" {
return s.ResizedFilename
}
if s.ModifiedFilename != "" {
return s.ModifiedFilename
}
return s.OriginalFilename
}
// UploadFilename provides a name to be assigned to the upload on Discord
func (s Store) UploadFilename() string {
return "image." + s.OriginalFormat
}
// Cleanup removes all the temporary files that we might have created
func (s Store) Cleanup() {
daulog.Infof("cleaning temporary files %#v", s)
if s.ModifiedFilename != "" {
daulog.Infof("removing %s", s.ModifiedFilename)
os.Remove(s.ModifiedFilename)
}
if s.ResizedFilename != "" {
daulog.Infof("removing %s", s.ResizedFilename)
os.Remove(s.ResizedFilename)
}
if s.WatermarkedFilename != "" {
daulog.Infof("removing %s", s.WatermarkedFilename)
os.Remove(s.WatermarkedFilename)
}
}

View File

@ -1,14 +1,13 @@
package imageprocess
package image
import (
"fmt"
"image"
i "image"
"image/png"
"io"
"log"
"os"
"github.com/tardisx/discord-auto-upload/upload"
"golang.org/x/image/draw"
)
@ -17,15 +16,21 @@ const (
thumbnailMaxY = 128
)
func (ip *Processor) ThumbPNG(ul *upload.Upload, which string, w io.Writer) error {
type ThumbType = string
const ThumbTypeOriginal = "orig"
const ThumbTypeMarkedUp = "markedup"
// ThumbPNG writes a thumbnail out to an io.Writer
func (ip *Store) ThumbPNG(t ThumbType, w io.Writer) error {
var filename string
if which == "orig" {
filename = ul.OriginalFilename
} else if which == "markedup" {
filename = ul.MarkedUpFilename
if t == ThumbTypeOriginal {
filename = ip.OriginalFilename
} else if t == ThumbTypeMarkedUp {
filename = ip.ModifiedFilename
} else {
log.Fatal("was passed incorrect 'which' arg")
log.Fatal("was passed incorrect 'type' arg")
}
file, err := os.Open(filename)
@ -33,12 +38,12 @@ func (ip *Processor) ThumbPNG(ul *upload.Upload, which string, w io.Writer) erro
return fmt.Errorf("could not open file: %s", err)
}
defer file.Close()
im, _, err := image.Decode(file)
im, _, err := i.Decode(file)
if err != nil {
return fmt.Errorf("could not decode file: %s", err)
}
newXY := image.Point{}
newXY := i.Point{}
if im.Bounds().Max.X/thumbnailMaxX > im.Bounds().Max.Y/thumbnailMaxY {
newXY.X = thumbnailMaxX
newXY.Y = im.Bounds().Max.Y / (im.Bounds().Max.X / thumbnailMaxX)
@ -47,7 +52,7 @@ func (ip *Processor) ThumbPNG(ul *upload.Upload, which string, w io.Writer) erro
newXY.X = im.Bounds().Max.X / (im.Bounds().Max.Y / thumbnailMaxY)
}
dst := image.NewRGBA(image.Rect(0, 0, newXY.X, newXY.Y))
dst := i.NewRGBA(i.Rect(0, 0, newXY.X, newXY.Y))
draw.BiLinear.Scale(dst, dst.Rect, im, im.Bounds(), draw.Over, nil)
png.Encode(w, dst)

64
image/watermark.go Normal file
View File

@ -0,0 +1,64 @@
package image
import (
"fmt"
i "image"
"image/jpeg"
"image/png"
"os"
daulog "github.com/tardisx/discord-auto-upload/log"
"github.com/fogleman/gg"
"golang.org/x/image/font/inconsolata"
)
// applyWatermark applies the watermark to the image
func (s *Store) applyWatermark() error {
in, err := os.Open(s.uploadSourceFilename())
defer in.Close()
im, _, err := i.Decode(in)
if err != nil {
daulog.Errorf("Cannot decode image: %v - skipping watermarking", err)
return fmt.Errorf("cannot decode image: %w", err)
}
bounds := im.Bounds()
// var S float64 = float64(bounds.Max.X)
dc := gg.NewContext(bounds.Max.X, bounds.Max.Y)
dc.Clear()
dc.SetRGB(0, 0, 0)
dc.SetFontFace(inconsolata.Regular8x16)
dc.DrawImage(im, 0, 0)
dc.DrawRoundedRectangle(0, float64(bounds.Max.Y-18.0), 320, float64(bounds.Max.Y), 0)
dc.SetRGB(0, 0, 0)
dc.Fill()
dc.SetRGB(1, 1, 1)
dc.DrawString("github.com/tardisx/discord-auto-upload", 5.0, float64(bounds.Max.Y)-5.0)
waterMarkedFile, err := os.CreateTemp("", "dau_watermark_file_*")
if err != nil {
return err
}
defer waterMarkedFile.Close()
if s.OriginalFormat == "png" {
png.Encode(waterMarkedFile, dc.Image())
} else if s.OriginalFormat == "jpeg" {
jpeg.Encode(waterMarkedFile, dc.Image(), nil)
} else {
panic("Cannot handle " + s.OriginalFormat)
}
s.WatermarkedFilename = waterMarkedFile.Name()
return nil
}

View File

@ -1,4 +0,0 @@
package imageprocess
type Processor struct {
}

View File

@ -1,10 +1,14 @@
package log
import (
"log"
"fmt"
"time"
)
type Logger interface {
WriteEntry(l LogEntry)
}
type LogEntryType string
type LogEntry struct {
@ -19,28 +23,73 @@ const (
LogTypeDebug = "debug"
)
var LogEntries []LogEntry
var loggers []Logger
var logInput chan LogEntry
var Memory *MemoryLogger
func init() {
// create some loggers
Memory = &MemoryLogger{maxsize: 100}
stdout := &StdoutLogger{}
loggers = []Logger{Memory, stdout}
// wait for log entries
logInput = make(chan LogEntry)
go func() {
for {
aLog := <-logInput
LogEntries = append(LogEntries, aLog)
for len(LogEntries) > 100 {
LogEntries = LogEntries[1:]
for _, l := range loggers {
l.WriteEntry(aLog)
}
}
}()
}
func SendLog(entry string, entryType LogEntryType) {
func Debug(entry string) {
logInput <- LogEntry{
Timestamp: time.Now(),
Entry: entry,
Type: entryType,
Type: LogTypeDebug,
}
}
func Debugf(entry string, args ...interface{}) {
logInput <- LogEntry{
Timestamp: time.Now(),
Entry: fmt.Sprintf(entry, args...),
Type: LogTypeDebug,
}
}
func Info(entry string) {
logInput <- LogEntry{
Timestamp: time.Now(),
Entry: entry,
Type: LogTypeInfo,
}
}
func Infof(entry string, args ...interface{}) {
logInput <- LogEntry{
Timestamp: time.Now(),
Entry: fmt.Sprintf(entry, args...),
Type: LogTypeInfo,
}
}
func Error(entry string) {
logInput <- LogEntry{
Timestamp: time.Now(),
Entry: entry,
Type: LogTypeError,
}
}
func Errorf(entry string, args ...interface{}) {
logInput <- LogEntry{
Timestamp: time.Now(),
Entry: fmt.Sprintf(entry, args...),
Type: LogTypeError,
}
log.Printf("%6s: %s", entryType, entry)
}

29
log/memory.go Normal file
View File

@ -0,0 +1,29 @@
package log
import (
"sync"
)
type MemoryLogger struct {
size int
entries []LogEntry
maxsize int
lock sync.Mutex
}
func (m *MemoryLogger) WriteEntry(l LogEntry) {
// xxx needs mutex
// if m.entries == nil {
// m.entries = make([]LogEntry, 0)
// }
m.lock.Lock()
m.entries = append(m.entries, l)
if len(m.entries) > m.maxsize {
m.entries = m.entries[1:]
}
m.lock.Unlock()
}
func (m *MemoryLogger) Entries() []LogEntry {
return m.entries
}

12
log/stdout.go Normal file
View File

@ -0,0 +1,12 @@
package log
import (
"log"
)
type StdoutLogger struct {
}
func (m StdoutLogger) WriteEntry(l LogEntry) {
log.Printf("%-6s %s", l.Type, l.Entry)
}

View File

@ -0,0 +1,84 @@
package main
import (
"log"
"os"
"regexp"
"strings"
"golang.org/x/mod/semver"
)
const versionInfoTemplate = `
{
"FixedFileInfo": {
"FileVersion": {
"Major": MAJOR,
"Minor": MINOR,
"Patch": PATCH,
"Build": 0
},
"ProductVersion": {
"Major": MAJOR,
"Minor": MINOR,
"Patch": PATCH,
"Build": 0
},
"FileFlagsMask": "3f",
"FileFlags ": "00",
"FileOS": "040004",
"FileType": "01",
"FileSubType": "00"
},
"StringFileInfo": {
"Comments": "",
"CompanyName": "tardisx@github",
"FileDescription": "https://github.com/tardisx/discord-auto-upload",
"FileVersion": "",
"InternalName": "",
"LegalCopyright": "https://github.com/tardisx/discord-auto-upload/blob/master/LICENSE",
"LegalTrademarks": "",
"OriginalFilename": "",
"PrivateBuild": "",
"ProductName": "discord-auto-upload",
"ProductVersion": "VERSION",
"SpecialBuild": ""
},
"VarFileInfo": {
"Translation": {
"LangID": "0409",
"CharsetID": "04B0"
}
},
"IconPath": "dau.ico",
"ManifestPath": ""
}
`
var nonAlphanumericRegex = regexp.MustCompile(`[^0-9]+`)
func main() {
version := os.Args[1]
if !semver.IsValid(version) {
panic("bad version" + version)
}
parts := strings.Split(version, ".")
if len(parts) < 3 {
log.Fatalf("bad version: %s", version)
}
parts[0] = nonAlphanumericRegex.ReplaceAllString(parts[0], "")
parts[1] = nonAlphanumericRegex.ReplaceAllString(parts[1], "")
parts[2] = nonAlphanumericRegex.ReplaceAllString(parts[2], "")
out := versionInfoTemplate
out = strings.Replace(out, "MAJOR", parts[0], -1)
out = strings.Replace(out, "MINOR", parts[1], -1)
out = strings.Replace(out, "PATCH", parts[2], -1)
out = strings.Replace(out, "VERSION", version, -1)
f, _ := os.Create("versioninfo.json")
f.Write([]byte(out))
f.Close()
}

View File

@ -7,32 +7,31 @@ import (
"encoding/json"
"errors"
"fmt"
"image"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"sync"
"sync/atomic"
"time"
"github.com/fogleman/gg"
"github.com/tardisx/discord-auto-upload/config"
"github.com/tardisx/discord-auto-upload/image"
daulog "github.com/tardisx/discord-auto-upload/log"
"golang.org/x/image/font/inconsolata"
)
type State string
const (
StatePending State = "Pending" // waiting for decision to upload (could be edited)
StateQueued State = "Queued" // ready for upload
StateUploading State = "Uploading" // uploading
StateComplete State = "Complete" // finished successfully
StateFailed State = "Failed" // failed
StateSkipped State = "Skipped" // user did not want to upload
StatePending State = "Pending" // waiting for decision to upload (could be edited)
StateQueued State = "Queued" // ready for upload
StateWatermarking State = "Adding Watermark" // thumbnail generation
StateUploading State = "Uploading" // uploading
StateComplete State = "Complete" // finished successfully
StateFailed State = "Failed" // failed
StateSkipped State = "Skipped" // user did not want to upload
)
var currentId int32
@ -43,19 +42,17 @@ type HTTPClient interface {
type Uploader struct {
Uploads []*Upload `json:"uploads"`
Lock sync.Mutex
}
type Upload struct {
Id int32 `json:"id"`
UploadedAt time.Time `json:"uploaded_at"`
OriginalFilename string `json:"original_file"` // path on the local disk
MarkedUpFilename string `json:"markedup_file"` // a temporary file, if the user did some markup
Image *image.Store
webhookURL string
watermark bool // should watermark
usernameOverride string
Url string `json:"url"` // url on the discord CDN
@ -63,7 +60,8 @@ type Upload struct {
Width int `json:"width"`
Height int `json:"height"`
State State `json:"state"`
State State `json:"state"`
StateReason string `json:"state_reason"`
Client HTTPClient `json:"-"`
}
@ -76,34 +74,46 @@ func NewUploader() *Uploader {
}
func (u *Uploader) AddFile(file string, conf config.Watcher) {
u.Lock.Lock()
atomic.AddInt32(&currentId, 1)
thisUpload := Upload{
Id: currentId,
OriginalFilename: file,
watermark: !conf.NoWatermark,
UploadedAt: time.Time{},
Image: &image.Store{OriginalFilename: file, Watermark: !conf.NoWatermark, MaxBytes: 8_000_000},
webhookURL: conf.WebHookURL,
usernameOverride: conf.Username,
Url: "",
State: StateQueued,
Client: nil,
}
// if the user wants uploads to be held for editing etc,
// set it to Pending instead
if conf.HoldUploads {
thisUpload.State = StatePending
thisUpload.StateReason = ""
}
u.Uploads = append(u.Uploads, &thisUpload)
u.Lock.Unlock()
}
// Upload uploads any files that have not yet been uploaded
func (u *Uploader) Upload() {
u.Lock.Lock()
for _, upload := range u.Uploads {
if upload.State == StateQueued {
upload.processUpload()
}
}
u.Lock.Unlock()
}
func (u *Uploader) UploadById(id int32) *Upload {
u.Lock.Lock()
defer u.Lock.Unlock()
for _, anUpload := range u.Uploads {
if anUpload.Id == int32(id) {
return anUpload
@ -112,26 +122,18 @@ func (u *Uploader) UploadById(id int32) *Upload {
return nil
}
func (u *Upload) RemoveMarkupTempFile() {
if len(u.MarkedUpFilename) > 0 {
os.Remove(u.MarkedUpFilename)
}
}
func (u *Upload) processUpload() error {
daulog.SendLog(fmt.Sprintf("Uploading: %s", u.OriginalFilename), daulog.LogTypeInfo)
baseFilename := filepath.Base(u.OriginalFilename)
daulog.Infof("Uploading: %s", u.Image.OriginalFilename)
if u.webhookURL == "" {
daulog.SendLog("WebHookURL is not configured - cannot upload!", daulog.LogTypeError)
daulog.Error("WebHookURL is not configured - cannot upload!")
return errors.New("webhook url not configured")
}
extraParams := map[string]string{}
if u.usernameOverride != "" {
daulog.SendLog("Overriding username with "+u.usernameOverride, daulog.LogTypeInfo)
daulog.Infof("Overriding username with '%s'", u.usernameOverride)
extraParams["username"] = u.usernameOverride
}
@ -153,43 +155,16 @@ func (u *Upload) processUpload() error {
for retriesRemaining > 0 {
// open an io.ReadCloser for the file we intend to upload
var filedata *os.File
var err error
if len(u.MarkedUpFilename) > 0 {
filedata, err = os.Open(u.MarkedUpFilename)
if err != nil {
log.Print("Error opening marked up file:", err)
retriesRemaining--
sleepForRetries(retriesRemaining)
continue
}
} else {
filedata, err = os.Open(u.OriginalFilename)
if err != nil {
log.Print("Error opening original file:", err)
retriesRemaining--
sleepForRetries(retriesRemaining)
continue
}
}
var imageData io.Reader
if u.watermark {
daulog.SendLog("Watermarking image", daulog.LogTypeInfo)
imageData, err = u.applyWatermark(filedata)
if err != nil {
log.Print("Error watermarking:", err)
retriesRemaining--
sleepForRetries(retriesRemaining)
continue
}
} else {
imageData = filedata
}
request, err := newfileUploadRequest(u.webhookURL, extraParams, "file", baseFilename, imageData)
imageData, err := u.Image.ReadCloser()
if err != nil {
log.Printf("error creating upload request: %s", err)
panic(err)
}
request, err := newfileUploadRequest(u.webhookURL, extraParams, "file", u.Image.UploadFilename(), imageData)
if err != nil {
daulog.Errorf("error creating upload request: %s", err)
return fmt.Errorf("could not create upload request: %s", err)
}
start := time.Now()
@ -202,24 +177,24 @@ func (u *Upload) processUpload() error {
resp, err := u.Client.Do(request)
if err != nil {
log.Print("Error performing request:", err)
daulog.Errorf("Error performing request: %s", err)
retriesRemaining--
sleepForRetries(retriesRemaining)
continue
} else {
if resp.StatusCode == 413 {
// just fail immediately, we know this means the file was too big
daulog.SendLog("413 received - file too large", daulog.LogTypeError)
daulog.Error("413 received - file too large")
u.State = StateFailed
u.StateReason = "discord API said file too large"
return errors.New("received 413 - file too large")
}
if resp.StatusCode != 200 {
// {"message": "Request entity too large", "code": 40005}
log.Print("Bad response from server:", resp.StatusCode)
daulog.Errorf("Bad response code from server: %d", resp.StatusCode)
if b, err := ioutil.ReadAll(resp.Body); err == nil {
log.Print("Body:", string(b))
daulog.SendLog(fmt.Sprintf("Bad response: %s", string(b)), daulog.LogTypeError)
daulog.Errorf("Body:\n%s", string(b))
}
retriesRemaining--
sleepForRetries(retriesRemaining)
@ -228,7 +203,7 @@ func (u *Upload) processUpload() error {
resBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Print("could not deal with body: ", err)
daulog.Errorf("could not deal with body: %s", err)
retriesRemaining--
sleepForRetries(retriesRemaining)
continue
@ -240,17 +215,17 @@ func (u *Upload) processUpload() error {
// {"id": "851092588608880670", "type": 0, "content": "", "channel_id": "849615269706203171", "author": {"bot": true, "id": "849615314274484224", "username": "abcdedf", "avatar": null, "discriminator": "0000"}, "attachments": [{"id": "851092588332449812", "filename": "dau480457962.png", "size": 859505, "url": "https://cdn.discordapp.com/attachments/849615269706203171/851092588332449812/dau480457962.png", "proxy_url": "https://media.discordapp.net/attachments/849615269706203171/851092588332449812/dau480457962.png", "width": 640, "height": 640, "content_type": "image/png"}], "embeds": [], "mentions": [], "mention_roles": [], "pinned": false, "mention_everyone": false, "tts": false, "timestamp": "2021-06-06T13:38:05.660000+00:00", "edited_timestamp": null, "flags": 0, "components": [], "webhook_id": "849615314274484224"}
daulog.SendLog(fmt.Sprintf("Response: %s", string(resBody[:])), daulog.LogTypeDebug)
daulog.Debugf("Response: %s", string(resBody[:]))
if err != nil {
log.Print("could not parse JSON: ", err)
fmt.Println("Response was:", string(resBody[:]))
daulog.Errorf("could not parse JSON: %s", err)
daulog.Errorf("Response was: %s", string(resBody[:]))
retriesRemaining--
sleepForRetries(retriesRemaining)
continue
}
if len(res.Attachments) < 1 {
log.Print("bad response - no attachments?")
daulog.Error("bad response - no attachments?")
retriesRemaining--
sleepForRetries(retriesRemaining)
continue
@ -259,11 +234,12 @@ func (u *Upload) processUpload() error {
elapsed := time.Since(start)
rate := float64(a.Size) / elapsed.Seconds() / 1024.0
daulog.SendLog(fmt.Sprintf("Uploaded to %s %dx%d", a.URL, a.Width, a.Height), daulog.LogTypeInfo)
daulog.SendLog(fmt.Sprintf("id: %d, %d bytes transferred in %.2f seconds (%.2f KiB/s)", res.ID, a.Size, elapsed.Seconds(), rate), daulog.LogTypeInfo)
daulog.Infof("Uploaded to %s %dx%d", a.URL, a.Width, a.Height)
daulog.Infof("id: %d, %d bytes transferred in %.2f seconds (%.2f KiB/s)", res.ID, a.Size, elapsed.Seconds(), rate)
u.Url = a.URL
u.State = StateComplete
u.StateReason = ""
u.Width = a.Width
u.Height = a.Height
u.UploadedAt = time.Now()
@ -272,12 +248,13 @@ func (u *Upload) processUpload() error {
}
}
// remove any marked up file
u.RemoveMarkupTempFile()
// remove any temporary files
u.Image.Cleanup()
if retriesRemaining == 0 {
daulog.SendLog("Failed to upload, even after all retries", daulog.LogTypeError)
daulog.Error("Failed to upload, even after all retries")
u.State = StateFailed
u.StateReason = "could not upload after all retries"
return errors.New("could not upload after all retries")
}
@ -310,45 +287,11 @@ func newfileUploadRequest(uri string, params map[string]string, paramName string
return req, err
}
// applyWatermark applies the watermark to the image
func (u *Upload) applyWatermark(in *os.File) (io.Reader, error) {
defer in.Close()
im, _, err := image.Decode(in)
if err != nil {
daulog.SendLog(fmt.Sprintf("Cannot decode image: %v - skipping watermarking", err), daulog.LogTypeError)
return nil, errors.New("cannot decode image")
}
bounds := im.Bounds()
// var S float64 = float64(bounds.Max.X)
dc := gg.NewContext(bounds.Max.X, bounds.Max.Y)
dc.Clear()
dc.SetRGB(0, 0, 0)
dc.SetFontFace(inconsolata.Regular8x16)
dc.DrawImage(im, 0, 0)
dc.DrawRoundedRectangle(0, float64(bounds.Max.Y-18.0), 320, float64(bounds.Max.Y), 0)
dc.SetRGB(0, 0, 0)
dc.Fill()
dc.SetRGB(1, 1, 1)
dc.DrawString("github.com/tardisx/discord-auto-upload", 5.0, float64(bounds.Max.Y)-5.0)
b := bytes.Buffer{}
dc.EncodePNG(&b)
return &b, nil
}
func sleepForRetries(retry int) {
if retry == 0 {
return
}
retryTime := (6-retry)*(6-retry) + 6
daulog.SendLog(fmt.Sprintf("Will retry in %d seconds (%d remaining attempts)", retryTime, retry), daulog.LogTypeError)
daulog.Errorf("Will retry in %d seconds (%d remaining attempts)", retryTime, retry)
time.Sleep(time.Duration(retryTime) * time.Second)
}

View File

@ -2,15 +2,13 @@ package upload
import (
"bytes"
"image"
i "image"
"image/color"
"image/png"
"io/ioutil"
"math/rand"
"net/http"
"os"
"testing"
// "github.com/tardisx/discord-auto-upload/config"
)
// https://www.thegreatcodeadventure.com/mocking-http-requests-in-golang/
@ -38,52 +36,50 @@ func DoTooBigUpload(req *http.Request) (*http.Response, error) {
}, nil
}
func TestSuccessfulUpload(t *testing.T) {
// create temporary file, processUpload requires that it exists, even though
// we will not really be uploading it here
f, _ := os.CreateTemp("", "dautest-upload-*")
defer os.Remove(f.Name())
u := Upload{webhookURL: "https://127.0.0.1/", OriginalFilename: f.Name()}
u.Client = &MockClient{DoFunc: DoGoodUpload}
err := u.processUpload()
if err != nil {
t.Errorf("error occured: %s", err)
}
if u.Width != 640 || u.Height != 640 {
t.Error("dimensions wrong")
}
if u.Url != "https://cdn.discordapp.com/attachments/849615269706203171/851092588332449812/dau480457962.png" {
t.Error("URL wrong")
}
}
// func TestSuccessfulUpload(t *testing.T) {
// // create temporary file, processUpload requires that it exists, even though
// // we will not really be uploading it here
// f, _ := os.CreateTemp("", "dautest-upload-*")
// defer os.Remove(f.Name())
// u := Upload{webhookURL: "https://127.0.0.1/", Image: &image.Store{OriginalFilename: f.Name()}}
// u.Client = &MockClient{DoFunc: DoGoodUpload}
// err := u.processUpload()
// if err != nil {
// t.Errorf("error occured: %s", err)
// }
func TestTooBigUpload(t *testing.T) {
// create temporary file, processUpload requires that it exists, even though
// we will not really be uploading it here
f, _ := os.CreateTemp("", "dautest-upload-*")
defer os.Remove(f.Name())
u := Upload{webhookURL: "https://127.0.0.1/", OriginalFilename: f.Name()}
u.Client = &MockClient{DoFunc: DoTooBigUpload}
err := u.processUpload()
if err == nil {
t.Error("error did not occur?")
} else if err.Error() != "received 413 - file too large" {
t.Errorf("wrong error occurred: %s", err.Error())
}
if u.State != StateFailed {
t.Error("upload should have been marked failed")
}
}
// if u.Url != "https://cdn.discordapp.com/attachments/849615269706203171/851092588332449812/dau480457962.png" {
// t.Error("URL wrong")
// }
// }
// func TestTooBigUpload(t *testing.T) {
// // create temporary file, processUpload requires that it exists, even though
// // we will not really be uploading it here
// f, _ := os.CreateTemp("", "dautest-upload-*")
// defer os.Remove(f.Name())
// u := Upload{webhookURL: "https://127.0.0.1/", Image: &image.Store{OriginalFilename: f.Name()}}
// u.Client = &MockClient{DoFunc: DoTooBigUpload}
// err := u.processUpload()
// if err == nil {
// t.Error("error did not occur?")
// } else if err.Error() != "received 413 - file too large" {
// t.Errorf("wrong error occurred: %s", err.Error())
// }
// if u.State != StateFailed {
// t.Error("upload should have been marked failed")
// }
// }
func tempImageGt8Mb() {
// about 12Mb
width := 2000
height := 2000
upLeft := image.Point{0, 0}
lowRight := image.Point{width, height}
upLeft := i.Point{0, 0}
lowRight := i.Point{width, height}
img := image.NewRGBA(image.Rectangle{upLeft, lowRight})
img := i.NewRGBA(i.Rectangle{upLeft, lowRight})
// Colors are defined by Red, Green, Blue, Alpha uint8 values.

View File

@ -1,24 +1,47 @@
package version
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"time"
daulog "github.com/tardisx/discord-auto-upload/log"
"golang.org/x/mod/semver"
)
const CurrentVersion string = "v0.12.0"
const CurrentVersion string = "v0.13.0"
func NewVersionAvailable(v string) bool {
type GithubRelease struct {
HTMLURL string `json:"html_url"`
TagName string `json:"tag_name"`
Name string `json:"name"`
Body string `json:"body"`
}
var LatestVersion string
var LatestVersionInfo GithubRelease
// UpdateAvailable returns true or false, depending on whether not a new version is available.
// It always returns false if the OnlineVersion has not yet been fetched.
func UpdateAvailable() bool {
if !semver.IsValid(CurrentVersion) {
panic(fmt.Sprintf("my current version '%s' is not valid", CurrentVersion))
}
if !semver.IsValid(v) {
// maybe this should just be a warning
log.Printf("passed in version '%s' is not valid - assuming no new version", v)
if LatestVersion == "" {
return false
}
comp := semver.Compare(v, CurrentVersion)
if !semver.IsValid(LatestVersion) {
// maybe this should just be a warning
daulog.Errorf("online version '%s' is not valid - assuming no new version", LatestVersion)
return false
}
comp := semver.Compare(LatestVersion, CurrentVersion)
if comp == 0 {
return false
}
@ -27,3 +50,32 @@ func NewVersionAvailable(v string) bool {
}
return false // they are using a newer one than exists?
}
func GetOnlineVersion() {
daulog.Info("checking for new version")
client := &http.Client{Timeout: time.Second * 5}
resp, err := client.Get("https://api.github.com/repos/tardisx/discord-auto-upload/releases/latest")
if err != nil {
daulog.Errorf("WARNING: Update check failed: %s", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal("could not check read update response")
}
var latest GithubRelease
err = json.Unmarshal(body, &latest)
if err != nil {
log.Fatal("could not parse JSON: ", err)
}
LatestVersion = latest.TagName
LatestVersionInfo = latest
daulog.Debugf("Latest version: %s", LatestVersion)
}

View File

@ -4,8 +4,18 @@ import (
"testing"
)
func TestVersioning(t *testing.T) {
if !NewVersionAvailable("v1.0.0") {
t.Error("should be a version newer than v1.0.0")
func TestVersioningUpdate(t *testing.T) {
// pretend there is a new version
LatestVersion = "v0.13.9"
if !UpdateAvailable() {
t.Error("should be a version newer than " + CurrentVersion)
}
}
func TestVersioningNoUpdate(t *testing.T) {
// pretend there is not a new version
LatestVersion = "v0.12.1"
if UpdateAvailable() {
t.Error("should NOT be a version newer than " + CurrentVersion)
}
}

View File

@ -21,7 +21,7 @@
</p>
<p>The Watch Interval is how often new files will be discovered by your
watchers (configured below).</p>
watchers in seconds (watchers are configured below).</p>
<div class="form-row align-items-center">
<div class="col-sm-6 my-1">
@ -33,6 +33,17 @@
</div>
</div>
<div class="form-row align-items-center">
<div class="col-sm-6 my-1">
<span>Open browser on startup</span>
</div>
<div class="col-sm-6 my-1">
<label class="sr-only">Open browser</label>
<button type="button" @click="config.OpenBrowserOnStart = ! config.OpenBrowserOnStart" class="btn btn-success" x-text="config.OpenBrowserOnStart ? 'Enabled' : 'Disabled'"></button>
</div>
</div>
<div class="form-row align-items-center">
<div class="col-sm-6 my-1">
<span>Watch interval</span>

View File

@ -1,24 +1,21 @@
{{ define "content" }}
<main role="main" class="inner DAU">
<main role="main" class="inner DAU" x-data="logs()" x-init="get_logs()">
<h1 class="DAU-heading">Logs</h1>
<p class="lead">Discord-auto-upload logs</p>
<div class="container">
<div class="row">
<div class="col-sm">
<button type="button" onClick="debug=1; get_logs();" class="btn btn-primary">all logs</button>
<button type="button" @click="debug = !debug" class="btn btn-primary" x-text="debug ? 'debug' : 'no debug'"></button>
</div>
<div class="col-sm">
<button type="button" onClick="debug=0; get_logs();" class="btn btn-primary">no debug</button>
</div>
<div class="col-sm">
<button type="button" id="scroll-button" onClick="toggle_scroll();" class="btn btn-primary">disable auto-scroll</button>
<button type="button" @click="scroll = !scroll" class="btn btn-primary" x-text="scroll ? 'auto-scroll' : 'no scroll'"></button>
</div>
</div>
</div>
<pre id="logs" class="text-left pre-scrollable">
<pre id="logs" x-text="text" class="text-left pre-scrollable">
</pre>
</main>
@ -27,35 +24,24 @@
{{ define "js" }}
<script>
var debug = 0;
var scrl = true;
$(document).ready(function() {
get_logs();
setInterval(function() { get_logs(); }, 1000);
});
function toggle_scroll() {
scrl = !scrl;
if (scrl) {
$('#scroll-button').text('disable auto-scroll');
function logs() {
return {
text: '', scroll: true, debug: false,
get_logs() {
fetch('/rest/logs?' + new URLSearchParams({ debug: this.debug ? "1" : "0" }))
.then(response => response.text())
.then(text => {
console.log(text);
this.text = text;
if (this.scroll) {
document.getElementById('logs').scrollTop =10000;
}
let self = this;
setTimeout(function() { self.get_logs(); }, 1000)
})
},
}
}
else {
$('#scroll-button').text('auto-scroll');
}
}
function get_logs() {
$.ajax({ method: 'get', url: '/rest/logs', data: { debug : debug }})
.done(function(data) {
$('#logs').text(data);
console.log('scrl is ', scrl);
if (scrl) {
$('#logs').scrollTop(10000);
}
});
}
</script>
{{ end }}

View File

@ -38,7 +38,7 @@
<thead>
<tr>
<th>filename</th>
<th>actions</th>
<th>state</th>
<th>&nbsp;</th>
</tr>
</thead>
@ -47,9 +47,11 @@
<template x-for="ul in uploads">
<tr>
<td x-text="ul.original_file"></td>
<td>
<button @click="start_upload(ul.id)" type="button" class="btn btn-primary">upload</button>
</td>
<td>
<span x-text="ul.state"></span>
<div x-if="ul.state_reason">(<span x-text="ul.state_reason"></span>)</div>
</td>
<td>
<img :src="'/rest/image/'+ul.id+'/thumb'">
</td>
@ -74,7 +76,10 @@
<template x-for="ul in finished">
<tr>
<td x-text="ul.original_file"></td>
<td x-text="ul.state"></td>
<td>
<span x-text="ul.state"></span>
<div x-if="ul.state_reason">(<span x-text="ul.state_reason"></span>)</div>
</td>
<td>
<img :src="'/rest/image/'+ul.id+'/thumb'">
</td>

View File

@ -42,6 +42,10 @@
<a class="nav-link {{ if eq .Path "config.html"}} active {{ end }}" href="/config.html">Config</a>
<a class="nav-link {{ if eq .Path "uploads.html"}} active {{ end }}" href="/uploads.html">Uploads</a>
<a class="nav-link {{ if eq .Path "logs.html"}} active {{ end }}" href="/logs.html">Logs</a>
{{ if eq .NewVersionAvailable true }}
<a class="nav-link" href="{{ .NewVersionInfo.HTMLURL }}">Ver {{ .NewVersionInfo.TagName }} available!</a>
{{ end }}
</nav>
</div>
</header>

View File

@ -19,7 +19,7 @@ import (
"github.com/gorilla/mux"
"github.com/tardisx/discord-auto-upload/config"
"github.com/tardisx/discord-auto-upload/imageprocess"
"github.com/tardisx/discord-auto-upload/image"
daulog "github.com/tardisx/discord-auto-upload/log"
"github.com/tardisx/discord-auto-upload/upload"
"github.com/tardisx/discord-auto-upload/version"
@ -66,7 +66,7 @@ func (ws *WebService) getStatic(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFS(webFS, "data/wrapper.tmpl", "data/"+path)
if err != nil {
daulog.SendLog(fmt.Sprintf("when fetching: %s got: %s", path, err), daulog.LogTypeError)
daulog.Errorf("when fetching: %s got: %s", path, err)
w.Header().Add("Content-Type", "text/plain")
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("not found"))
@ -74,12 +74,16 @@ func (ws *WebService) getStatic(w http.ResponseWriter, r *http.Request) {
}
var b struct {
Body string
Path string
Version string
Body string
Path string
Version string
NewVersionAvailable bool
NewVersionInfo version.GithubRelease
}
b.Path = path
b.Version = version.CurrentVersion
b.NewVersionAvailable = version.UpdateAvailable()
b.NewVersionInfo = version.LatestVersionInfo
err = t.ExecuteTemplate(w, "layout", b)
if err != nil {
@ -90,7 +94,7 @@ func (ws *WebService) getStatic(w http.ResponseWriter, r *http.Request) {
otherStatic, err := webFS.ReadFile("data/" + path)
if err != nil {
daulog.SendLog(fmt.Sprintf("when fetching: %s got: %s", path, err), daulog.LogTypeError)
daulog.Errorf("when fetching: %s got: %s", path, err)
w.Header().Add("Content-Type", "text/plain")
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("not found"))
@ -114,7 +118,7 @@ func (ws *WebService) getLogs(w http.ResponseWriter, r *http.Request) {
}
text := ""
for _, log := range daulog.LogEntries {
for _, log := range daulog.Memory.Entries() {
if !showDebug && log.Type == daulog.LogTypeDebug {
continue
}
@ -123,14 +127,13 @@ func (ws *WebService) getLogs(w http.ResponseWriter, r *http.Request) {
)
}
// js, _ := json.Marshal(daulog.LogEntries)
w.Write([]byte(text))
}
func (ws *WebService) handleConfig(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
newConfig := config.ConfigV2{}
newConfig := config.ConfigV3{}
defer r.Body.Close()
b, err := ioutil.ReadAll(r.Body)
@ -175,7 +178,6 @@ func (ws *WebService) getUploads(w http.ResponseWriter, r *http.Request) {
func (ws *WebService) imageThumb(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/png")
processor := imageprocess.Processor{}
vars := mux.Vars(r)
id, err := strconv.ParseInt(vars["id"], 10, 32)
@ -189,7 +191,7 @@ func (ws *WebService) imageThumb(w http.ResponseWriter, r *http.Request) {
returnJSONError(w, "bad id")
return
}
err = processor.ThumbPNG(ul, "orig", w)
err = ul.Image.ThumbPNG(image.ThumbTypeOriginal, w)
if err != nil {
returnJSONError(w, "could not create thumb")
return
@ -199,7 +201,6 @@ func (ws *WebService) imageThumb(w http.ResponseWriter, r *http.Request) {
func (ws *WebService) imageMarkedupThumb(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/png")
processor := imageprocess.Processor{}
vars := mux.Vars(r)
id, err := strconv.ParseInt(vars["id"], 10, 32)
@ -213,7 +214,7 @@ func (ws *WebService) imageMarkedupThumb(w http.ResponseWriter, r *http.Request)
returnJSONError(w, "bad id")
return
}
err = processor.ThumbPNG(ul, "markedup", w)
err = ul.Image.ThumbPNG(image.ThumbTypeMarkedUp, w)
if err != nil {
returnJSONError(w, "could not create thumb")
return
@ -235,7 +236,7 @@ func (ws *WebService) image(w http.ResponseWriter, r *http.Request) {
return
}
img, err := os.Open(ul.OriginalFilename)
img, err := os.Open(ul.Image.OriginalFilename)
if err != nil {
returnJSONError(w, "could not open image file")
return
@ -273,7 +274,7 @@ func (ws *WebService) modifyUpload(w http.ResponseWriter, r *http.Request) {
return
} else if change == "skip" {
anUpload.State = upload.StateSkipped
anUpload.RemoveMarkupTempFile()
anUpload.Image.Cleanup()
res := StartUploadResponse{Success: true, Message: "upload skipped"}
resString, _ := json.Marshal(res)
w.Write(resString)
@ -295,7 +296,7 @@ func (ws *WebService) modifyUpload(w http.ResponseWriter, r *http.Request) {
}
// write to a temporary file
tempfile, err := ioutil.TempFile("", "dau_markup")
tempfile, err := ioutil.TempFile("", "dau_markup-*")
if err != nil {
log.Fatal(err)
}
@ -308,7 +309,7 @@ func (ws *WebService) modifyUpload(w http.ResponseWriter, r *http.Request) {
}
tempfile.Close()
anUpload.MarkedUpFilename = tempfile.Name()
anUpload.Image.ModifiedFilename = tempfile.Name()
} else {
returnJSONError(w, "bad change type")
@ -343,12 +344,11 @@ func (ws *WebService) StartWebServer() {
go func() {
listen := fmt.Sprintf(":%d", ws.Config.Config.Port)
log.Printf("Starting web server on http://localhost%s", listen)
daulog.Infof("Starting web server on http://localhost%s", listen)
srv := &http.Server{
Handler: r,
Addr: listen,
// Good practice: enforce timeouts for servers you create!
Handler: r,
Addr: listen,
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}

View File

@ -78,7 +78,7 @@ func TestGetConfig(t *testing.T) {
t.Errorf("expected error to be nil got %v", err)
}
exp := `{"WatchInterval":10,"Version":2,"Port":9090,"Watchers":[{"WebHookURL":"https://webhook.url.here","Path":"/your/screenshot/dir/here","Username":"","NoWatermark":false,"HoldUploads":false,"Exclude":[]}]}`
exp := `{"WatchInterval":10,"Version":3,"Port":9090,"OpenBrowserOnStart":true,"Watchers":[{"WebHookURL":"https://webhook.url.here","Path":"/your/screenshot/dir/here","Username":"","NoWatermark":false,"HoldUploads":false,"Exclude":[]}]}`
if string(b) != exp {
t.Errorf("Got unexpected response\n%v\n%v", string(b), exp)
}