view pkg/misc/tmpfiles.go @ 5490:5f47eeea988d logging

Use own logging package.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Mon, 20 Sep 2021 17:45:39 +0200
parents 5b9b8eabcd01
children
line wrap: on
line source

// This is Free Software under GNU Affero General Public License v >= 3.0
// without warranty, see README.md and license for details.
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// License-Filename: LICENSES/AGPL-3.0.txt
//
// Copyright (C) 2018 by via donau
//   – Österreichische Wasserstraßen-Gesellschaft mbH
// Software engineering by Intevation GmbH
//
// Author(s):
//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>

package misc

import (
	"encoding/hex"
	"os"
	"path/filepath"
	"sort"
	"sync"
	"time"

	"gemma.intevation.de/gemma/pkg/common"
	"gemma.intevation.de/gemma/pkg/config"
	"gemma.intevation.de/gemma/pkg/log"
)

const (
	tmpFilesDir     = "tmpfiles"
	maxTmpFiles     = 100
	maxTmpFileAge   = 45 * time.Minute
	tmpfilesCleanUp = 15 * time.Minute
)

var tmpfilesMu sync.Mutex

func init() {
	go func() {
		config.WaitReady()
		for {
			if err := cleanupTmpFiles(); err != nil {
				log.Errorf("%v\n", err)
			}
			time.Sleep(tmpfilesCleanUp)
		}
	}()
}

func tmpfilesDir() string {
	dir := config.TmpDir()
	if dir == "" {
		dir = os.TempDir()
	}
	return filepath.Join(dir, tmpFilesDir)
}

func cleanupTmpFiles() error {
	tmpfilesMu.Lock()
	defer tmpfilesMu.Unlock()

	tmp := tmpfilesDir()

	_, err := os.Stat(tmp)
	switch {
	case os.IsNotExist(err):
		return nil
	case err != nil:
		return err
	}

	f, err := os.Open(tmp)
	if err != nil {
		return err
	}

	files, err := f.Readdir(-1)
	f.Close()
	if err != nil {
		return err
	}

	bestBefore := time.Now().Add(-maxTmpFileAge)

	var errs []error
	var deleted int

	for _, fi := range files {
		if fi.ModTime().Before(bestBefore) {
			fname := filepath.Join(tmp, fi.Name())
			if err := os.RemoveAll(fname); err != nil {
				errs = append(errs, err)
			} else {
				deleted++
			}
		}
	}

	// If empty remove temp folder.
	if deleted == len(files) {
		if err := os.RemoveAll(tmp); err != nil {
			errs = append(errs, err)
		}
	}

	if len(errs) > 0 {
		return common.ToError(errs)
	}

	return nil
}

// DeleteTempFile deletes a file identified by its token
// from the special folder of temporary files.
func DeleteTempFile(token string) error {
	tmpfilesMu.Lock()
	defer tmpfilesMu.Unlock()
	tmp := tmpfilesDir()
	return os.RemoveAll(filepath.Join(tmp, token))
}

// UnmakeTempFile moves a file identified by a token
// back from the special folder to a normal path.
// This only will succeed if the token is not expired.
func UnmakeTempFile(token, path string) error {
	tmpfilesMu.Lock()
	defer tmpfilesMu.Unlock()
	tmp := tmpfilesDir()
	return os.Rename(filepath.Join(tmp, token), path)
}

// MakeTempFile moves a file into a special temporary file folder
// and returns a token for later accessing the file again.
// Files in this special folder are only quaranteed to be
// kept alive among of time. After this time expires file
// will be likely to be deleted. There are only a limited
// amount of temporary files allowed. If you push more
// files into the special folder the oldest will be erased.
func MakeTempFile(fname string) (string, error) {
	tmpfilesMu.Lock()
	defer tmpfilesMu.Unlock()

	tmp := tmpfilesDir()

	_, err := os.Stat(tmp)
	switch {
	case os.IsNotExist(err):
		if err := os.MkdirAll(tmp, 0700); err != nil {
			return "", err
		}
	case err != nil:
		return "", err
	}

	f, err := os.Open(tmp)
	if err != nil {
		return "", err
	}

	files, err := f.Readdir(-1)
	f.Close()
	if err != nil {
		return "", err
	}

	// If there are too many throw away old.
	if len(files) >= maxTmpFiles {
		sort.Slice(files, func(i, j int) bool {
			return files[i].ModTime().Before(files[j].ModTime())
		})

		var errs []error
		for len(files) >= maxTmpFiles {
			fname := filepath.Join(tmp, files[0].Name())
			if err := os.RemoveAll(fname); err != nil {
				errs = append(errs, err)
			}
			files = files[1:]
		}
		if len(errs) > 0 {
			return "", common.ToError(errs)
		}
	}

	var token string
again:
	token = generateToken()
	for _, f := range files {
		if f.Name() == token {
			goto again
		}
	}
	err = os.Rename(fname, filepath.Join(tmp, token))
	return token, err
}

func generateToken() string {
	return hex.EncodeToString(common.GenerateRandomKey(20))
}