@@ -23,21 +23,27 @@ type Server struct {
2323 DB * db
2424 Conf Config
2525 Logger * log.Logger
26+
27+ fileCleanerStopChan chan bool
2628}
2729
2830func New (conf Config ) (* Server , error ) {
2931 s := new (Server )
3032 var err error
3133
3234 s .Conf = conf
35+ s .fileCleanerStopChan = make (chan bool )
3336 s .Logger = log .New (os .Stderr , "filedrop " , log .LstdFlags )
3437 s .DB , err = openDB (conf .DB .Driver , conf .DB .DSN )
38+
39+ go s .fileCleaner ()
40+
3541 return s , err
3642}
3743
3844// AddFile adds file to storage and returns assigned UUID which can be directly
3945// substituted into URL.
40- func (s * Server ) AddFile (contents io.Reader , maxUses uint , storeUntil time.Time ) (string , error ) {
46+ func (s * Server ) AddFile (contents io.Reader , contentType string , maxUses uint , storeUntil time.Time ) (string , error ) {
4147 fileUUID := uuid .NewV4 ()
4248 outLocation := filepath .Join (s .Conf .StorageDir , fileUUID .String ())
4349
@@ -53,7 +59,7 @@ func (s *Server) AddFile(contents io.Reader, maxUses uint, storeUntil time.Time)
5359 if _ , err := io .Copy (file , contents ); err != nil {
5460 return "" , errors .Wrap (err , "file write" )
5561 }
56- if err := s .DB .AddFile (nil , fileUUID .String (), maxUses , storeUntil ); err != nil {
62+ if err := s .DB .AddFile (nil , fileUUID .String (), contentType , maxUses , storeUntil ); err != nil {
5763 os .Remove (outLocation )
5864 return "" , errors .Wrap (err , "db add" )
5965 }
@@ -110,48 +116,54 @@ func (s *Server) OpenFile(fileUUID string) (io.Reader, error) {
110116// Note that access using this function is equivalent to access
111117// through HTTP API, so it will count against usage count, for example.
112118// To avoid this use OpenFile(fileUUID).
113- func (s * Server ) GetFile (fileUUID string ) (io.Reader , error ) {
119+ func (s * Server ) GetFile (fileUUID string ) (r io.Reader , contentType string , err error ) {
114120 // Just to check validity.
115- _ , err : = uuid .FromString (fileUUID )
121+ _ , err = uuid .FromString (fileUUID )
116122 if err != nil {
117- return nil , errors .Wrap (err , "uuid parse" )
123+ return nil , "" , errors .Wrap (err , "uuid parse" )
118124 }
119125
120126 tx , err := s .DB .Begin ()
121127 if err != nil {
122- return nil , errors .Wrap (err , "tx begin" )
128+ return nil , "" , errors .Wrap (err , "tx begin" )
123129 }
124130 defer tx .Rollback () // rollback is no-op after commit
125131
126132 if s .DB .ShouldDelete (tx , fileUUID ) {
127133 if err := s .removeFile (tx , fileUUID ); err != nil {
128- log .Println ("Error while trying to remove file" , fileUUID + ":" , err )
134+ s . Logger .Println ("Error while trying to remove file" , fileUUID + ":" , err )
129135
130136 }
131137 if err := tx .Commit (); err != nil {
132- log .Println ("Tx commit error:" , err )
133- return nil , err
138+ s . Logger .Println ("Tx commit error:" , err )
139+ return nil , "" , err
134140 }
135- return nil , ErrFileDoesntExists
141+ return nil , "" , ErrFileDoesntExists
136142 }
137143 if err := s .DB .AddUse (tx , fileUUID ); err != nil {
138- return nil , errors .Wrap (err , "add use" )
144+ return nil , "" , errors .Wrap (err , "add use" )
139145 }
140146
141147 fileLocation := filepath .Join (s .Conf .StorageDir , fileUUID )
142148 file , err := os .Open (fileLocation )
143149 if err != nil {
144150 if os .IsNotExist (err ) {
145151 s .removeFile (tx , fileUUID )
146- return nil , ErrFileDoesntExists
152+ return nil , "" , ErrFileDoesntExists
147153 }
148- return nil , err
154+ return nil , "" , err
149155 }
150156 if err := tx .Commit (); err != nil {
151- log .Println ("Tx commit error:" , err )
152- return nil , errors .Wrap (err , "tx commit" )
157+ s . Logger .Println ("Tx commit error:" , err )
158+ return nil , "" , errors .Wrap (err , "tx commit" )
153159 }
154- return file , nil
160+
161+ ttype , err := s .DB .ContentType (nil , fileUUID )
162+ if err != nil {
163+ return nil , "" , errors .Wrap (err , "content type query" )
164+ }
165+
166+ return file , ttype , nil
155167}
156168
157169func (s * Server ) acceptFile (w http.ResponseWriter , r * http.Request ) {
@@ -205,7 +217,7 @@ func (s *Server) acceptFile(w http.ResponseWriter, r *http.Request) {
205217 }
206218 }
207219
208- fileUUID , err := s .AddFile (r .Body , maxUses , storeUntil )
220+ fileUUID , err := s .AddFile (r .Body , r . Header . Get ( "Content-Type" ), maxUses , storeUntil )
209221 if err != nil {
210222 w .WriteHeader (http .StatusInternalServerError )
211223 w .Write ([]byte (err .Error ()))
@@ -244,7 +256,7 @@ func (s *Server) serveFile(w http.ResponseWriter, r *http.Request) {
244256 return
245257 }
246258 fileUUID := splittenPath [len (splittenPath )- 2 ]
247- reader , err := s .GetFile (fileUUID )
259+ reader , ttype , err := s .GetFile (fileUUID )
248260 if err != nil {
249261 if err == ErrFileDoesntExists {
250262 w .WriteHeader (http .StatusNotFound )
@@ -256,6 +268,9 @@ func (s *Server) serveFile(w http.ResponseWriter, r *http.Request) {
256268 }
257269 return
258270 }
271+ if ttype != "" {
272+ w .Header ().Set ("Content-Type" , ttype )
273+ }
259274 w .WriteHeader (http .StatusOK )
260275 _ , err = io .Copy (w , reader )
261276 if err != nil {
@@ -276,5 +291,47 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
276291}
277292
278293func (s * Server ) Close () error {
294+ // don't close DB if "cleaner" is doing something, wait for it to finish
295+ s .fileCleanerStopChan <- true
296+ <- s .fileCleanerStopChan
297+
279298 return s .DB .Close ()
280299}
300+
301+ func (s * Server ) fileCleaner () {
302+ tick := time .NewTicker (time .Minute )
303+ for {
304+ select {
305+ case <- s .fileCleanerStopChan :
306+ s .fileCleanerStopChan <- true
307+ return
308+ case <- tick .C :
309+ s .cleanupFiles ()
310+ }
311+ }
312+ }
313+
314+ func (s * Server ) cleanupFiles () {
315+ tx , err := s .DB .Begin ()
316+ if err != nil {
317+ s .Logger .Println ("Failed to begin transaction for clean-up:" , err )
318+ return
319+ }
320+ defer tx .Rollback () // rollback is no-op after commit
321+
322+ uuids , err := s .DB .UnreachableFiles (tx )
323+ if err != nil {
324+ s .Logger .Println ("Failed to get list of files pending removal:" , err )
325+ return
326+ }
327+
328+ for _ , fileUUID := range uuids {
329+ if err := os .Remove (filepath .Join (s .Conf .StorageDir , fileUUID )); err != nil {
330+ s .Logger .Println ("Failed to remove file during clean-up:" , err )
331+ }
332+ }
333+
334+ if err := tx .Commit (); err != nil {
335+ s .Logger .Println ("Failed to begin transaction for clean-up:" , err )
336+ }
337+ }
0 commit comments