Update deps
This commit is contained in:
24
vendor/github.com/adrg/xdg/README.md
generated
vendored
24
vendor/github.com/adrg/xdg/README.md
generated
vendored
@@ -7,8 +7,8 @@
|
||||
<h4 align="center">Go implementation of the XDG Base Directory Specification and XDG user directories.</h4>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/adrg/xdg/actions?query=workflow%3ACI">
|
||||
<img alt="Build status" src="https://github.com/adrg/xdg/workflows/CI/badge.svg">
|
||||
<a href="https://github.com/adrg/xdg/actions/workflows/tests.yml">
|
||||
<img alt="Tests status" src="https://github.com/adrg/xdg/actions/workflows/tests.yml/badge.svg">
|
||||
</a>
|
||||
<a href="https://app.codecov.io/gh/adrg/xdg">
|
||||
<img alt="Code coverage" src="https://codecov.io/gh/adrg/xdg/branch/master/graphs/badge.svg?branch=master">
|
||||
@@ -58,7 +58,7 @@ See usage [examples](#usage) below. Full documentation can be found at https://p
|
||||
The package defines sensible defaults for XDG variables which are empty or not
|
||||
present in the environment.
|
||||
|
||||
- On Unix-like operating systems, XDG environment variables are tipically defined.
|
||||
- On Unix-like operating systems, XDG environment variables are typically defined.
|
||||
Appropriate default locations are used for the environment variables which are not set.
|
||||
- On Windows, XDG environment variables are usually not set. If that is the case,
|
||||
the package relies on the appropriate [Known Folders](https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid).
|
||||
@@ -100,6 +100,17 @@ Sensible fallback locations are used for the folders which are not set.
|
||||
|
||||
### XDG user directories
|
||||
|
||||
XDG user directories environment variables are usually **not** set on most
|
||||
operating systems. However, if they are present in the environment, they take
|
||||
precedence. Appropriate fallback locations are used for the environment
|
||||
variables which are not set.
|
||||
|
||||
- On Unix-like operating systems (except macOS and Plan 9), the package reads the [user-dirs.dirs](https://man.archlinux.org/man/user-dirs.dirs.5.en) config file.
|
||||
- On Windows, the package uses the appropriate [Known Folders](https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid).
|
||||
|
||||
Lastly, default locations are used for any user directories which are not set,
|
||||
as shown in the following tables.
|
||||
|
||||
<details open>
|
||||
<summary><strong>Unix-like operating systems</strong></summary>
|
||||
<br/>
|
||||
@@ -156,7 +167,7 @@ Sensible fallback locations are used for the folders which are not set.
|
||||
| :-----------------------------------------------------------: | :--------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| <kbd><b>Home</b></kbd> | <kbd>Profile</kbd> | <kbd>%USERPROFILE%</kbd> |
|
||||
| <kbd><b>Applications</b></kbd> | <kbd>Programs</kbd><br/><kbd>CommonPrograms</kbd> | <kbd>%APPDATA%\Microsoft\Windows\Start Menu\Programs</kbd><br/><kbd>%ProgramData%\Microsoft\Windows\Start Menu\Programs</kbd> |
|
||||
| <kbd><b>Fonts</b></kbd> | <kbd>Fonts</kbd><br/><kbd>-</kbd> | <kbd>%SystemRoot%\Fonts</kbd><br/><kbd>%LOCALAPPDATA%\Microsoft\Windows\Fonts</kbd> |
|
||||
| <kbd><b>Fonts</b></kbd> | <kbd>Fonts</kbd> | <kbd>%SystemRoot%\Fonts</kbd><br/><kbd>%LOCALAPPDATA%\Microsoft\Windows\Fonts</kbd> |
|
||||
|
||||
</details>
|
||||
|
||||
@@ -263,7 +274,10 @@ See [CONTRIBUTING.MD](CONTRIBUTING.md).
|
||||
[gabriel-vasile](https://github.com/gabriel-vasile),
|
||||
[KalleDK](https://github.com/KalleDK),
|
||||
[nvkv](https://github.com/nvkv),
|
||||
[djdv](https://github.com/djdv).
|
||||
[djdv](https://github.com/djdv),
|
||||
[rrjjvv](https://github.com/rrjjvv),
|
||||
[GreyXor](https://github.com/GreyXor),
|
||||
[Rican7](https://github.com/Rican7).
|
||||
|
||||
## References
|
||||
|
||||
|
||||
57
vendor/github.com/adrg/xdg/internal/pathutil/pathutil.go
generated
vendored
57
vendor/github.com/adrg/xdg/internal/pathutil/pathutil.go
generated
vendored
@@ -8,18 +8,17 @@ import (
|
||||
)
|
||||
|
||||
// Unique eliminates the duplicate paths from the provided slice and returns
|
||||
// the result. The items in the output slice are in the order in which they
|
||||
// occur in the input slice. If a `home` location is provided, the paths are
|
||||
// expanded using the `ExpandHome` function.
|
||||
func Unique(paths []string, home string) []string {
|
||||
// the result. The paths are expanded using the `ExpandHome` function and only
|
||||
// absolute paths are kept. The items in the output slice are in the order in
|
||||
// which they occur in the input slice.
|
||||
func Unique(paths []string) []string {
|
||||
var (
|
||||
uniq []string
|
||||
registry = map[string]struct{}{}
|
||||
)
|
||||
|
||||
for _, p := range paths {
|
||||
p = ExpandHome(p, home)
|
||||
if p != "" && filepath.IsAbs(p) {
|
||||
if p = ExpandHome(p); p != "" && filepath.IsAbs(p) {
|
||||
if _, ok := registry[p]; ok {
|
||||
continue
|
||||
}
|
||||
@@ -32,6 +31,18 @@ func Unique(paths []string, home string) []string {
|
||||
return uniq
|
||||
}
|
||||
|
||||
// First returns the first absolute path from the provided slice.
|
||||
// The paths in the input slice are expanded using the `ExpandHome` function.
|
||||
func First(paths []string) string {
|
||||
for _, p := range paths {
|
||||
if p = ExpandHome(p); p != "" && filepath.IsAbs(p) {
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Create returns a suitable location relative to which the file with the
|
||||
// specified `name` can be written. The first path from the provided `paths`
|
||||
// slice which is successfully created (or already exists) is used as a base
|
||||
@@ -40,7 +51,8 @@ func Unique(paths []string, home string) []string {
|
||||
// it can also contain a set of parent directories, which will be created
|
||||
// relative to the selected parent path.
|
||||
func Create(name string, paths []string) (string, error) {
|
||||
var searchedPaths []string
|
||||
searchedPaths := make([]string, 0, len(paths))
|
||||
|
||||
for _, p := range paths {
|
||||
p = filepath.Join(p, name)
|
||||
|
||||
@@ -48,7 +60,7 @@ func Create(name string, paths []string) (string, error) {
|
||||
if Exists(dir) {
|
||||
return p, nil
|
||||
}
|
||||
if err := os.MkdirAll(dir, os.ModeDir|0700); err == nil {
|
||||
if err := os.MkdirAll(dir, os.ModeDir|0o700); err == nil {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
@@ -63,7 +75,8 @@ func Create(name string, paths []string) (string, error) {
|
||||
// slice of `paths`. The `name` parameter must contain the name of the file,
|
||||
// but it can also contain a set of parent directories.
|
||||
func Search(name string, paths []string) (string, error) {
|
||||
var searchedPaths []string
|
||||
searchedPaths := make([]string, 0, len(paths))
|
||||
|
||||
for _, p := range paths {
|
||||
p = filepath.Join(p, name)
|
||||
if Exists(p) {
|
||||
@@ -76,3 +89,29 @@ func Search(name string, paths []string) (string, error) {
|
||||
return "", fmt.Errorf("could not locate `%s` in any of the following paths: %s",
|
||||
filepath.Base(name), strings.Join(searchedPaths, ", "))
|
||||
}
|
||||
|
||||
// EnvPath returns the value of the environment variable with the specified
|
||||
// `name` if it is an absolute path, or the first absolute fallback path.
|
||||
// All paths are expanded using the `ExpandHome` function.
|
||||
func EnvPath(name string, fallbackPaths ...string) string {
|
||||
dir := ExpandHome(os.Getenv(name))
|
||||
if dir != "" && filepath.IsAbs(dir) {
|
||||
return dir
|
||||
}
|
||||
|
||||
return First(fallbackPaths)
|
||||
}
|
||||
|
||||
// EnvPathList reads the value of the environment variable with the specified
|
||||
// `name` and attempts to extract a list of absolute paths from it. If there
|
||||
// are none, a list of absolute fallback paths is returned instead. Duplicate
|
||||
// paths are removed from the returned slice. All paths are expanded using the
|
||||
// `ExpandHome` function.
|
||||
func EnvPathList(name string, fallbackPaths ...string) []string {
|
||||
dirs := Unique(filepath.SplitList(os.Getenv(name)))
|
||||
if len(dirs) != 0 {
|
||||
return dirs
|
||||
}
|
||||
|
||||
return Unique(fallbackPaths)
|
||||
}
|
||||
|
||||
19
vendor/github.com/adrg/xdg/internal/pathutil/pathutil_plan9.go
generated
vendored
19
vendor/github.com/adrg/xdg/internal/pathutil/pathutil_plan9.go
generated
vendored
@@ -1,20 +1,31 @@
|
||||
package pathutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UserHomeDir returns the home directory of the current user.
|
||||
func UserHomeDir() string {
|
||||
if home := os.Getenv("home"); home != "" {
|
||||
return home
|
||||
}
|
||||
|
||||
return "/"
|
||||
}
|
||||
|
||||
// Exists returns true if the specified path exists.
|
||||
func Exists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil || os.IsExist(err)
|
||||
return err == nil || errors.Is(err, fs.ErrExist)
|
||||
}
|
||||
|
||||
// ExpandHome substitutes `~` and `$home` at the start of the specified
|
||||
// `path` using the provided `home` location.
|
||||
func ExpandHome(path, home string) string {
|
||||
// ExpandHome substitutes `~` and `$home` at the start of the specified `path`.
|
||||
func ExpandHome(path string) string {
|
||||
home := UserHomeDir()
|
||||
if path == "" || home == "" {
|
||||
return path
|
||||
}
|
||||
|
||||
20
vendor/github.com/adrg/xdg/internal/pathutil/pathutil_unix.go
generated
vendored
20
vendor/github.com/adrg/xdg/internal/pathutil/pathutil_unix.go
generated
vendored
@@ -1,23 +1,33 @@
|
||||
//go:build aix || darwin || dragonfly || freebsd || (js && wasm) || nacl || linux || netbsd || openbsd || solaris
|
||||
// +build aix darwin dragonfly freebsd js,wasm nacl linux netbsd openbsd solaris
|
||||
|
||||
package pathutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UserHomeDir returns the home directory of the current user.
|
||||
func UserHomeDir() string {
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home
|
||||
}
|
||||
|
||||
return "/"
|
||||
}
|
||||
|
||||
// Exists returns true if the specified path exists.
|
||||
func Exists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil || os.IsExist(err)
|
||||
return err == nil || errors.Is(err, fs.ErrExist)
|
||||
}
|
||||
|
||||
// ExpandHome substitutes `~` and `$HOME` at the start of the specified
|
||||
// `path` using the provided `home` location.
|
||||
func ExpandHome(path, home string) string {
|
||||
// ExpandHome substitutes `~` and `$HOME` at the start of the specified `path`.
|
||||
func ExpandHome(path string) string {
|
||||
home := UserHomeDir()
|
||||
if path == "" || home == "" {
|
||||
return path
|
||||
}
|
||||
|
||||
15
vendor/github.com/adrg/xdg/internal/pathutil/pathutil_windows.go
generated
vendored
15
vendor/github.com/adrg/xdg/internal/pathutil/pathutil_windows.go
generated
vendored
@@ -1,6 +1,8 @@
|
||||
package pathutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -8,6 +10,11 @@ import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// UserHomeDir returns the home directory of the current user.
|
||||
func UserHomeDir() string {
|
||||
return KnownFolder(windows.FOLDERID_Profile, []string{"USERPROFILE"}, nil)
|
||||
}
|
||||
|
||||
// Exists returns true if the specified path exists.
|
||||
func Exists(path string) bool {
|
||||
fi, err := os.Lstat(path)
|
||||
@@ -15,12 +22,12 @@ func Exists(path string) bool {
|
||||
_, err = filepath.EvalSymlinks(path)
|
||||
}
|
||||
|
||||
return err == nil || os.IsExist(err)
|
||||
return err == nil || errors.Is(err, fs.ErrExist)
|
||||
}
|
||||
|
||||
// ExpandHome substitutes `%USERPROFILE%` at the start of the specified
|
||||
// `path` using the provided `home` location.
|
||||
func ExpandHome(path, home string) string {
|
||||
// ExpandHome substitutes `%USERPROFILE%` at the start of the specified `path`.
|
||||
func ExpandHome(path string) string {
|
||||
home := UserHomeDir()
|
||||
if path == "" || home == "" {
|
||||
return path
|
||||
}
|
||||
|
||||
82
vendor/github.com/adrg/xdg/internal/userdirs/config_unix.go
generated
vendored
Normal file
82
vendor/github.com/adrg/xdg/internal/userdirs/config_unix.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
//go:build aix || dragonfly || freebsd || (js && wasm) || nacl || linux || netbsd || openbsd || solaris
|
||||
|
||||
package userdirs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/adrg/xdg/internal/pathutil"
|
||||
)
|
||||
|
||||
// ParseConfigFile parses the user directories config file at the
|
||||
// specified location.
|
||||
func ParseConfigFile(name string) (*Directories, error) {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return ParseConfig(f)
|
||||
}
|
||||
|
||||
// ParseConfig parses the user directories config file contained in
|
||||
// the provided reader.
|
||||
func ParseConfig(r io.Reader) (*Directories, error) {
|
||||
dirs := &Directories{}
|
||||
fieldsMap := map[string]*string{
|
||||
EnvDesktopDir: &dirs.Desktop,
|
||||
EnvDownloadDir: &dirs.Download,
|
||||
EnvDocumentsDir: &dirs.Documents,
|
||||
EnvMusicDir: &dirs.Music,
|
||||
EnvPicturesDir: &dirs.Pictures,
|
||||
EnvVideosDir: &dirs.Videos,
|
||||
EnvTemplatesDir: &dirs.Templates,
|
||||
EnvPublicShareDir: &dirs.PublicShare,
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(line, "XDG_") {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.Split(line, "=")
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse key.
|
||||
field, ok := fieldsMap[strings.TrimSpace(parts[0])]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse value.
|
||||
runes := []rune(strings.TrimSpace(parts[1]))
|
||||
|
||||
lenRunes := len(runes)
|
||||
if lenRunes <= 2 || runes[0] != '"' {
|
||||
continue
|
||||
}
|
||||
|
||||
for i := 1; i < lenRunes; i++ {
|
||||
if runes[i] == '"' {
|
||||
*field = pathutil.ExpandHome(string(runes[1:i]))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dirs, nil
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
package xdg
|
||||
package userdirs
|
||||
|
||||
// XDG user directories environment variables.
|
||||
const (
|
||||
envDesktopDir = "XDG_DESKTOP_DIR"
|
||||
envDownloadDir = "XDG_DOWNLOAD_DIR"
|
||||
envDocumentsDir = "XDG_DOCUMENTS_DIR"
|
||||
envMusicDir = "XDG_MUSIC_DIR"
|
||||
envPicturesDir = "XDG_PICTURES_DIR"
|
||||
envVideosDir = "XDG_VIDEOS_DIR"
|
||||
envTemplatesDir = "XDG_TEMPLATES_DIR"
|
||||
envPublicShareDir = "XDG_PUBLICSHARE_DIR"
|
||||
EnvDesktopDir = "XDG_DESKTOP_DIR"
|
||||
EnvDownloadDir = "XDG_DOWNLOAD_DIR"
|
||||
EnvDocumentsDir = "XDG_DOCUMENTS_DIR"
|
||||
EnvMusicDir = "XDG_MUSIC_DIR"
|
||||
EnvPicturesDir = "XDG_PICTURES_DIR"
|
||||
EnvVideosDir = "XDG_VIDEOS_DIR"
|
||||
EnvTemplatesDir = "XDG_TEMPLATES_DIR"
|
||||
EnvPublicShareDir = "XDG_PUBLICSHARE_DIR"
|
||||
)
|
||||
|
||||
// UserDirectories defines the locations of well known user directories.
|
||||
type UserDirectories struct {
|
||||
// Directories defines the locations of well known user directories.
|
||||
type Directories struct {
|
||||
// Desktop defines the location of the user's desktop directory.
|
||||
Desktop string
|
||||
|
||||
42
vendor/github.com/adrg/xdg/paths_darwin.go
generated
vendored
42
vendor/github.com/adrg/xdg/paths_darwin.go
generated
vendored
@@ -1,18 +1,12 @@
|
||||
package xdg
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/adrg/xdg/internal/pathutil"
|
||||
"github.com/adrg/xdg/internal/userdirs"
|
||||
)
|
||||
|
||||
func homeDir() string {
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home
|
||||
}
|
||||
|
||||
return "/"
|
||||
}
|
||||
|
||||
func initDirs(home string) {
|
||||
initBaseDirs(home)
|
||||
initUserDirs(home)
|
||||
@@ -23,17 +17,17 @@ func initBaseDirs(home string) {
|
||||
rootAppSupport := "/Library/Application Support"
|
||||
|
||||
// Initialize standard directories.
|
||||
baseDirs.dataHome = xdgPath(envDataHome, homeAppSupport)
|
||||
baseDirs.data = xdgPaths(envDataDirs, rootAppSupport)
|
||||
baseDirs.configHome = xdgPath(envConfigHome, homeAppSupport)
|
||||
baseDirs.config = xdgPaths(envConfigDirs,
|
||||
baseDirs.dataHome = pathutil.EnvPath(envDataHome, homeAppSupport)
|
||||
baseDirs.data = pathutil.EnvPathList(envDataDirs, rootAppSupport)
|
||||
baseDirs.configHome = pathutil.EnvPath(envConfigHome, homeAppSupport)
|
||||
baseDirs.config = pathutil.EnvPathList(envConfigDirs,
|
||||
filepath.Join(home, "Library", "Preferences"),
|
||||
rootAppSupport,
|
||||
"/Library/Preferences",
|
||||
)
|
||||
baseDirs.stateHome = xdgPath(envStateHome, homeAppSupport)
|
||||
baseDirs.cacheHome = xdgPath(envCacheHome, filepath.Join(home, "Library", "Caches"))
|
||||
baseDirs.runtime = xdgPath(envRuntimeDir, homeAppSupport)
|
||||
baseDirs.stateHome = pathutil.EnvPath(envStateHome, homeAppSupport)
|
||||
baseDirs.cacheHome = pathutil.EnvPath(envCacheHome, filepath.Join(home, "Library", "Caches"))
|
||||
baseDirs.runtime = pathutil.EnvPath(envRuntimeDir, homeAppSupport)
|
||||
|
||||
// Initialize non-standard directories.
|
||||
baseDirs.applications = []string{
|
||||
@@ -49,12 +43,12 @@ func initBaseDirs(home string) {
|
||||
}
|
||||
|
||||
func initUserDirs(home string) {
|
||||
UserDirs.Desktop = xdgPath(envDesktopDir, filepath.Join(home, "Desktop"))
|
||||
UserDirs.Download = xdgPath(envDownloadDir, filepath.Join(home, "Downloads"))
|
||||
UserDirs.Documents = xdgPath(envDocumentsDir, filepath.Join(home, "Documents"))
|
||||
UserDirs.Music = xdgPath(envMusicDir, filepath.Join(home, "Music"))
|
||||
UserDirs.Pictures = xdgPath(envPicturesDir, filepath.Join(home, "Pictures"))
|
||||
UserDirs.Videos = xdgPath(envVideosDir, filepath.Join(home, "Movies"))
|
||||
UserDirs.Templates = xdgPath(envTemplatesDir, filepath.Join(home, "Templates"))
|
||||
UserDirs.PublicShare = xdgPath(envPublicShareDir, filepath.Join(home, "Public"))
|
||||
UserDirs.Desktop = pathutil.EnvPath(userdirs.EnvDesktopDir, filepath.Join(home, "Desktop"))
|
||||
UserDirs.Download = pathutil.EnvPath(userdirs.EnvDownloadDir, filepath.Join(home, "Downloads"))
|
||||
UserDirs.Documents = pathutil.EnvPath(userdirs.EnvDocumentsDir, filepath.Join(home, "Documents"))
|
||||
UserDirs.Music = pathutil.EnvPath(userdirs.EnvMusicDir, filepath.Join(home, "Music"))
|
||||
UserDirs.Pictures = pathutil.EnvPath(userdirs.EnvPicturesDir, filepath.Join(home, "Pictures"))
|
||||
UserDirs.Videos = pathutil.EnvPath(userdirs.EnvVideosDir, filepath.Join(home, "Movies"))
|
||||
UserDirs.Templates = pathutil.EnvPath(userdirs.EnvTemplatesDir, filepath.Join(home, "Templates"))
|
||||
UserDirs.PublicShare = pathutil.EnvPath(userdirs.EnvPublicShareDir, filepath.Join(home, "Public"))
|
||||
}
|
||||
|
||||
42
vendor/github.com/adrg/xdg/paths_plan9.go
generated
vendored
42
vendor/github.com/adrg/xdg/paths_plan9.go
generated
vendored
@@ -1,18 +1,12 @@
|
||||
package xdg
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/adrg/xdg/internal/pathutil"
|
||||
"github.com/adrg/xdg/internal/userdirs"
|
||||
)
|
||||
|
||||
func homeDir() string {
|
||||
if home := os.Getenv("home"); home != "" {
|
||||
return home
|
||||
}
|
||||
|
||||
return "/"
|
||||
}
|
||||
|
||||
func initDirs(home string) {
|
||||
initBaseDirs(home)
|
||||
initUserDirs(home)
|
||||
@@ -23,13 +17,13 @@ func initBaseDirs(home string) {
|
||||
rootLibDir := "/lib"
|
||||
|
||||
// Initialize standard directories.
|
||||
baseDirs.dataHome = xdgPath(envDataHome, homeLibDir)
|
||||
baseDirs.data = xdgPaths(envDataDirs, rootLibDir)
|
||||
baseDirs.configHome = xdgPath(envConfigHome, homeLibDir)
|
||||
baseDirs.config = xdgPaths(envConfigDirs, rootLibDir)
|
||||
baseDirs.stateHome = xdgPath(envStateHome, filepath.Join(homeLibDir, "state"))
|
||||
baseDirs.cacheHome = xdgPath(envCacheHome, filepath.Join(homeLibDir, "cache"))
|
||||
baseDirs.runtime = xdgPath(envRuntimeDir, "/tmp")
|
||||
baseDirs.dataHome = pathutil.EnvPath(envDataHome, homeLibDir)
|
||||
baseDirs.data = pathutil.EnvPathList(envDataDirs, rootLibDir)
|
||||
baseDirs.configHome = pathutil.EnvPath(envConfigHome, homeLibDir)
|
||||
baseDirs.config = pathutil.EnvPathList(envConfigDirs, rootLibDir)
|
||||
baseDirs.stateHome = pathutil.EnvPath(envStateHome, filepath.Join(homeLibDir, "state"))
|
||||
baseDirs.cacheHome = pathutil.EnvPath(envCacheHome, filepath.Join(homeLibDir, "cache"))
|
||||
baseDirs.runtime = pathutil.EnvPath(envRuntimeDir, "/tmp")
|
||||
|
||||
// Initialize non-standard directories.
|
||||
baseDirs.applications = []string{
|
||||
@@ -44,12 +38,12 @@ func initBaseDirs(home string) {
|
||||
}
|
||||
|
||||
func initUserDirs(home string) {
|
||||
UserDirs.Desktop = xdgPath(envDesktopDir, filepath.Join(home, "desktop"))
|
||||
UserDirs.Download = xdgPath(envDownloadDir, filepath.Join(home, "downloads"))
|
||||
UserDirs.Documents = xdgPath(envDocumentsDir, filepath.Join(home, "documents"))
|
||||
UserDirs.Music = xdgPath(envMusicDir, filepath.Join(home, "music"))
|
||||
UserDirs.Pictures = xdgPath(envPicturesDir, filepath.Join(home, "pictures"))
|
||||
UserDirs.Videos = xdgPath(envVideosDir, filepath.Join(home, "videos"))
|
||||
UserDirs.Templates = xdgPath(envTemplatesDir, filepath.Join(home, "templates"))
|
||||
UserDirs.PublicShare = xdgPath(envPublicShareDir, filepath.Join(home, "public"))
|
||||
UserDirs.Desktop = pathutil.EnvPath(userdirs.EnvDesktopDir, filepath.Join(home, "desktop"))
|
||||
UserDirs.Download = pathutil.EnvPath(userdirs.EnvDownloadDir, filepath.Join(home, "downloads"))
|
||||
UserDirs.Documents = pathutil.EnvPath(userdirs.EnvDocumentsDir, filepath.Join(home, "documents"))
|
||||
UserDirs.Music = pathutil.EnvPath(userdirs.EnvMusicDir, filepath.Join(home, "music"))
|
||||
UserDirs.Pictures = pathutil.EnvPath(userdirs.EnvPicturesDir, filepath.Join(home, "pictures"))
|
||||
UserDirs.Videos = pathutil.EnvPath(userdirs.EnvVideosDir, filepath.Join(home, "videos"))
|
||||
UserDirs.Templates = pathutil.EnvPath(userdirs.EnvTemplatesDir, filepath.Join(home, "templates"))
|
||||
UserDirs.PublicShare = pathutil.EnvPath(userdirs.EnvPublicShareDir, filepath.Join(home, "public"))
|
||||
}
|
||||
|
||||
53
vendor/github.com/adrg/xdg/paths_unix.go
generated
vendored
53
vendor/github.com/adrg/xdg/paths_unix.go
generated
vendored
@@ -1,5 +1,4 @@
|
||||
//go:build aix || dragonfly || freebsd || (js && wasm) || nacl || linux || netbsd || openbsd || solaris
|
||||
// +build aix dragonfly freebsd js,wasm nacl linux netbsd openbsd solaris
|
||||
|
||||
package xdg
|
||||
|
||||
@@ -9,30 +8,23 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/adrg/xdg/internal/pathutil"
|
||||
"github.com/adrg/xdg/internal/userdirs"
|
||||
)
|
||||
|
||||
func homeDir() string {
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home
|
||||
}
|
||||
|
||||
return "/"
|
||||
}
|
||||
|
||||
func initDirs(home string) {
|
||||
initBaseDirs(home)
|
||||
initUserDirs(home)
|
||||
initUserDirs(home, baseDirs.configHome)
|
||||
}
|
||||
|
||||
func initBaseDirs(home string) {
|
||||
// Initialize standard directories.
|
||||
baseDirs.dataHome = xdgPath(envDataHome, filepath.Join(home, ".local", "share"))
|
||||
baseDirs.data = xdgPaths(envDataDirs, "/usr/local/share", "/usr/share")
|
||||
baseDirs.configHome = xdgPath(envConfigHome, filepath.Join(home, ".config"))
|
||||
baseDirs.config = xdgPaths(envConfigDirs, "/etc/xdg")
|
||||
baseDirs.stateHome = xdgPath(envStateHome, filepath.Join(home, ".local", "state"))
|
||||
baseDirs.cacheHome = xdgPath(envCacheHome, filepath.Join(home, ".cache"))
|
||||
baseDirs.runtime = xdgPath(envRuntimeDir, filepath.Join("/run/user", strconv.Itoa(os.Getuid())))
|
||||
baseDirs.dataHome = pathutil.EnvPath(envDataHome, filepath.Join(home, ".local", "share"))
|
||||
baseDirs.data = pathutil.EnvPathList(envDataDirs, "/usr/local/share", "/usr/share")
|
||||
baseDirs.configHome = pathutil.EnvPath(envConfigHome, filepath.Join(home, ".config"))
|
||||
baseDirs.config = pathutil.EnvPathList(envConfigDirs, "/etc/xdg")
|
||||
baseDirs.stateHome = pathutil.EnvPath(envStateHome, filepath.Join(home, ".local", "state"))
|
||||
baseDirs.cacheHome = pathutil.EnvPath(envCacheHome, filepath.Join(home, ".cache"))
|
||||
baseDirs.runtime = pathutil.EnvPath(envRuntimeDir, filepath.Join("/run/user", strconv.Itoa(os.Getuid())))
|
||||
|
||||
// Initialize non-standard directories.
|
||||
appDirs := []string{
|
||||
@@ -55,17 +47,22 @@ func initBaseDirs(home string) {
|
||||
fontDirs = append(fontDirs, filepath.Join(dir, "fonts"))
|
||||
}
|
||||
|
||||
baseDirs.applications = pathutil.Unique(appDirs, Home)
|
||||
baseDirs.fonts = pathutil.Unique(fontDirs, Home)
|
||||
baseDirs.applications = pathutil.Unique(appDirs)
|
||||
baseDirs.fonts = pathutil.Unique(fontDirs)
|
||||
}
|
||||
|
||||
func initUserDirs(home string) {
|
||||
UserDirs.Desktop = xdgPath(envDesktopDir, filepath.Join(home, "Desktop"))
|
||||
UserDirs.Download = xdgPath(envDownloadDir, filepath.Join(home, "Downloads"))
|
||||
UserDirs.Documents = xdgPath(envDocumentsDir, filepath.Join(home, "Documents"))
|
||||
UserDirs.Music = xdgPath(envMusicDir, filepath.Join(home, "Music"))
|
||||
UserDirs.Pictures = xdgPath(envPicturesDir, filepath.Join(home, "Pictures"))
|
||||
UserDirs.Videos = xdgPath(envVideosDir, filepath.Join(home, "Videos"))
|
||||
UserDirs.Templates = xdgPath(envTemplatesDir, filepath.Join(home, "Templates"))
|
||||
UserDirs.PublicShare = xdgPath(envPublicShareDir, filepath.Join(home, "Public"))
|
||||
func initUserDirs(home, configHome string) {
|
||||
dirs, err := userdirs.ParseConfigFile(filepath.Join(configHome, "user-dirs.dirs"))
|
||||
if err != nil {
|
||||
dirs = &UserDirectories{}
|
||||
}
|
||||
|
||||
UserDirs.Desktop = pathutil.EnvPath(userdirs.EnvDesktopDir, dirs.Desktop, filepath.Join(home, "Desktop"))
|
||||
UserDirs.Download = pathutil.EnvPath(userdirs.EnvDownloadDir, dirs.Download, filepath.Join(home, "Downloads"))
|
||||
UserDirs.Documents = pathutil.EnvPath(userdirs.EnvDocumentsDir, dirs.Documents, filepath.Join(home, "Documents"))
|
||||
UserDirs.Music = pathutil.EnvPath(userdirs.EnvMusicDir, dirs.Music, filepath.Join(home, "Music"))
|
||||
UserDirs.Pictures = pathutil.EnvPath(userdirs.EnvPicturesDir, dirs.Pictures, filepath.Join(home, "Pictures"))
|
||||
UserDirs.Videos = pathutil.EnvPath(userdirs.EnvVideosDir, dirs.Videos, filepath.Join(home, "Videos"))
|
||||
UserDirs.Templates = pathutil.EnvPath(userdirs.EnvTemplatesDir, dirs.Templates, filepath.Join(home, "Templates"))
|
||||
UserDirs.PublicShare = pathutil.EnvPath(userdirs.EnvPublicShareDir, dirs.PublicShare, filepath.Join(home, "Public"))
|
||||
}
|
||||
|
||||
39
vendor/github.com/adrg/xdg/paths_windows.go
generated
vendored
39
vendor/github.com/adrg/xdg/paths_windows.go
generated
vendored
@@ -4,17 +4,10 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/adrg/xdg/internal/pathutil"
|
||||
"github.com/adrg/xdg/internal/userdirs"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func homeDir() string {
|
||||
return pathutil.KnownFolder(
|
||||
windows.FOLDERID_Profile,
|
||||
[]string{"USERPROFILE"},
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
func initDirs(home string) {
|
||||
kf := initKnownFolders(home)
|
||||
initBaseDirs(home, kf)
|
||||
@@ -23,13 +16,13 @@ func initDirs(home string) {
|
||||
|
||||
func initBaseDirs(home string, kf *knownFolders) {
|
||||
// Initialize standard directories.
|
||||
baseDirs.dataHome = xdgPath(envDataHome, kf.localAppData)
|
||||
baseDirs.data = xdgPaths(envDataDirs, kf.roamingAppData, kf.programData)
|
||||
baseDirs.configHome = xdgPath(envConfigHome, kf.localAppData)
|
||||
baseDirs.config = xdgPaths(envConfigDirs, kf.programData, kf.roamingAppData)
|
||||
baseDirs.stateHome = xdgPath(envStateHome, kf.localAppData)
|
||||
baseDirs.cacheHome = xdgPath(envCacheHome, filepath.Join(kf.localAppData, "cache"))
|
||||
baseDirs.runtime = xdgPath(envRuntimeDir, kf.localAppData)
|
||||
baseDirs.dataHome = pathutil.EnvPath(envDataHome, kf.localAppData)
|
||||
baseDirs.data = pathutil.EnvPathList(envDataDirs, kf.roamingAppData, kf.programData)
|
||||
baseDirs.configHome = pathutil.EnvPath(envConfigHome, kf.localAppData)
|
||||
baseDirs.config = pathutil.EnvPathList(envConfigDirs, kf.programData, kf.roamingAppData)
|
||||
baseDirs.stateHome = pathutil.EnvPath(envStateHome, kf.localAppData)
|
||||
baseDirs.cacheHome = pathutil.EnvPath(envCacheHome, filepath.Join(kf.localAppData, "cache"))
|
||||
baseDirs.runtime = pathutil.EnvPath(envRuntimeDir, kf.localAppData)
|
||||
|
||||
// Initialize non-standard directories.
|
||||
baseDirs.applications = []string{
|
||||
@@ -43,14 +36,14 @@ func initBaseDirs(home string, kf *knownFolders) {
|
||||
}
|
||||
|
||||
func initUserDirs(home string, kf *knownFolders) {
|
||||
UserDirs.Desktop = xdgPath(envDesktopDir, kf.desktop)
|
||||
UserDirs.Download = xdgPath(envDownloadDir, kf.downloads)
|
||||
UserDirs.Documents = xdgPath(envDocumentsDir, kf.documents)
|
||||
UserDirs.Music = xdgPath(envMusicDir, kf.music)
|
||||
UserDirs.Pictures = xdgPath(envPicturesDir, kf.pictures)
|
||||
UserDirs.Videos = xdgPath(envVideosDir, kf.videos)
|
||||
UserDirs.Templates = xdgPath(envTemplatesDir, kf.templates)
|
||||
UserDirs.PublicShare = xdgPath(envPublicShareDir, kf.public)
|
||||
UserDirs.Desktop = pathutil.EnvPath(userdirs.EnvDesktopDir, kf.desktop)
|
||||
UserDirs.Download = pathutil.EnvPath(userdirs.EnvDownloadDir, kf.downloads)
|
||||
UserDirs.Documents = pathutil.EnvPath(userdirs.EnvDocumentsDir, kf.documents)
|
||||
UserDirs.Music = pathutil.EnvPath(userdirs.EnvMusicDir, kf.music)
|
||||
UserDirs.Pictures = pathutil.EnvPath(userdirs.EnvPicturesDir, kf.pictures)
|
||||
UserDirs.Videos = pathutil.EnvPath(userdirs.EnvVideosDir, kf.videos)
|
||||
UserDirs.Templates = pathutil.EnvPath(userdirs.EnvTemplatesDir, kf.templates)
|
||||
UserDirs.PublicShare = pathutil.EnvPath(userdirs.EnvPublicShareDir, kf.public)
|
||||
}
|
||||
|
||||
type knownFolders struct {
|
||||
|
||||
29
vendor/github.com/adrg/xdg/xdg.go
generated
vendored
29
vendor/github.com/adrg/xdg/xdg.go
generated
vendored
@@ -1,12 +1,13 @@
|
||||
package xdg
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/adrg/xdg/internal/pathutil"
|
||||
"github.com/adrg/xdg/internal/userdirs"
|
||||
)
|
||||
|
||||
// UserDirectories defines the locations of well known user directories.
|
||||
type UserDirectories = userdirs.Directories
|
||||
|
||||
var (
|
||||
// Home contains the path of the user's home directory.
|
||||
Home string
|
||||
@@ -29,7 +30,7 @@ var (
|
||||
|
||||
// ConfigHome defines the base directory relative to which user-specific
|
||||
// configuration files should be written. This directory is defined by
|
||||
// the $XDG_CONFIG_HOME environment variable. If the variable is not
|
||||
// the $XDG_CONFIG_HOME environment variable. If the variable is
|
||||
// not set, a default equal to $HOME/.config should be used.
|
||||
ConfigHome string
|
||||
|
||||
@@ -88,7 +89,7 @@ func init() {
|
||||
// in the environment.
|
||||
func Reload() {
|
||||
// Initialize home directory.
|
||||
Home = homeDir()
|
||||
Home = pathutil.UserHomeDir()
|
||||
|
||||
// Initialize base and user directories.
|
||||
initDirs(Home)
|
||||
@@ -198,21 +199,3 @@ func SearchCacheFile(relPath string) (string, error) {
|
||||
func SearchRuntimeFile(relPath string) (string, error) {
|
||||
return baseDirs.searchRuntimeFile(relPath)
|
||||
}
|
||||
|
||||
func xdgPath(name, defaultPath string) string {
|
||||
dir := pathutil.ExpandHome(os.Getenv(name), Home)
|
||||
if dir != "" && filepath.IsAbs(dir) {
|
||||
return dir
|
||||
}
|
||||
|
||||
return defaultPath
|
||||
}
|
||||
|
||||
func xdgPaths(name string, defaultPaths ...string) []string {
|
||||
dirs := pathutil.Unique(filepath.SplitList(os.Getenv(name)), Home)
|
||||
if len(dirs) != 0 {
|
||||
return dirs
|
||||
}
|
||||
|
||||
return pathutil.Unique(defaultPaths, Home)
|
||||
}
|
||||
|
||||
25
vendor/github.com/go-co-op/gocron/v2/.golangci.yaml
generated
vendored
25
vendor/github.com/go-co-op/gocron/v2/.golangci.yaml
generated
vendored
@@ -2,14 +2,20 @@ run:
|
||||
timeout: 5m
|
||||
issues-exit-code: 1
|
||||
tests: true
|
||||
skip-dirs:
|
||||
- local
|
||||
|
||||
issues:
|
||||
max-same-issues: 100
|
||||
include:
|
||||
- EXC0012
|
||||
- EXC0014
|
||||
exclude-dirs:
|
||||
- local
|
||||
exclude-rules:
|
||||
- path: example_test.go
|
||||
linters:
|
||||
- revive
|
||||
text: "seems to be unused"
|
||||
fix: true
|
||||
|
||||
linters:
|
||||
enable:
|
||||
@@ -29,21 +35,10 @@ linters:
|
||||
- whitespace
|
||||
|
||||
output:
|
||||
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
|
||||
format: colored-line-number
|
||||
# print lines of code with issue, default is true
|
||||
formats:
|
||||
- format: colored-line-number
|
||||
print-issued-lines: true
|
||||
# print linter name in the end of issue text, default is true
|
||||
print-linter-name: true
|
||||
# make issues output unique by line, default is true
|
||||
uniq-by-line: true
|
||||
# add a prefix to the output file references; default is no prefix
|
||||
path-prefix: ""
|
||||
# sorts results by: filepath, line and column
|
||||
sort-results: true
|
||||
|
||||
linters-settings:
|
||||
golint:
|
||||
min-confidence: 0.8
|
||||
|
||||
fix: true
|
||||
|
||||
5
vendor/github.com/go-co-op/gocron/v2/Makefile
generated
vendored
5
vendor/github.com/go-co-op/gocron/v2/Makefile
generated
vendored
@@ -1,4 +1,4 @@
|
||||
.PHONY: fmt lint test mocks test_coverage
|
||||
.PHONY: fmt lint test mocks test_coverage test_ci
|
||||
|
||||
GO_PKGS := $(shell go list -f {{.Dir}} ./...)
|
||||
|
||||
@@ -15,5 +15,8 @@ test:
|
||||
test_coverage:
|
||||
@go test -race -v $(GO_FLAGS) -count=1 -coverprofile=coverage.out -covermode=atomic $(GO_PKGS)
|
||||
|
||||
test_ci:
|
||||
@TEST_ENV=ci go test -race -v $(GO_FLAGS) -count=1 $(GO_PKGS)
|
||||
|
||||
mocks:
|
||||
@go generate ./...
|
||||
|
||||
3
vendor/github.com/go-co-op/gocron/v2/README.md
generated
vendored
3
vendor/github.com/go-co-op/gocron/v2/README.md
generated
vendored
@@ -98,7 +98,7 @@ Jobs can be run every x weeks on specific days of the week and at specific times
|
||||
- [**Monthly**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#MonthlyJob):
|
||||
Jobs can be run every x months on specific days of the month and at specific times.
|
||||
- [**One time**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#OneTimeJob):
|
||||
Jobs can be run once at a specific time. These are non-recurring jobs.
|
||||
Jobs can be run at specific time(s) (either once or many times).
|
||||
|
||||
### Concurrency Limits
|
||||
Jobs can be limited individually or across the entire scheduler.
|
||||
@@ -120,6 +120,7 @@ other instances checking to see if a new leader needs to be elected.
|
||||
(don't see what you need? request on slack to get a repo created to contribute it!)
|
||||
- [**Locker**](https://pkg.go.dev/github.com/go-co-op/gocron/v2#WithDistributedLocker):
|
||||
A locker can be used to lock each run of a job to a single instance of gocron.
|
||||
Locker can be at job or scheduler, if it is defined both at job and scheduler then locker of job will take precedence.
|
||||
- Implementations: [go-co-op lockers](https://github.com/go-co-op?q=-lock&type=all&language=&sort=)
|
||||
(don't see what you need? request on slack to get a repo created to contribute it!)
|
||||
|
||||
|
||||
7
vendor/github.com/go-co-op/gocron/v2/SECURITY.md
generated
vendored
7
vendor/github.com/go-co-op/gocron/v2/SECURITY.md
generated
vendored
@@ -2,14 +2,15 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
The current plan is to maintain version 1 as long as possible incorporating any necessary security patches.
|
||||
The current plan is to maintain version 2 as long as possible incorporating any necessary security patches. Version 1 is deprecated and will no longer be patched.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 1.x.x | :white_check_mark: |
|
||||
| 1.x.x | :heavy_multiplication_x: |
|
||||
| 2.x.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Vulnerabilities can be reported by [opening an issue](https://github.com/go-co-op/gocron/issues/new/choose) or reaching out on Slack: [<img src="https://img.shields.io/badge/gophers-gocron-brightgreen?logo=slack">](https://gophers.slack.com/archives/CQ7T0T1FW)
|
||||
|
||||
We will do our best to addrerss any vulnerabilities in an expeditious manner.
|
||||
We will do our best to address any vulnerabilities in an expeditious manner.
|
||||
|
||||
6
vendor/github.com/go-co-op/gocron/v2/errors.go
generated
vendored
6
vendor/github.com/go-co-op/gocron/v2/errors.go
generated
vendored
@@ -33,15 +33,21 @@ var (
|
||||
ErrWeeklyJobDaysOfTheWeekNil = fmt.Errorf("gocron: WeeklyJob: daysOfTheWeek must not be nil")
|
||||
ErrWeeklyJobHours = fmt.Errorf("gocron: WeeklyJob: atTimes hours must be between 0 and 23 inclusive")
|
||||
ErrWeeklyJobMinutesSeconds = fmt.Errorf("gocron: WeeklyJob: atTimes minutes and seconds must be between 0 and 59 inclusive")
|
||||
ErrPanicRecovered = fmt.Errorf("gocron: panic recovered")
|
||||
ErrWithClockNil = fmt.Errorf("gocron: WithClock: clock must not be nil")
|
||||
ErrWithDistributedElectorNil = fmt.Errorf("gocron: WithDistributedElector: elector must not be nil")
|
||||
ErrWithDistributedLockerNil = fmt.Errorf("gocron: WithDistributedLocker: locker must not be nil")
|
||||
ErrWithDistributedJobLockerNil = fmt.Errorf("gocron: WithDistributedJobLocker: locker must not be nil")
|
||||
ErrWithIdentifierNil = fmt.Errorf("gocron: WithIdentifier: identifier must not be nil")
|
||||
ErrWithLimitConcurrentJobsZero = fmt.Errorf("gocron: WithLimitConcurrentJobs: limit must be greater than 0")
|
||||
ErrWithLocationNil = fmt.Errorf("gocron: WithLocation: location must not be nil")
|
||||
ErrWithLoggerNil = fmt.Errorf("gocron: WithLogger: logger must not be nil")
|
||||
ErrWithMonitorNil = fmt.Errorf("gocron: WithMonitor: monitor must not be nil")
|
||||
ErrWithNameEmpty = fmt.Errorf("gocron: WithName: name must not be empty")
|
||||
ErrWithStartDateTimePast = fmt.Errorf("gocron: WithStartDateTime: start must not be in the past")
|
||||
ErrWithStopDateTimePast = fmt.Errorf("gocron: WithStopDateTime: end must not be in the past")
|
||||
ErrStartTimeLaterThanEndTime = fmt.Errorf("gocron: WithStartDateTime: start must not be later than end")
|
||||
ErrStopTimeEarlierThanStartTime = fmt.Errorf("gocron: WithStopDateTime: end must not be earlier than start")
|
||||
ErrWithStopTimeoutZeroOrNegative = fmt.Errorf("gocron: WithStopTimeout: timeout must be greater than 0")
|
||||
)
|
||||
|
||||
|
||||
105
vendor/github.com/go-co-op/gocron/v2/executor.go
generated
vendored
105
vendor/github.com/go-co-op/gocron/v2/executor.go
generated
vendored
@@ -2,29 +2,53 @@ package gocron
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jonboulle/clockwork"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type executor struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
logger Logger
|
||||
stopCh chan struct{}
|
||||
jobsIn chan jobIn
|
||||
// context used for shutting down
|
||||
ctx context.Context
|
||||
// cancel used by the executor to signal a stop of it's functions
|
||||
cancel context.CancelFunc
|
||||
// clock used for regular time or mocking time
|
||||
clock clockwork.Clock
|
||||
// the executor's logger
|
||||
logger Logger
|
||||
|
||||
// receives jobs scheduled to execute
|
||||
jobsIn chan jobIn
|
||||
// sends out jobs for rescheduling
|
||||
jobsOutForRescheduling chan uuid.UUID
|
||||
jobsOutCompleted chan uuid.UUID
|
||||
jobOutRequest chan jobOutRequest
|
||||
stopTimeout time.Duration
|
||||
done chan error
|
||||
singletonRunners *sync.Map // map[uuid.UUID]singletonRunner
|
||||
limitMode *limitModeConfig
|
||||
elector Elector
|
||||
locker Locker
|
||||
monitor Monitor
|
||||
// sends out jobs once completed
|
||||
jobsOutCompleted chan uuid.UUID
|
||||
// used to request jobs from the scheduler
|
||||
jobOutRequest chan jobOutRequest
|
||||
|
||||
// used by the executor to receive a stop signal from the scheduler
|
||||
stopCh chan struct{}
|
||||
// the timeout value when stopping
|
||||
stopTimeout time.Duration
|
||||
// used to signal that the executor has completed shutdown
|
||||
done chan error
|
||||
|
||||
// runners for any singleton type jobs
|
||||
// map[uuid.UUID]singletonRunner
|
||||
singletonRunners *sync.Map
|
||||
// config for limit mode
|
||||
limitMode *limitModeConfig
|
||||
// the elector when running distributed instances
|
||||
elector Elector
|
||||
// the locker when running distributed instances
|
||||
locker Locker
|
||||
// monitor for reporting metrics
|
||||
monitor Monitor
|
||||
}
|
||||
|
||||
type jobIn struct {
|
||||
@@ -131,8 +155,8 @@ func (e *executor) start() {
|
||||
// to work through the channel backlog. A hard limit of 1000 is in place
|
||||
// at which point this call would block.
|
||||
// TODO when metrics are added, this should increment a wait metric
|
||||
e.limitMode.in <- jIn
|
||||
e.sendOutForRescheduling(&jIn)
|
||||
e.limitMode.in <- jIn
|
||||
}
|
||||
} else {
|
||||
// no limit mode, so we're either running a regular job or
|
||||
@@ -303,6 +327,10 @@ func (e *executor) singletonModeRunner(name string, in chan jobIn, wg *waitGroup
|
||||
j := requestJobCtx(ctx, jIn.id, e.jobOutRequest)
|
||||
cancel()
|
||||
if j != nil {
|
||||
// need to set shouldSendOut = false here, as there is a duplicative call to sendOutForRescheduling
|
||||
// inside the runJob function that needs to be skipped. sendOutForRescheduling is previously called
|
||||
// when the job is sent to the singleton mode runner.
|
||||
jIn.shouldSendOut = false
|
||||
e.runJob(*j, jIn)
|
||||
}
|
||||
|
||||
@@ -330,15 +358,31 @@ func (e *executor) runJob(j internalJob, jIn jobIn) {
|
||||
default:
|
||||
}
|
||||
|
||||
if j.stopTimeReached(e.clock.Now()) {
|
||||
return
|
||||
}
|
||||
|
||||
if e.elector != nil {
|
||||
if err := e.elector.IsLeader(j.ctx); err != nil {
|
||||
e.sendOutForRescheduling(&jIn)
|
||||
e.incrementJobCounter(j, Skip)
|
||||
return
|
||||
}
|
||||
} else if j.locker != nil {
|
||||
lock, err := j.locker.Lock(j.ctx, j.name)
|
||||
if err != nil {
|
||||
_ = callJobFuncWithParams(j.afterLockError, j.id, j.name, err)
|
||||
e.sendOutForRescheduling(&jIn)
|
||||
e.incrementJobCounter(j, Skip)
|
||||
return
|
||||
}
|
||||
defer func() { _ = lock.Unlock(j.ctx) }()
|
||||
} else if e.locker != nil {
|
||||
lock, err := e.locker.Lock(j.ctx, j.name)
|
||||
if err != nil {
|
||||
_ = callJobFuncWithParams(j.afterLockError, j.id, j.name, err)
|
||||
e.sendOutForRescheduling(&jIn)
|
||||
e.incrementJobCounter(j, Skip)
|
||||
return
|
||||
}
|
||||
defer func() { _ = lock.Unlock(j.ctx) }()
|
||||
@@ -352,20 +396,35 @@ func (e *executor) runJob(j internalJob, jIn jobIn) {
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
err := callJobFuncWithParams(j.function, j.parameters...)
|
||||
err := e.callJobWithRecover(j)
|
||||
if e.monitor != nil {
|
||||
e.monitor.RecordJobTiming(startTime, time.Now(), j.id, j.name, j.tags)
|
||||
}
|
||||
if err != nil {
|
||||
_ = callJobFuncWithParams(j.afterJobRunsWithError, j.id, j.name, err)
|
||||
if e.monitor != nil {
|
||||
e.monitor.IncrementJob(j.id, j.name, j.tags, Fail)
|
||||
}
|
||||
e.incrementJobCounter(j, Fail)
|
||||
} else {
|
||||
_ = callJobFuncWithParams(j.afterJobRuns, j.id, j.name)
|
||||
if e.monitor != nil {
|
||||
e.monitor.IncrementJob(j.id, j.name, j.tags, Success)
|
||||
e.incrementJobCounter(j, Success)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *executor) callJobWithRecover(j internalJob) (err error) {
|
||||
defer func() {
|
||||
if recoverData := recover(); recoverData != nil {
|
||||
_ = callJobFuncWithParams(j.afterJobRunsWithPanic, j.id, j.name, recoverData)
|
||||
|
||||
// if panic is occurred, we should return an error
|
||||
err = fmt.Errorf("%w from %v", ErrPanicRecovered, recoverData)
|
||||
}
|
||||
}()
|
||||
|
||||
return callJobFuncWithParams(j.function, j.parameters...)
|
||||
}
|
||||
|
||||
func (e *executor) incrementJobCounter(j internalJob, status JobStatus) {
|
||||
if e.monitor != nil {
|
||||
e.monitor.IncrementJob(j.id, j.name, j.tags, status)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,4 +507,8 @@ func (e *executor) stop(standardJobsWg, singletonJobsWg, limitModeJobsWg *waitGr
|
||||
e.logger.Debug("gocron: executor stopped")
|
||||
}
|
||||
waiterCancel()
|
||||
|
||||
if e.limitMode != nil {
|
||||
e.limitMode.started = false
|
||||
}
|
||||
}
|
||||
|
||||
258
vendor/github.com/go-co-op/gocron/v2/job.go
generated
vendored
258
vendor/github.com/go-co-op/gocron/v2/job.go
generated
vendored
@@ -24,8 +24,11 @@ type internalJob struct {
|
||||
name string
|
||||
tags []string
|
||||
jobSchedule
|
||||
lastScheduledRun time.Time
|
||||
nextScheduled time.Time
|
||||
|
||||
// as some jobs may queue up, it's possible to
|
||||
// have multiple nextScheduled times
|
||||
nextScheduled []time.Time
|
||||
|
||||
lastRun time.Time
|
||||
function any
|
||||
parameters []any
|
||||
@@ -35,10 +38,15 @@ type internalJob struct {
|
||||
limitRunsTo *limitRunsTo
|
||||
startTime time.Time
|
||||
startImmediately bool
|
||||
stopTime time.Time
|
||||
// event listeners
|
||||
afterJobRuns func(jobID uuid.UUID, jobName string)
|
||||
beforeJobRuns func(jobID uuid.UUID, jobName string)
|
||||
afterJobRunsWithError func(jobID uuid.UUID, jobName string, err error)
|
||||
afterJobRunsWithPanic func(jobID uuid.UUID, jobName string, recoverData any)
|
||||
afterLockError func(jobID uuid.UUID, jobName string, err error)
|
||||
|
||||
locker Locker
|
||||
}
|
||||
|
||||
// stop is used to stop the job's timer and cancel the context
|
||||
@@ -53,6 +61,13 @@ func (j *internalJob) stop() {
|
||||
j.cancel()
|
||||
}
|
||||
|
||||
func (j *internalJob) stopTimeReached(now time.Time) bool {
|
||||
if j.stopTime.IsZero() {
|
||||
return false
|
||||
}
|
||||
return j.stopTime.Before(now)
|
||||
}
|
||||
|
||||
// task stores the function and parameters
|
||||
// that are actually run when the job is executed.
|
||||
type task struct {
|
||||
@@ -91,7 +106,7 @@ type limitRunsTo struct {
|
||||
// JobDefinition defines the interface that must be
|
||||
// implemented to create a job from the definition.
|
||||
type JobDefinition interface {
|
||||
setup(*internalJob, *time.Location) error
|
||||
setup(j *internalJob, l *time.Location, now time.Time) error
|
||||
}
|
||||
|
||||
var _ JobDefinition = (*cronJobDefinition)(nil)
|
||||
@@ -101,7 +116,7 @@ type cronJobDefinition struct {
|
||||
withSeconds bool
|
||||
}
|
||||
|
||||
func (c cronJobDefinition) setup(j *internalJob, location *time.Location) error {
|
||||
func (c cronJobDefinition) setup(j *internalJob, location *time.Location, _ time.Time) error {
|
||||
var withLocation string
|
||||
if strings.HasPrefix(c.crontab, "TZ=") || strings.HasPrefix(c.crontab, "CRON_TZ=") {
|
||||
withLocation = c.crontab
|
||||
@@ -117,7 +132,7 @@ func (c cronJobDefinition) setup(j *internalJob, location *time.Location) error
|
||||
)
|
||||
|
||||
if c.withSeconds {
|
||||
p := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
|
||||
p := cron.NewParser(cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
|
||||
cronSchedule, err = p.Parse(withLocation)
|
||||
} else {
|
||||
cronSchedule, err = cron.ParseStandard(withLocation)
|
||||
@@ -149,7 +164,7 @@ type durationJobDefinition struct {
|
||||
duration time.Duration
|
||||
}
|
||||
|
||||
func (d durationJobDefinition) setup(j *internalJob, _ *time.Location) error {
|
||||
func (d durationJobDefinition) setup(j *internalJob, _ *time.Location, _ time.Time) error {
|
||||
if d.duration == 0 {
|
||||
return ErrDurationJobIntervalZero
|
||||
}
|
||||
@@ -171,7 +186,7 @@ type durationRandomJobDefinition struct {
|
||||
min, max time.Duration
|
||||
}
|
||||
|
||||
func (d durationRandomJobDefinition) setup(j *internalJob, _ *time.Location) error {
|
||||
func (d durationRandomJobDefinition) setup(j *internalJob, _ *time.Location, _ time.Time) error {
|
||||
if d.min >= d.max {
|
||||
return ErrDurationRandomJobMinMax
|
||||
}
|
||||
@@ -221,7 +236,7 @@ type dailyJobDefinition struct {
|
||||
atTimes AtTimes
|
||||
}
|
||||
|
||||
func (d dailyJobDefinition) setup(j *internalJob, location *time.Location) error {
|
||||
func (d dailyJobDefinition) setup(j *internalJob, location *time.Location, _ time.Time) error {
|
||||
atTimesDate, err := convertAtTimesToDateTime(d.atTimes, location)
|
||||
switch {
|
||||
case errors.Is(err, errAtTimesNil):
|
||||
@@ -250,7 +265,7 @@ type weeklyJobDefinition struct {
|
||||
atTimes AtTimes
|
||||
}
|
||||
|
||||
func (w weeklyJobDefinition) setup(j *internalJob, location *time.Location) error {
|
||||
func (w weeklyJobDefinition) setup(j *internalJob, location *time.Location, _ time.Time) error {
|
||||
var ws weeklyJob
|
||||
ws.interval = w.interval
|
||||
|
||||
@@ -286,7 +301,8 @@ type Weekdays func() []time.Weekday
|
||||
// NewWeekdays provide the days of the week the job should run.
|
||||
func NewWeekdays(weekday time.Weekday, weekdays ...time.Weekday) Weekdays {
|
||||
return func() []time.Weekday {
|
||||
return append(weekdays, weekday)
|
||||
weekdays = append(weekdays, weekday)
|
||||
return weekdays
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,7 +330,7 @@ type monthlyJobDefinition struct {
|
||||
atTimes AtTimes
|
||||
}
|
||||
|
||||
func (m monthlyJobDefinition) setup(j *internalJob, location *time.Location) error {
|
||||
func (m monthlyJobDefinition) setup(j *internalJob, location *time.Location, _ time.Time) error {
|
||||
var ms monthlyJob
|
||||
ms.interval = m.interval
|
||||
|
||||
@@ -437,31 +453,46 @@ type oneTimeJobDefinition struct {
|
||||
startAt OneTimeJobStartAtOption
|
||||
}
|
||||
|
||||
func (o oneTimeJobDefinition) setup(j *internalJob, _ *time.Location) error {
|
||||
j.jobSchedule = oneTimeJob{}
|
||||
return o.startAt(j)
|
||||
func (o oneTimeJobDefinition) setup(j *internalJob, _ *time.Location, now time.Time) error {
|
||||
sortedTimes := o.startAt(j)
|
||||
slices.SortStableFunc(sortedTimes, ascendingTime)
|
||||
// keep only schedules that are in the future
|
||||
idx, found := slices.BinarySearchFunc(sortedTimes, now, ascendingTime)
|
||||
if found {
|
||||
idx++
|
||||
}
|
||||
sortedTimes = sortedTimes[idx:]
|
||||
if !j.startImmediately && len(sortedTimes) == 0 {
|
||||
return ErrOneTimeJobStartDateTimePast
|
||||
}
|
||||
j.jobSchedule = oneTimeJob{sortedTimes: sortedTimes}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OneTimeJobStartAtOption defines when the one time job is run
|
||||
type OneTimeJobStartAtOption func(*internalJob) error
|
||||
type OneTimeJobStartAtOption func(*internalJob) []time.Time
|
||||
|
||||
// OneTimeJobStartImmediately tells the scheduler to run the one time job immediately.
|
||||
func OneTimeJobStartImmediately() OneTimeJobStartAtOption {
|
||||
return func(j *internalJob) error {
|
||||
return func(j *internalJob) []time.Time {
|
||||
j.startImmediately = true
|
||||
return nil
|
||||
return []time.Time{}
|
||||
}
|
||||
}
|
||||
|
||||
// OneTimeJobStartDateTime sets the date & time at which the job should run.
|
||||
// This datetime must be in the future.
|
||||
// This datetime must be in the future (according to the scheduler clock).
|
||||
func OneTimeJobStartDateTime(start time.Time) OneTimeJobStartAtOption {
|
||||
return func(j *internalJob) error {
|
||||
if start.IsZero() || start.Before(time.Now()) {
|
||||
return ErrOneTimeJobStartDateTimePast
|
||||
}
|
||||
j.startTime = start
|
||||
return nil
|
||||
return func(_ *internalJob) []time.Time {
|
||||
return []time.Time{start}
|
||||
}
|
||||
}
|
||||
|
||||
// OneTimeJobStartDateTimes sets the date & times at which the job should run.
|
||||
// At least one of the date/times must be in the future (according to the scheduler clock).
|
||||
func OneTimeJobStartDateTimes(times ...time.Time) OneTimeJobStartAtOption {
|
||||
return func(_ *internalJob) []time.Time {
|
||||
return times
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,12 +511,25 @@ func OneTimeJob(startAt OneTimeJobStartAtOption) JobDefinition {
|
||||
// -----------------------------------------------
|
||||
|
||||
// JobOption defines the constructor for job options.
|
||||
type JobOption func(*internalJob) error
|
||||
type JobOption func(*internalJob, time.Time) error
|
||||
|
||||
// WithDistributedJobLocker sets the locker to be used by multiple
|
||||
// Scheduler instances to ensure that only one instance of each
|
||||
// job is run.
|
||||
func WithDistributedJobLocker(locker Locker) JobOption {
|
||||
return func(j *internalJob, _ time.Time) error {
|
||||
if locker == nil {
|
||||
return ErrWithDistributedJobLockerNil
|
||||
}
|
||||
j.locker = locker
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithEventListeners sets the event listeners that should be
|
||||
// run for the job.
|
||||
func WithEventListeners(eventListeners ...EventListener) JobOption {
|
||||
return func(j *internalJob) error {
|
||||
return func(j *internalJob, _ time.Time) error {
|
||||
for _, eventListener := range eventListeners {
|
||||
if err := eventListener(j); err != nil {
|
||||
return err
|
||||
@@ -498,7 +542,7 @@ func WithEventListeners(eventListeners ...EventListener) JobOption {
|
||||
// WithLimitedRuns limits the number of executions of this job to n.
|
||||
// Upon reaching the limit, the job is removed from the scheduler.
|
||||
func WithLimitedRuns(limit uint) JobOption {
|
||||
return func(j *internalJob) error {
|
||||
return func(j *internalJob, _ time.Time) error {
|
||||
j.limitRunsTo = &limitRunsTo{
|
||||
limit: limit,
|
||||
runCount: 0,
|
||||
@@ -510,8 +554,7 @@ func WithLimitedRuns(limit uint) JobOption {
|
||||
// WithName sets the name of the job. Name provides
|
||||
// a human-readable identifier for the job.
|
||||
func WithName(name string) JobOption {
|
||||
// TODO use the name for metrics and future logging option
|
||||
return func(j *internalJob) error {
|
||||
return func(j *internalJob, _ time.Time) error {
|
||||
if name == "" {
|
||||
return ErrWithNameEmpty
|
||||
}
|
||||
@@ -524,7 +567,7 @@ func WithName(name string) JobOption {
|
||||
// This is useful for jobs that should not overlap, and that occasionally
|
||||
// (but not consistently) run longer than the interval between job runs.
|
||||
func WithSingletonMode(mode LimitMode) JobOption {
|
||||
return func(j *internalJob) error {
|
||||
return func(j *internalJob, _ time.Time) error {
|
||||
j.singletonMode = true
|
||||
j.singletonLimitMode = mode
|
||||
return nil
|
||||
@@ -534,19 +577,19 @@ func WithSingletonMode(mode LimitMode) JobOption {
|
||||
// WithStartAt sets the option for starting the job at
|
||||
// a specific datetime.
|
||||
func WithStartAt(option StartAtOption) JobOption {
|
||||
return func(j *internalJob) error {
|
||||
return option(j)
|
||||
return func(j *internalJob, now time.Time) error {
|
||||
return option(j, now)
|
||||
}
|
||||
}
|
||||
|
||||
// StartAtOption defines options for starting the job
|
||||
type StartAtOption func(*internalJob) error
|
||||
type StartAtOption func(*internalJob, time.Time) error
|
||||
|
||||
// WithStartImmediately tells the scheduler to run the job immediately
|
||||
// regardless of the type or schedule of job. After this immediate run
|
||||
// the job is scheduled from this time based on the job definition.
|
||||
func WithStartImmediately() StartAtOption {
|
||||
return func(j *internalJob) error {
|
||||
return func(j *internalJob, _ time.Time) error {
|
||||
j.startImmediately = true
|
||||
return nil
|
||||
}
|
||||
@@ -555,25 +598,69 @@ func WithStartImmediately() StartAtOption {
|
||||
// WithStartDateTime sets the first date & time at which the job should run.
|
||||
// This datetime must be in the future.
|
||||
func WithStartDateTime(start time.Time) StartAtOption {
|
||||
return func(j *internalJob) error {
|
||||
if start.IsZero() || start.Before(time.Now()) {
|
||||
return func(j *internalJob, now time.Time) error {
|
||||
if start.IsZero() || start.Before(now) {
|
||||
return ErrWithStartDateTimePast
|
||||
}
|
||||
if !j.stopTime.IsZero() && j.stopTime.Before(start) {
|
||||
return ErrStartTimeLaterThanEndTime
|
||||
}
|
||||
j.startTime = start
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithStopAt sets the option for stopping the job from running
|
||||
// after the specified time.
|
||||
func WithStopAt(option StopAtOption) JobOption {
|
||||
return func(j *internalJob, now time.Time) error {
|
||||
return option(j, now)
|
||||
}
|
||||
}
|
||||
|
||||
// StopAtOption defines options for stopping the job
|
||||
type StopAtOption func(*internalJob, time.Time) error
|
||||
|
||||
// WithStopDateTime sets the final date & time after which the job should stop.
|
||||
// This must be in the future and should be after the startTime (if specified).
|
||||
// The job's final run may be at the stop time, but not after.
|
||||
func WithStopDateTime(end time.Time) StopAtOption {
|
||||
return func(j *internalJob, now time.Time) error {
|
||||
if end.IsZero() || end.Before(now) {
|
||||
return ErrWithStopDateTimePast
|
||||
}
|
||||
if end.Before(j.startTime) {
|
||||
return ErrStopTimeEarlierThanStartTime
|
||||
}
|
||||
j.stopTime = end
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTags sets the tags for the job. Tags provide
|
||||
// a way to identify jobs by a set of tags and remove
|
||||
// multiple jobs by tag.
|
||||
func WithTags(tags ...string) JobOption {
|
||||
return func(j *internalJob) error {
|
||||
return func(j *internalJob, _ time.Time) error {
|
||||
j.tags = tags
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithIdentifier sets the identifier for the job. The identifier
|
||||
// is used to uniquely identify the job and is used for logging
|
||||
// and metrics.
|
||||
func WithIdentifier(id uuid.UUID) JobOption {
|
||||
return func(j *internalJob, _ time.Time) error {
|
||||
if id == uuid.Nil {
|
||||
return ErrWithIdentifierNil
|
||||
}
|
||||
|
||||
j.id = id
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// -----------------------------------------------
|
||||
// ------------- Job Event Listeners -------------
|
||||
@@ -584,6 +671,18 @@ func WithTags(tags ...string) JobOption {
|
||||
// listeners that can be used to listen for job events.
|
||||
type EventListener func(*internalJob) error
|
||||
|
||||
// BeforeJobRuns is used to listen for when a job is about to run and
|
||||
// then run the provided function.
|
||||
func BeforeJobRuns(eventListenerFunc func(jobID uuid.UUID, jobName string)) EventListener {
|
||||
return func(j *internalJob) error {
|
||||
if eventListenerFunc == nil {
|
||||
return ErrEventListenerFuncNil
|
||||
}
|
||||
j.beforeJobRuns = eventListenerFunc
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AfterJobRuns is used to listen for when a job has run
|
||||
// without an error, and then run the provided function.
|
||||
func AfterJobRuns(eventListenerFunc func(jobID uuid.UUID, jobName string)) EventListener {
|
||||
@@ -608,14 +707,26 @@ func AfterJobRunsWithError(eventListenerFunc func(jobID uuid.UUID, jobName strin
|
||||
}
|
||||
}
|
||||
|
||||
// BeforeJobRuns is used to listen for when a job is about to run and
|
||||
// then run the provided function.
|
||||
func BeforeJobRuns(eventListenerFunc func(jobID uuid.UUID, jobName string)) EventListener {
|
||||
// AfterJobRunsWithPanic is used to listen for when a job has run and
|
||||
// returned panicked recover data, and then run the provided function.
|
||||
func AfterJobRunsWithPanic(eventListenerFunc func(jobID uuid.UUID, jobName string, recoverData any)) EventListener {
|
||||
return func(j *internalJob) error {
|
||||
if eventListenerFunc == nil {
|
||||
return ErrEventListenerFuncNil
|
||||
}
|
||||
j.beforeJobRuns = eventListenerFunc
|
||||
j.afterJobRunsWithPanic = eventListenerFunc
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AfterLockError is used to when the distributed locker returns an error and
|
||||
// then run the provided function.
|
||||
func AfterLockError(eventListenerFunc func(jobID uuid.UUID, jobName string, err error)) EventListener {
|
||||
return func(j *internalJob) error {
|
||||
if eventListenerFunc == nil {
|
||||
return ErrEventListenerFuncNil
|
||||
}
|
||||
j.afterLockError = eventListenerFunc
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -826,10 +937,33 @@ func (m monthlyJob) nextMonthDayAtTime(lastRun time.Time, days []int, firstPass
|
||||
|
||||
var _ jobSchedule = (*oneTimeJob)(nil)
|
||||
|
||||
type oneTimeJob struct{}
|
||||
type oneTimeJob struct {
|
||||
sortedTimes []time.Time
|
||||
}
|
||||
|
||||
func (o oneTimeJob) next(_ time.Time) time.Time {
|
||||
return time.Time{}
|
||||
// next finds the next item in a sorted list of times using binary-search.
|
||||
//
|
||||
// example: sortedTimes: [2, 4, 6, 8]
|
||||
//
|
||||
// lastRun: 1 => [idx=0,found=false] => next is 2 - sorted[idx] idx=0
|
||||
// lastRun: 2 => [idx=0,found=true] => next is 4 - sorted[idx+1] idx=1
|
||||
// lastRun: 3 => [idx=1,found=false] => next is 4 - sorted[idx] idx=1
|
||||
// lastRun: 4 => [idx=1,found=true] => next is 6 - sorted[idx+1] idx=2
|
||||
// lastRun: 7 => [idx=3,found=false] => next is 8 - sorted[idx] idx=3
|
||||
// lastRun: 8 => [idx=3,found=found] => next is none
|
||||
// lastRun: 9 => [idx=3,found=found] => next is none
|
||||
func (o oneTimeJob) next(lastRun time.Time) time.Time {
|
||||
idx, found := slices.BinarySearchFunc(o.sortedTimes, lastRun, ascendingTime)
|
||||
// if found, the next run is the following index
|
||||
if found {
|
||||
idx++
|
||||
}
|
||||
// exhausted runs
|
||||
if idx >= len(o.sortedTimes) {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
return o.sortedTimes[idx]
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
@@ -849,6 +983,8 @@ type Job interface {
|
||||
Name() string
|
||||
// NextRun returns the time of the job's next scheduled run.
|
||||
NextRun() (time.Time, error)
|
||||
// NextRuns returns the requested number of calculated next run values.
|
||||
NextRuns(int) ([]time.Time, error)
|
||||
// RunNow runs the job once, now. This does not alter
|
||||
// the existing run schedule, and will respect all job
|
||||
// and scheduler limits. This means that running a job now may
|
||||
@@ -894,7 +1030,39 @@ func (j job) NextRun() (time.Time, error) {
|
||||
if ij == nil || ij.id == uuid.Nil {
|
||||
return time.Time{}, ErrJobNotFound
|
||||
}
|
||||
return ij.nextScheduled, nil
|
||||
if len(ij.nextScheduled) == 0 {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
// the first element is the next scheduled run with subsequent
|
||||
// runs following after in the slice
|
||||
return ij.nextScheduled[0], nil
|
||||
}
|
||||
|
||||
func (j job) NextRuns(count int) ([]time.Time, error) {
|
||||
ij := requestJob(j.id, j.jobOutRequest)
|
||||
if ij == nil || ij.id == uuid.Nil {
|
||||
return nil, ErrJobNotFound
|
||||
}
|
||||
|
||||
lengthNextScheduled := len(ij.nextScheduled)
|
||||
if lengthNextScheduled == 0 {
|
||||
return nil, nil
|
||||
} else if count <= lengthNextScheduled {
|
||||
return ij.nextScheduled[:count], nil
|
||||
}
|
||||
|
||||
out := make([]time.Time, count)
|
||||
for i := 0; i < count; i++ {
|
||||
if i < lengthNextScheduled {
|
||||
out[i] = ij.nextScheduled[i]
|
||||
continue
|
||||
}
|
||||
|
||||
from := out[i-1]
|
||||
out[i] = ij.next(from)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (j job) Tags() []string {
|
||||
|
||||
1
vendor/github.com/go-co-op/gocron/v2/monitor.go
generated
vendored
1
vendor/github.com/go-co-op/gocron/v2/monitor.go
generated
vendored
@@ -13,6 +13,7 @@ type JobStatus string
|
||||
const (
|
||||
Fail JobStatus = "fail"
|
||||
Success JobStatus = "success"
|
||||
Skip JobStatus = "skip"
|
||||
)
|
||||
|
||||
// Monitor represents the interface to collect jobs metrics.
|
||||
|
||||
225
vendor/github.com/go-co-op/gocron/v2/scheduler.go
generated
vendored
225
vendor/github.com/go-co-op/gocron/v2/scheduler.go
generated
vendored
@@ -44,6 +44,9 @@ type Scheduler interface {
|
||||
// Update replaces the existing Job's JobDefinition with the provided
|
||||
// JobDefinition. The Job's Job.ID() remains the same.
|
||||
Update(uuid.UUID, JobDefinition, Task, ...JobOption) (Job, error)
|
||||
// JobsWaitingInQueue number of jobs waiting in Queue in case of LimitModeWait
|
||||
// In case of LimitModeReschedule or no limit it will be always zero
|
||||
JobsWaitingInQueue() int
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
@@ -53,25 +56,43 @@ type Scheduler interface {
|
||||
// -----------------------------------------------
|
||||
|
||||
type scheduler struct {
|
||||
shutdownCtx context.Context
|
||||
shutdownCancel context.CancelFunc
|
||||
exec executor
|
||||
jobs map[uuid.UUID]internalJob
|
||||
location *time.Location
|
||||
clock clockwork.Clock
|
||||
started bool
|
||||
// context used for shutting down
|
||||
shutdownCtx context.Context
|
||||
// cancel used to signal scheduler should shut down
|
||||
shutdownCancel context.CancelFunc
|
||||
// the executor, which actually runs the jobs sent to it via the scheduler
|
||||
exec executor
|
||||
// the map of jobs registered in the scheduler
|
||||
jobs map[uuid.UUID]internalJob
|
||||
// the location used by the scheduler for scheduling when relevant
|
||||
location *time.Location
|
||||
// whether the scheduler has been started or not
|
||||
started bool
|
||||
// globally applied JobOption's set on all jobs added to the scheduler
|
||||
// note: individually set JobOption's take precedence.
|
||||
globalJobOptions []JobOption
|
||||
logger Logger
|
||||
// the scheduler's logger
|
||||
logger Logger
|
||||
|
||||
startCh chan struct{}
|
||||
startedCh chan struct{}
|
||||
stopCh chan struct{}
|
||||
stopErrCh chan error
|
||||
allJobsOutRequest chan allJobsOutRequest
|
||||
jobOutRequestCh chan jobOutRequest
|
||||
runJobRequestCh chan runJobRequest
|
||||
newJobCh chan newJobIn
|
||||
removeJobCh chan uuid.UUID
|
||||
// used to tell the scheduler to start
|
||||
startCh chan struct{}
|
||||
// used to report that the scheduler has started
|
||||
startedCh chan struct{}
|
||||
// used to tell the scheduler to stop
|
||||
stopCh chan struct{}
|
||||
// used to report that the scheduler has stopped
|
||||
stopErrCh chan error
|
||||
// used to send all the jobs out when a request is made by the client
|
||||
allJobsOutRequest chan allJobsOutRequest
|
||||
// used to send a jobs out when a request is made by the client
|
||||
jobOutRequestCh chan jobOutRequest
|
||||
// used to run a job on-demand when requested by the client
|
||||
runJobRequestCh chan runJobRequest
|
||||
// new jobs are received here
|
||||
newJobCh chan newJobIn
|
||||
// requests from the client to remove jobs by ID are received here
|
||||
removeJobCh chan uuid.UUID
|
||||
// requests from the client to remove jobs by tags are received here
|
||||
removeJobsByTagsCh chan []string
|
||||
}
|
||||
|
||||
@@ -108,6 +129,7 @@ func NewScheduler(options ...SchedulerOption) (Scheduler, error) {
|
||||
stopTimeout: time.Second * 10,
|
||||
singletonRunners: nil,
|
||||
logger: &noOpLogger{},
|
||||
clock: clockwork.NewRealClock(),
|
||||
|
||||
jobsIn: make(chan jobIn),
|
||||
jobsOutForRescheduling: make(chan uuid.UUID),
|
||||
@@ -122,7 +144,6 @@ func NewScheduler(options ...SchedulerOption) (Scheduler, error) {
|
||||
exec: exec,
|
||||
jobs: make(map[uuid.UUID]internalJob),
|
||||
location: time.Local,
|
||||
clock: clockwork.NewRealClock(),
|
||||
logger: &noOpLogger{},
|
||||
|
||||
newJobCh: make(chan newJobIn),
|
||||
@@ -292,20 +313,42 @@ func (s *scheduler) selectRemoveJob(id uuid.UUID) {
|
||||
// Jobs coming back from the executor to the scheduler that
|
||||
// need to evaluated for rescheduling.
|
||||
func (s *scheduler) selectExecJobsOutForRescheduling(id uuid.UUID) {
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
j, ok := s.jobs[id]
|
||||
if !ok {
|
||||
// the job was removed while it was running, and
|
||||
// so we don't need to reschedule it.
|
||||
return
|
||||
}
|
||||
j.lastScheduledRun = j.nextScheduled
|
||||
|
||||
next := j.next(j.lastScheduledRun)
|
||||
if j.stopTimeReached(s.now()) {
|
||||
return
|
||||
}
|
||||
|
||||
scheduleFrom := j.lastRun
|
||||
if len(j.nextScheduled) > 0 {
|
||||
// always grab the last element in the slice as that is the furthest
|
||||
// out in the future and the time from which we want to calculate
|
||||
// the subsequent next run time.
|
||||
slices.SortStableFunc(j.nextScheduled, ascendingTime)
|
||||
scheduleFrom = j.nextScheduled[len(j.nextScheduled)-1]
|
||||
}
|
||||
|
||||
if scheduleFrom.IsZero() {
|
||||
scheduleFrom = j.startTime
|
||||
}
|
||||
|
||||
next := j.next(scheduleFrom)
|
||||
if next.IsZero() {
|
||||
// the job's next function will return zero for OneTime jobs.
|
||||
// since they are one time only, they do not need rescheduling.
|
||||
return
|
||||
}
|
||||
|
||||
if next.Before(s.now()) {
|
||||
// in some cases the next run time can be in the past, for example:
|
||||
// - the time on the machine was incorrect and has been synced with ntp
|
||||
@@ -316,8 +359,8 @@ func (s *scheduler) selectExecJobsOutForRescheduling(id uuid.UUID) {
|
||||
next = j.next(next)
|
||||
}
|
||||
}
|
||||
j.nextScheduled = next
|
||||
j.timer = s.clock.AfterFunc(next.Sub(s.now()), func() {
|
||||
j.nextScheduled = append(j.nextScheduled, next)
|
||||
j.timer = s.exec.clock.AfterFunc(next.Sub(s.now()), func() {
|
||||
// set the actual timer on the job here and listen for
|
||||
// shut down events so that the job doesn't attempt to
|
||||
// run if the scheduler has been shutdown.
|
||||
@@ -340,6 +383,17 @@ func (s *scheduler) selectExecJobsOutCompleted(id uuid.UUID) {
|
||||
return
|
||||
}
|
||||
|
||||
// if the job has nextScheduled time in the past,
|
||||
// we need to remove any that are in the past.
|
||||
var newNextScheduled []time.Time
|
||||
for _, t := range j.nextScheduled {
|
||||
if t.Before(s.now()) {
|
||||
continue
|
||||
}
|
||||
newNextScheduled = append(newNextScheduled, t)
|
||||
}
|
||||
j.nextScheduled = newNextScheduled
|
||||
|
||||
// if the job has a limited number of runs set, we need to
|
||||
// check how many runs have occurred and stop running this
|
||||
// job if it has reached the limit.
|
||||
@@ -390,7 +444,7 @@ func (s *scheduler) selectNewJob(in newJobIn) {
|
||||
}
|
||||
|
||||
id := j.id
|
||||
j.timer = s.clock.AfterFunc(next.Sub(s.now()), func() {
|
||||
j.timer = s.exec.clock.AfterFunc(next.Sub(s.now()), func() {
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
case s.exec.jobsIn <- jobIn{
|
||||
@@ -400,7 +454,8 @@ func (s *scheduler) selectNewJob(in newJobIn) {
|
||||
}
|
||||
})
|
||||
}
|
||||
j.nextScheduled = next
|
||||
j.startTime = next
|
||||
j.nextScheduled = append(j.nextScheduled, next)
|
||||
}
|
||||
|
||||
s.jobs[j.id] = j
|
||||
@@ -441,7 +496,7 @@ func (s *scheduler) selectStart() {
|
||||
}
|
||||
|
||||
jobID := id
|
||||
j.timer = s.clock.AfterFunc(next.Sub(s.now()), func() {
|
||||
j.timer = s.exec.clock.AfterFunc(next.Sub(s.now()), func() {
|
||||
select {
|
||||
case <-s.shutdownCtx.Done():
|
||||
case s.exec.jobsIn <- jobIn{
|
||||
@@ -451,7 +506,8 @@ func (s *scheduler) selectStart() {
|
||||
}
|
||||
})
|
||||
}
|
||||
j.nextScheduled = next
|
||||
j.startTime = next
|
||||
j.nextScheduled = append(j.nextScheduled, next)
|
||||
s.jobs[id] = j
|
||||
}
|
||||
select {
|
||||
@@ -468,7 +524,7 @@ func (s *scheduler) selectStart() {
|
||||
// -----------------------------------------------
|
||||
|
||||
func (s *scheduler) now() time.Time {
|
||||
return s.clock.Now().In(s.location)
|
||||
return s.exec.clock.Now().In(s.location)
|
||||
}
|
||||
|
||||
func (s *scheduler) jobFromInternalJob(in internalJob) job {
|
||||
@@ -501,6 +557,70 @@ func (s *scheduler) NewJob(jobDefinition JobDefinition, task Task, options ...Jo
|
||||
return s.addOrUpdateJob(uuid.Nil, jobDefinition, task, options)
|
||||
}
|
||||
|
||||
func (s *scheduler) verifyInterfaceVariadic(taskFunc reflect.Value, tsk task, variadicStart int) error {
|
||||
ifaceType := taskFunc.Type().In(variadicStart).Elem()
|
||||
for i := variadicStart; i < len(tsk.parameters); i++ {
|
||||
if !reflect.TypeOf(tsk.parameters[i]).Implements(ifaceType) {
|
||||
return ErrNewJobWrongTypeOfParameters
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scheduler) verifyVariadic(taskFunc reflect.Value, tsk task, variadicStart int) error {
|
||||
if err := s.verifyNonVariadic(taskFunc, tsk, variadicStart); err != nil {
|
||||
return err
|
||||
}
|
||||
parameterType := taskFunc.Type().In(variadicStart).Elem().Kind()
|
||||
if parameterType == reflect.Interface {
|
||||
return s.verifyInterfaceVariadic(taskFunc, tsk, variadicStart)
|
||||
}
|
||||
if parameterType == reflect.Pointer {
|
||||
parameterType = reflect.Indirect(reflect.ValueOf(taskFunc.Type().In(variadicStart))).Kind()
|
||||
}
|
||||
|
||||
for i := variadicStart; i < len(tsk.parameters); i++ {
|
||||
argumentType := reflect.TypeOf(tsk.parameters[i]).Kind()
|
||||
if argumentType == reflect.Interface || argumentType == reflect.Pointer {
|
||||
argumentType = reflect.TypeOf(tsk.parameters[i]).Elem().Kind()
|
||||
}
|
||||
if argumentType != parameterType {
|
||||
return ErrNewJobWrongTypeOfParameters
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scheduler) verifyNonVariadic(taskFunc reflect.Value, tsk task, length int) error {
|
||||
for i := 0; i < length; i++ {
|
||||
t1 := reflect.TypeOf(tsk.parameters[i]).Kind()
|
||||
if t1 == reflect.Interface || t1 == reflect.Pointer {
|
||||
t1 = reflect.TypeOf(tsk.parameters[i]).Elem().Kind()
|
||||
}
|
||||
t2 := reflect.New(taskFunc.Type().In(i)).Elem().Kind()
|
||||
if t2 == reflect.Interface || t2 == reflect.Pointer {
|
||||
t2 = reflect.Indirect(reflect.ValueOf(taskFunc.Type().In(i))).Kind()
|
||||
}
|
||||
if t1 != t2 {
|
||||
return ErrNewJobWrongTypeOfParameters
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scheduler) verifyParameterType(taskFunc reflect.Value, tsk task) error {
|
||||
isVariadic := taskFunc.Type().IsVariadic()
|
||||
if isVariadic {
|
||||
variadicStart := taskFunc.Type().NumIn() - 1
|
||||
return s.verifyVariadic(taskFunc, tsk, variadicStart)
|
||||
}
|
||||
expectedParameterLength := taskFunc.Type().NumIn()
|
||||
if len(tsk.parameters) != expectedParameterLength {
|
||||
return ErrNewJobWrongNumberOfParameters
|
||||
}
|
||||
return s.verifyNonVariadic(taskFunc, tsk, expectedParameterLength)
|
||||
}
|
||||
|
||||
func (s *scheduler) addOrUpdateJob(id uuid.UUID, definition JobDefinition, taskWrapper Task, options []JobOption) (Job, error) {
|
||||
j := internalJob{}
|
||||
if id == uuid.Nil {
|
||||
@@ -535,23 +655,8 @@ func (s *scheduler) addOrUpdateJob(id uuid.UUID, definition JobDefinition, taskW
|
||||
return nil, ErrNewJobTaskNotFunc
|
||||
}
|
||||
|
||||
expectedParameterLength := taskFunc.Type().NumIn()
|
||||
if len(tsk.parameters) != expectedParameterLength {
|
||||
return nil, ErrNewJobWrongNumberOfParameters
|
||||
}
|
||||
|
||||
for i := 0; i < expectedParameterLength; i++ {
|
||||
t1 := reflect.TypeOf(tsk.parameters[i]).Kind()
|
||||
if t1 == reflect.Interface || t1 == reflect.Pointer {
|
||||
t1 = reflect.TypeOf(tsk.parameters[i]).Elem().Kind()
|
||||
}
|
||||
t2 := reflect.New(taskFunc.Type().In(i)).Elem().Kind()
|
||||
if t2 == reflect.Interface || t2 == reflect.Pointer {
|
||||
t2 = reflect.Indirect(reflect.ValueOf(taskFunc.Type().In(i))).Kind()
|
||||
}
|
||||
if t1 != t2 {
|
||||
return nil, ErrNewJobWrongTypeOfParameters
|
||||
}
|
||||
if err := s.verifyParameterType(taskFunc, tsk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
j.name = runtime.FuncForPC(taskFunc.Pointer()).Name()
|
||||
@@ -560,19 +665,19 @@ func (s *scheduler) addOrUpdateJob(id uuid.UUID, definition JobDefinition, taskW
|
||||
|
||||
// apply global job options
|
||||
for _, option := range s.globalJobOptions {
|
||||
if err := option(&j); err != nil {
|
||||
if err := option(&j, s.now()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// apply job specific options, which take precedence
|
||||
for _, option := range options {
|
||||
if err := option(&j); err != nil {
|
||||
if err := option(&j, s.now()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := definition.setup(&j, s.location); err != nil {
|
||||
if err := definition.setup(&j, s.location, s.exec.clock.Now()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -591,13 +696,8 @@ func (s *scheduler) addOrUpdateJob(id uuid.UUID, definition JobDefinition, taskW
|
||||
case <-s.shutdownCtx.Done():
|
||||
}
|
||||
|
||||
return &job{
|
||||
id: j.id,
|
||||
name: j.name,
|
||||
tags: slices.Clone(j.tags),
|
||||
jobOutRequest: s.jobOutRequestCh,
|
||||
runJobRequest: s.runJobRequestCh,
|
||||
}, nil
|
||||
out := s.jobFromInternalJob(j)
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (s *scheduler) RemoveByTags(tags ...string) {
|
||||
@@ -656,6 +756,13 @@ func (s *scheduler) Update(id uuid.UUID, jobDefinition JobDefinition, task Task,
|
||||
return s.addOrUpdateJob(id, jobDefinition, task, options)
|
||||
}
|
||||
|
||||
func (s *scheduler) JobsWaitingInQueue() int {
|
||||
if s.exec.limitMode != nil && s.exec.limitMode.mode == LimitModeWait {
|
||||
return len(s.exec.limitMode.in)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// -----------------------------------------------
|
||||
// ------------- Scheduler Options ---------------
|
||||
@@ -673,7 +780,7 @@ func WithClock(clock clockwork.Clock) SchedulerOption {
|
||||
if clock == nil {
|
||||
return ErrWithClockNil
|
||||
}
|
||||
s.clock = clock
|
||||
s.exec.clock = clock
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -756,6 +863,14 @@ const (
|
||||
// WithLimitConcurrentJobs sets the limit and mode to be used by the
|
||||
// Scheduler for limiting the number of jobs that may be running at
|
||||
// a given time.
|
||||
//
|
||||
// Note: the limit mode selected for WithLimitConcurrentJobs takes initial
|
||||
// precedence in the event you are also running a limit mode at the job level
|
||||
// using WithSingletonMode.
|
||||
//
|
||||
// Warning: a single time consuming job can dominate your limit in the event
|
||||
// you are running both the scheduler limit WithLimitConcurrentJobs(1, LimitModeWait)
|
||||
// and a job limit WithSingletonMode(LimitModeReschedule).
|
||||
func WithLimitConcurrentJobs(limit uint, mode LimitMode) SchedulerOption {
|
||||
return func(s *scheduler) error {
|
||||
if limit == 0 {
|
||||
|
||||
8
vendor/github.com/go-co-op/gocron/v2/util.go
generated
vendored
8
vendor/github.com/go-co-op/gocron/v2/util.go
generated
vendored
@@ -88,12 +88,14 @@ func convertAtTimesToDateTime(atTimes AtTimes, location *time.Location) ([]time.
|
||||
}
|
||||
atTimesDate = append(atTimesDate, at.time(location))
|
||||
}
|
||||
slices.SortStableFunc(atTimesDate, func(a, b time.Time) int {
|
||||
return a.Compare(b)
|
||||
})
|
||||
slices.SortStableFunc(atTimesDate, ascendingTime)
|
||||
return atTimesDate, nil
|
||||
}
|
||||
|
||||
func ascendingTime(a, b time.Time) int {
|
||||
return a.Compare(b)
|
||||
}
|
||||
|
||||
type waitGroupWithMutex struct {
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
|
||||
Reference in New Issue
Block a user