comparison pkg/misc/tmpfiles.go @ 1221:c193649d4f11

Add an area for temp uploads on the server to be addressed by tokens. If unused they will me thrown away after 45 minutes. There could be max 100 of them. If there are more to upload the oldest are removed first.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Mon, 19 Nov 2018 16:15:30 +0100
parents
children bc4b642c8d04
comparison
equal deleted inserted replaced
1220:d11d5e39c8d0 1221:c193649d4f11
1 // This is Free Software under GNU Affero General Public License v >= 3.0
2 // without warranty, see README.md and license for details.
3 //
4 // SPDX-License-Identifier: AGPL-3.0-or-later
5 // License-Filename: LICENSES/AGPL-3.0.txt
6 //
7 // Copyright (C) 2018 by via donau
8 // – Österreichische Wasserstraßen-Gesellschaft mbH
9 // Software engineering by Intevation GmbH
10 //
11 // Author(s):
12 // * Sascha L. Teichmann <sascha.teichmann@intevation.de>
13
14 package misc
15
16 import (
17 "encoding/hex"
18 "errors"
19 "fmt"
20 "log"
21 "os"
22 "path/filepath"
23 "sort"
24 "strings"
25 "sync"
26 "time"
27
28 "gemma.intevation.de/gemma/pkg/common"
29 "gemma.intevation.de/gemma/pkg/config"
30 )
31
32 const (
33 tmpFilesDir = "tmpfiles"
34 maxTmpFiles = 100
35 maxTmpFileAge = 45 * time.Minute
36 tmpfilesCleanUp = 15 * time.Minute
37 )
38
39 var tmpfilesMu sync.Mutex
40
41 func init() {
42 go func() {
43 config.WaitReady()
44 for {
45 if err := cleanupTmpFiles(); err != nil {
46 log.Printf("error: %v\n", err)
47 }
48 time.Sleep(tmpfilesCleanUp)
49 }
50 }()
51 }
52
53 func tmpfilesDir() string {
54 dir := config.TmpDir()
55 if dir == "" {
56 dir = os.TempDir()
57 }
58 return filepath.Join(dir, tmpFilesDir)
59 }
60
61 func toError(errs []error) error {
62 var b strings.Builder
63 for i, err := range errs {
64 if i > 0 {
65 fmt.Fprintf(&b, ", ")
66 }
67 fmt.Fprintf(&b, "%v", err)
68 }
69 return errors.New(b.String())
70 }
71
72 func cleanupTmpFiles() error {
73 tmpfilesMu.Lock()
74 defer tmpfilesMu.Unlock()
75
76 tmp := tmpfilesDir()
77
78 _, err := os.Stat(tmp)
79 switch {
80 case os.IsNotExist(err):
81 return nil
82 case err != nil:
83 return err
84 }
85
86 f, err := os.Open(tmp)
87 if err != nil {
88 return err
89 }
90
91 files, err := f.Readdir(-1)
92 f.Close()
93 if err != nil {
94 return err
95 }
96
97 bestBefore := time.Now().Add(-maxTmpFileAge)
98
99 var errs []error
100 var deleted int
101
102 for _, fi := range files {
103 if fi.ModTime().Before(bestBefore) {
104 fname := filepath.Join(tmp, fi.Name())
105 if err := os.RemoveAll(fname); err != nil {
106 errs = append(errs, err)
107 } else {
108 deleted++
109 }
110 }
111 }
112
113 // If empty remove temp folder.
114 if deleted == len(files) {
115 if err := os.RemoveAll(tmp); err != nil {
116 errs = append(errs, err)
117 }
118 }
119
120 if len(errs) > 0 {
121 return toError(errs)
122 }
123
124 return nil
125 }
126
127 func UnmakeTempFile(token, path string) error {
128 tmpfilesMu.Lock()
129 defer tmpfilesMu.Unlock()
130 tmp := tmpfilesDir()
131 return os.Rename(filepath.Join(tmp, token), path)
132 }
133
134 func MakeTempFile(fname string) (string, error) {
135 tmpfilesMu.Lock()
136 defer tmpfilesMu.Unlock()
137
138 tmp := tmpfilesDir()
139
140 _, err := os.Stat(tmp)
141 switch {
142 case os.IsNotExist(err):
143 if err := os.MkdirAll(tmp, 0700); err != nil {
144 return "", err
145 }
146 case err != nil:
147 return "", err
148 }
149
150 f, err := os.Open(tmp)
151 if err != nil {
152 return "", err
153 }
154
155 files, err := f.Readdir(-1)
156 f.Close()
157 if err != nil {
158 return "", err
159 }
160
161 // If there are too many throw away old.
162 if len(files) >= maxTmpFiles {
163 sort.Slice(files, func(i, j int) bool {
164 return files[i].ModTime().Before(files[j].ModTime())
165 })
166
167 var errs []error
168 for len(files) >= maxTmpFiles {
169 fname := filepath.Join(tmp, files[0].Name())
170 if err := os.RemoveAll(fname); err != nil {
171 errs = append(errs, err)
172 }
173 files = files[1:]
174 }
175 if len(errs) > 0 {
176 return "", toError(errs)
177 }
178 }
179
180 var token string
181 again:
182 token = generateToken()
183 for _, f := range files {
184 if f.Name() == token {
185 goto again
186 }
187 }
188 err = os.Rename(fname, filepath.Join(tmp, token))
189 return token, err
190 }
191
192 func generateToken() string {
193 return hex.EncodeToString(common.GenerateRandomKey(20))
194 }