024b11b1e8
* refactor: separate resolving tests * feat: use pubgrub to resolve dependencies * feat: show friendly mod name in error message * feat: show single version in error message when only one matches * ci: update go version to match go.mod * feat: format FactoryGame incompatibility and term * chore: fetch all necessary data of the version at once * chore: upgrade pubgrub * chore: upgrade pubgrub * ci: update golangci-lint version for go 1.21 * chore: lint * chore: update go version in readme
195 lines
4.3 KiB
Go
195 lines
4.3 KiB
Go
package disk
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/jlaffaye/ftp"
|
|
"github.com/pkg/errors"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
var _ Disk = (*ftpDisk)(nil)
|
|
|
|
type ftpDisk struct {
|
|
client *ftp.ServerConn
|
|
path string
|
|
stepLock sync.Mutex
|
|
}
|
|
|
|
type ftpEntry struct {
|
|
*ftp.Entry
|
|
}
|
|
|
|
func (f ftpEntry) IsDir() bool {
|
|
return f.Entry.Type == ftp.EntryTypeFolder
|
|
}
|
|
|
|
func (f ftpEntry) Name() string {
|
|
return f.Entry.Name
|
|
}
|
|
|
|
func newFTP(path string) (Disk, error) {
|
|
u, err := url.Parse(path)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to parse ftp url")
|
|
}
|
|
|
|
c, err := ftp.Dial(u.Host, ftp.DialWithTimeout(time.Second*5))
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to dial host "+u.Host)
|
|
}
|
|
|
|
password, _ := u.User.Password()
|
|
if err := c.Login(u.User.Username(), password); err != nil {
|
|
return nil, errors.Wrap(err, "failed to login")
|
|
}
|
|
|
|
log.Debug().Msg("logged into ftp")
|
|
|
|
return &ftpDisk{
|
|
path: u.Path,
|
|
client: c,
|
|
}, nil
|
|
}
|
|
|
|
func (l *ftpDisk) Exists(path string) error {
|
|
l.stepLock.Lock()
|
|
defer l.stepLock.Unlock()
|
|
|
|
log.Debug().Str("path", path).Str("schema", "ftp").Msg("checking if file exists")
|
|
_, err := l.client.FileSize(path)
|
|
return errors.Wrap(err, "failed to check if file exists")
|
|
}
|
|
|
|
func (l *ftpDisk) Read(path string) ([]byte, error) {
|
|
l.stepLock.Lock()
|
|
defer l.stepLock.Unlock()
|
|
|
|
log.Debug().Str("path", path).Str("schema", "ftp").Msg("reading file")
|
|
|
|
f, err := l.client.Retr(path)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to retrieve path")
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
data, err := io.ReadAll(f)
|
|
return data, errors.Wrap(err, "failed to read file")
|
|
}
|
|
|
|
func (l *ftpDisk) Write(path string, data []byte) error {
|
|
l.stepLock.Lock()
|
|
defer l.stepLock.Unlock()
|
|
|
|
log.Debug().Str("path", path).Str("schema", "ftp").Msg("writing to file")
|
|
return errors.Wrap(l.client.Stor(path, bytes.NewReader(data)), "failed to write file")
|
|
}
|
|
|
|
func (l *ftpDisk) Remove(path string) error {
|
|
l.stepLock.Lock()
|
|
defer l.stepLock.Unlock()
|
|
|
|
log.Debug().Str("path", path).Str("schema", "ftp").Msg("deleting path")
|
|
return errors.Wrap(l.client.Delete(path), "failed to delete path")
|
|
}
|
|
|
|
func (l *ftpDisk) MkDir(path string) error {
|
|
l.stepLock.Lock()
|
|
defer l.stepLock.Unlock()
|
|
|
|
log.Debug().Str("schema", "ftp").Msg("going to root directory")
|
|
err := l.client.ChangeDir("/")
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to change directory")
|
|
}
|
|
|
|
split := strings.Split(path[1:], "/")
|
|
for _, s := range split {
|
|
dir, err := l.ReadDirLock("", false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
foundDir := false
|
|
for _, entry := range dir {
|
|
if entry.IsDir() && entry.Name() == s {
|
|
foundDir = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !foundDir {
|
|
log.Debug().Str("dir", s).Str("schema", "ftp").Msg("making directory")
|
|
if err := l.client.MakeDir(s); err != nil {
|
|
return errors.Wrap(err, "failed to make directory")
|
|
}
|
|
}
|
|
|
|
log.Debug().Str("dir", s).Str("schema", "ftp").Msg("entering directory")
|
|
if err := l.client.ChangeDir(s); err != nil {
|
|
return errors.Wrap(err, "failed to enter directory")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l *ftpDisk) ReadDir(path string) ([]Entry, error) {
|
|
return l.ReadDirLock(path, true)
|
|
}
|
|
|
|
func (l *ftpDisk) ReadDirLock(path string, lock bool) ([]Entry, error) {
|
|
if lock {
|
|
l.stepLock.Lock()
|
|
defer l.stepLock.Unlock()
|
|
}
|
|
|
|
log.Debug().Str("path", path).Str("schema", "ftp").Msg("reading directory")
|
|
|
|
dir, err := l.client.List(path)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to list files in directory")
|
|
}
|
|
|
|
entries := make([]Entry, len(dir))
|
|
for i, entry := range dir {
|
|
entries[i] = ftpEntry{
|
|
Entry: entry,
|
|
}
|
|
}
|
|
|
|
return entries, nil
|
|
}
|
|
|
|
func (l *ftpDisk) IsNotExist(err error) bool {
|
|
return strings.Contains(err.Error(), "Could not get file") || strings.Contains(err.Error(), "Failed to open file")
|
|
}
|
|
|
|
func (l *ftpDisk) IsExist(err error) bool {
|
|
return strings.Contains(err.Error(), "Create directory operation failed")
|
|
}
|
|
|
|
func (l *ftpDisk) Open(path string, _ int) (io.WriteCloser, error) {
|
|
reader, writer := io.Pipe()
|
|
|
|
log.Debug().Str("path", path).Str("schema", "ftp").Msg("opening for writing")
|
|
|
|
go func() {
|
|
l.stepLock.Lock()
|
|
defer l.stepLock.Unlock()
|
|
|
|
err := l.client.Stor(path, reader)
|
|
if err != nil {
|
|
log.Err(err).Msg("failed to store file")
|
|
}
|
|
log.Debug().Str("path", path).Str("schema", "ftp").Msg("write success")
|
|
}()
|
|
|
|
return writer, nil
|
|
}
|