Mercurial > gemma
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 } |