2020-01-06 14:32:15 +01:00
|
|
|
/*
|
2020-01-12 14:00:56 +01:00
|
|
|
Package fs provides filesystem related constants and functions.
|
2020-01-06 14:32:15 +01:00
|
|
|
|
2023-01-11 16:43:01 +01:00
|
|
|
Copyright (c) 2018 - 2023 PhotoPrism UG. All rights reserved.
|
2020-06-23 13:44:14 +02:00
|
|
|
|
2022-08-10 16:09:21 +02:00
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under Version 3 of the GNU Affero General Public License (the "AGPL"):
|
|
|
|
<https://docs.photoprism.app/license/agpl>
|
2020-06-23 13:44:14 +02:00
|
|
|
|
2022-08-10 16:09:21 +02:00
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU Affero General Public License for more details.
|
2020-06-23 13:44:14 +02:00
|
|
|
|
2022-08-10 16:09:21 +02:00
|
|
|
The AGPL is supplemented by our Trademark and Brand Guidelines,
|
|
|
|
which describe how our Brand Assets may be used:
|
2023-02-08 09:07:42 +01:00
|
|
|
<https://www.photoprism.app/trademark>
|
2020-06-23 13:44:14 +02:00
|
|
|
|
2022-04-13 22:17:59 +02:00
|
|
|
Feel free to send an email to hello@photoprism.app if you have questions,
|
2020-06-23 13:44:14 +02:00
|
|
|
want to support our work, or just want to say hello.
|
|
|
|
|
2020-01-06 14:32:15 +01:00
|
|
|
Additional information can be found in our Developer Guide:
|
2022-02-27 17:32:54 +01:00
|
|
|
<https://docs.photoprism.app/developer-guide/>
|
2020-01-06 14:32:15 +01:00
|
|
|
*/
|
2020-01-12 14:00:56 +01:00
|
|
|
package fs
|
2018-11-17 06:21:39 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"archive/zip"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"os/user"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2022-07-21 20:23:00 +02:00
|
|
|
"syscall"
|
2018-11-17 06:21:39 +01:00
|
|
|
)
|
|
|
|
|
2020-12-26 18:06:54 +01:00
|
|
|
var ignoreCase bool
|
|
|
|
|
2020-05-19 11:00:17 +02:00
|
|
|
const IgnoreFile = ".ppignore"
|
|
|
|
const HiddenPath = ".photoprism"
|
2020-10-19 09:52:52 +02:00
|
|
|
const PathSeparator = string(filepath.Separator)
|
|
|
|
const Home = "~"
|
|
|
|
const HomePath = Home + PathSeparator
|
2020-05-19 11:00:17 +02:00
|
|
|
|
2020-01-31 15:29:06 +01:00
|
|
|
// FileExists returns true if file exists and is not a directory.
|
2020-05-06 10:35:41 +02:00
|
|
|
func FileExists(fileName string) bool {
|
|
|
|
if fileName == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
info, err := os.Stat(fileName)
|
2018-11-17 06:21:39 +01:00
|
|
|
|
|
|
|
return err == nil && !info.IsDir()
|
|
|
|
}
|
|
|
|
|
2022-04-18 17:21:31 +02:00
|
|
|
// FileExistsNotEmpty returns true if file exists, is not a directory, and not empty.
|
|
|
|
func FileExistsNotEmpty(fileName string) bool {
|
|
|
|
if fileName == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
info, err := os.Stat(fileName)
|
|
|
|
|
|
|
|
return err == nil && !info.IsDir() && info.Size() > 0
|
|
|
|
}
|
|
|
|
|
2020-12-04 11:37:31 +01:00
|
|
|
// PathExists tests if a path exists, and is a directory or symlink.
|
2020-01-31 15:29:06 +01:00
|
|
|
func PathExists(path string) bool {
|
2020-12-04 11:02:19 +01:00
|
|
|
if path == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-01-31 15:29:06 +01:00
|
|
|
info, err := os.Stat(path)
|
|
|
|
|
2020-03-23 20:55:23 +01:00
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
m := info.Mode()
|
|
|
|
|
|
|
|
return m&os.ModeDir != 0 || m&os.ModeSymlink != 0
|
2020-01-31 15:29:06 +01:00
|
|
|
}
|
|
|
|
|
2022-07-21 20:23:00 +02:00
|
|
|
// Writable checks if the path is accessible for reading and writing.
|
|
|
|
func Writable(path string) bool {
|
|
|
|
if path == "" {
|
2020-12-04 11:37:31 +01:00
|
|
|
return false
|
|
|
|
}
|
2022-07-21 20:23:00 +02:00
|
|
|
return syscall.Access(path, syscall.O_RDWR) == nil
|
|
|
|
}
|
2020-12-04 11:37:31 +01:00
|
|
|
|
2022-07-21 20:23:00 +02:00
|
|
|
// PathWritable tests if a path exists and is writable.
|
|
|
|
func PathWritable(path string) bool {
|
|
|
|
if !PathExists(path) {
|
2020-12-04 11:37:31 +01:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2022-07-21 20:23:00 +02:00
|
|
|
return Writable(path)
|
2020-12-04 11:37:31 +01:00
|
|
|
}
|
|
|
|
|
2019-06-20 01:11:03 +02:00
|
|
|
// Overwrite overwrites the file with data. Creates file if not present.
|
|
|
|
func Overwrite(fileName string, data []byte) bool {
|
|
|
|
f, err := os.Create(fileName)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = f.Write(data)
|
|
|
|
return err == nil
|
|
|
|
}
|
|
|
|
|
2020-01-31 15:29:06 +01:00
|
|
|
// Abs returns the full path of a file or directory, "~" is replaced with home.
|
|
|
|
func Abs(name string) string {
|
|
|
|
if name == "" {
|
2019-05-04 05:25:00 +02:00
|
|
|
return ""
|
2018-11-17 06:21:39 +01:00
|
|
|
}
|
|
|
|
|
2020-10-19 09:52:52 +02:00
|
|
|
if len(name) > 2 && name[:2] == HomePath {
|
2019-01-03 05:45:54 +01:00
|
|
|
if usr, err := user.Current(); err == nil {
|
2020-01-31 15:29:06 +01:00
|
|
|
name = filepath.Join(usr.HomeDir, name[2:])
|
2019-01-03 05:45:54 +01:00
|
|
|
}
|
2018-11-17 06:21:39 +01:00
|
|
|
}
|
|
|
|
|
2020-01-31 15:29:06 +01:00
|
|
|
result, err := filepath.Abs(name)
|
2019-01-03 05:45:54 +01:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2018-11-17 06:21:39 +01:00
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2019-01-13 00:45:22 +01:00
|
|
|
// copyToFile copies the zip file to destination
|
|
|
|
// if the zip file is a directory, a directory is created at the destination.
|
|
|
|
func copyToFile(f *zip.File, dest string) (fileName string, err error) {
|
|
|
|
rc, err := f.Open()
|
|
|
|
if err != nil {
|
|
|
|
return fileName, err
|
|
|
|
}
|
2018-11-17 06:21:39 +01:00
|
|
|
|
2019-01-13 00:45:22 +01:00
|
|
|
defer rc.Close()
|
2018-11-17 06:21:39 +01:00
|
|
|
|
2019-01-13 00:45:22 +01:00
|
|
|
// Store filename/path for returning and using later on
|
|
|
|
fileName = filepath.Join(dest, f.Name)
|
2018-11-17 06:21:39 +01:00
|
|
|
|
2019-01-13 00:45:22 +01:00
|
|
|
if f.FileInfo().IsDir() {
|
|
|
|
// Make Folder
|
2022-10-31 15:01:48 +01:00
|
|
|
return fileName, os.MkdirAll(fileName, ModeDir)
|
2019-01-13 00:45:22 +01:00
|
|
|
}
|
2018-11-17 06:21:39 +01:00
|
|
|
|
2019-01-13 00:45:22 +01:00
|
|
|
// Make File
|
|
|
|
var fdir string
|
2020-10-19 09:52:52 +02:00
|
|
|
|
2019-01-13 00:45:22 +01:00
|
|
|
if lastIndex := strings.LastIndex(fileName, string(os.PathSeparator)); lastIndex > -1 {
|
|
|
|
fdir = fileName[:lastIndex]
|
|
|
|
}
|
2018-11-17 06:21:39 +01:00
|
|
|
|
2022-10-31 15:01:48 +01:00
|
|
|
err = os.MkdirAll(fdir, ModeDir)
|
2019-01-13 00:45:22 +01:00
|
|
|
if err != nil {
|
|
|
|
return fileName, err
|
|
|
|
}
|
2018-11-17 06:21:39 +01:00
|
|
|
|
2019-01-13 00:45:22 +01:00
|
|
|
fd, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
|
|
|
if err != nil {
|
|
|
|
return fileName, err
|
2018-11-17 06:21:39 +01:00
|
|
|
}
|
|
|
|
|
2019-01-13 00:45:22 +01:00
|
|
|
defer fd.Close()
|
2020-10-19 09:52:52 +02:00
|
|
|
|
2019-01-13 00:45:22 +01:00
|
|
|
_, err = io.Copy(fd, rc)
|
|
|
|
if err != nil {
|
|
|
|
return fileName, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return fileName, nil
|
2018-11-17 06:21:39 +01:00
|
|
|
}
|
|
|
|
|
2020-01-12 14:00:56 +01:00
|
|
|
// Download downloads a file from a URL.
|
2019-01-13 00:45:22 +01:00
|
|
|
func Download(filepath string, url string) error {
|
2022-10-31 15:01:48 +01:00
|
|
|
os.MkdirAll("/tmp/photoprism", ModeDir)
|
2018-11-17 06:21:39 +01:00
|
|
|
|
|
|
|
// Create the file
|
|
|
|
out, err := os.Create(filepath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-10-19 09:52:52 +02:00
|
|
|
|
2018-11-17 06:21:39 +01:00
|
|
|
defer out.Close()
|
|
|
|
|
|
|
|
// Get the data
|
|
|
|
resp, err := http.Get(url)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-10-19 09:52:52 +02:00
|
|
|
|
2018-11-17 06:21:39 +01:00
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
// Check server response
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return fmt.Errorf("bad status: %s", resp.Status)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Writer the body to file
|
|
|
|
_, err = io.Copy(out, resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2019-05-13 18:01:50 +02:00
|
|
|
|
2022-08-01 17:08:46 +02:00
|
|
|
// DirIsEmpty returns true if a directory is empty.
|
|
|
|
func DirIsEmpty(path string) bool {
|
2019-05-13 18:01:50 +02:00
|
|
|
f, err := os.Open(path)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
_, err = f.Readdirnames(1)
|
|
|
|
|
|
|
|
if err == io.EOF {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|