Skip to content

Commit 411c90f

Browse files
authored
Allow to download files to a temp dir first (#149)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
1 parent 1552d44 commit 411c90f

File tree

10 files changed

+139
-34
lines changed

10 files changed

+139
-34
lines changed

docs/config/download.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
since: 2019-02-01T18:50:05Z
1616
retry: 3
1717
hideSkipped: false
18+
tempFirst: false
1819
createBaseDir: false
1920
```
2021

@@ -150,6 +151,19 @@ Not display skipped downloads. (default: `false`)
150151
!!! abstract "Environment variables"
151152
* `FTPGRAB_DOWNLOAD_HIDESKIPPED`
152153

154+
## `tempFirst`
155+
156+
First download the files to a temporary location and then move them to the final destination. (default `false`)
157+
158+
!!! example "Config file"
159+
```yaml
160+
download:
161+
tempFirst: false
162+
```
163+
164+
!!! abstract "Environment variables"
165+
* `FTPGRAB_DOWNLOAD_TEMPFIRST`
166+
153167
## `createBaseDir`
154168

155169
Create basename of a FTP source path in the destination folder. This is highly recommended if you have multiple FTP

docs/config/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ You can override this using the [`--config` flag or `CONFIG` env var](../usage/c
5757
since: 2019-02-01T18:50:05Z
5858
retry: 3
5959
hideSkipped: false
60+
tempFirst: false
6061
createBaseDir: false
6162

6263
notif:
@@ -114,6 +115,7 @@ All configuration from file can be transposed into environment variables. As an
114115
since: 2019-02-01T18:50:05Z
115116
retry: 3
116117
hideSkipped: false
118+
tempFirst: false
117119
createBaseDir: false
118120

119121
notif:
@@ -154,6 +156,7 @@ Can be transposed to:
154156
FTPGRAB_DOWNLOAD_SINCE=2019-02-01T18:50:05Z
155157
FTPGRAB_DOWNLOAD_RETRY=3
156158
FTPGRAB_DOWNLOAD_HIDESKIPPED=false
159+
FTPGRAB_DOWNLOAD_TEMPFIRST=false
157160
FTPGRAB_DOWNLOAD_CREATEBASEDIR=false
158161

159162
FTPGRAB_NOTIF_MAIL_HOST=smtp.example.com

internal/config/config_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ func TestLoadFile(t *testing.T) {
6868
SinceTime: time.Date(2019, 2, 1, 18, 50, 05, 0, time.UTC),
6969
Retry: 3,
7070
HideSkipped: utl.NewFalse(),
71+
TempFirst: utl.NewFalse(),
7172
CreateBaseDir: utl.NewFalse(),
7273
},
7374
Notif: &Notif{
@@ -169,6 +170,7 @@ func TestLoadEnv(t *testing.T) {
169170
ChmodDir: 0755,
170171
Retry: 3,
171172
HideSkipped: utl.NewFalse(),
173+
TempFirst: utl.NewFalse(),
172174
CreateBaseDir: utl.NewFalse(),
173175
},
174176
},
@@ -206,6 +208,7 @@ func TestLoadEnv(t *testing.T) {
206208
ChmodDir: 0755,
207209
Retry: 3,
208210
HideSkipped: utl.NewFalse(),
211+
TempFirst: utl.NewFalse(),
209212
CreateBaseDir: utl.NewFalse(),
210213
},
211214
},
@@ -322,6 +325,7 @@ func TestLoadMixed(t *testing.T) {
322325
ChmodDir: 0755,
323326
Retry: 3,
324327
HideSkipped: utl.NewFalse(),
328+
TempFirst: utl.NewFalse(),
325329
CreateBaseDir: utl.NewFalse(),
326330
},
327331
Notif: &Notif{
@@ -373,6 +377,7 @@ func TestLoadMixed(t *testing.T) {
373377
ChmodDir: 0755,
374378
Retry: 3,
375379
HideSkipped: utl.NewTrue(),
380+
TempFirst: utl.NewFalse(),
376381
CreateBaseDir: utl.NewFalse(),
377382
},
378383
Notif: &Notif{

internal/config/download.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type Download struct {
2020
SinceTime time.Time `yaml:"-" json:"-" label:"-" file:"-"`
2121
Retry int `yaml:"retry,omitempty" json:"retry,omitempty"`
2222
HideSkipped *bool `yaml:"hideSkipped,omitempty" json:"hideSkipped,omitempty"`
23+
TempFirst *bool `yaml:"tempFirst,omitempty" json:"tempFirst,omitempty"`
2324
CreateBaseDir *bool `yaml:"createBaseDir,omitempty" json:"createBaseDir,omitempty"`
2425
}
2526

@@ -38,5 +39,6 @@ func (s *Download) SetDefaults() {
3839
s.ChmodDir = 0755
3940
s.Retry = 3
4041
s.HideSkipped = utl.NewFalse()
42+
s.TempFirst = utl.NewFalse()
4143
s.CreateBaseDir = utl.NewFalse()
4244
}

internal/config/fixtures/config.test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ download:
2020
since: 2019-02-01T18:50:05Z
2121
retry: 3
2222
hideSkipped: false
23+
tempFirst: false
2324
createBaseDir: false
2425

2526
notif:

internal/grabber/grabber.go

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ package grabber
22

33
import (
44
"fmt"
5+
"io/ioutil"
56
"os"
67
"path"
7-
"runtime"
88
"time"
99

1010
"github.com/crazy-max/ftpgrab/v7/internal/config"
@@ -21,9 +21,10 @@ import (
2121

2222
// Client represents an active grabber object
2323
type Client struct {
24-
config *config.Download
25-
db *db.Client
26-
server *server.Client
24+
config *config.Download
25+
db *db.Client
26+
server *server.Client
27+
tempdir string
2728
}
2829

2930
// New creates new grabber instance
@@ -49,10 +50,17 @@ func New(dlConfig *config.Download, dbConfig *config.Db, serverConfig *config.Se
4950
return nil, errors.Wrap(err, "Cannot connect to server")
5051
}
5152

53+
// Temp dir to download files
54+
tempdir, err := ioutil.TempDir("", ".ftpgrab.*")
55+
if err != nil {
56+
return nil, errors.Wrap(err, "Cannot create temp dir")
57+
}
58+
5259
return &Client{
53-
config: dlConfig,
54-
db: dbCli,
55-
server: serverCli,
60+
config: dlConfig,
61+
db: dbCli,
62+
server: serverCli,
63+
tempdir: tempdir,
5664
}, nil
5765
}
5866

@@ -115,13 +123,14 @@ func (c *Client) download(file File, retry int) *journal.Entry {
115123
sublogger.Warn().Err(err).Msg("Cannot fix parent folder permissions")
116124
}
117125

118-
destfile, err := os.Create(destpath)
126+
destfile, err := c.createFile(destpath)
119127
if err != nil {
120128
sublogger.Error().Err(err).Msg("Cannot create destination file")
121129
entry.Level = journal.EntryLevelError
122130
entry.Text = fmt.Sprintf("Cannot create destination file: %v", err)
123131
return entry
124132
}
133+
defer destfile.Close()
125134

126135
err = c.server.Retrieve(srcpath, destfile)
127136
if err != nil {
@@ -135,9 +144,31 @@ func (c *Client) download(file File, retry int) *journal.Entry {
135144
return c.download(file, retry)
136145
}
137146
} else {
147+
if err = destfile.Close(); err != nil {
148+
sublogger.Error().Err(err).Msg("Cannot close destination file")
149+
entry.Level = journal.EntryLevelError
150+
entry.Text = fmt.Sprintf("Cannot close destination file: %v", err)
151+
return entry
152+
}
153+
154+
if *c.config.TempFirst {
155+
log.Debug().
156+
Str("tempfile", destfile.Name()).
157+
Str("destfile", destpath).
158+
Msgf("Move temp file")
159+
err := moveFile(destfile.Name(), destpath)
160+
if err != nil {
161+
sublogger.Error().Err(err).Msg("Cannot move file")
162+
entry.Level = journal.EntryLevelError
163+
entry.Text = fmt.Sprintf("Cannot move file: %v", err)
164+
return entry
165+
}
166+
}
167+
138168
sublogger.Info().
139169
Str("duration", time.Since(retrieveStart).Round(time.Millisecond).String()).
140170
Msg("File successfully downloaded")
171+
141172
entry.Level = journal.EntryLevelSuccess
142173
entry.Text = fmt.Sprintf("%s successfully downloaded in %s",
143174
units.HumanSize(float64(file.Info.Size())),
@@ -159,6 +190,22 @@ func (c *Client) download(file File, retry int) *journal.Entry {
159190
return entry
160191
}
161192

193+
func (c *Client) createFile(filename string) (*os.File, error) {
194+
if *c.config.TempFirst {
195+
tempfile, err := ioutil.TempFile(c.tempdir, path.Base(filename))
196+
if err != nil {
197+
return nil, err
198+
}
199+
return tempfile, nil
200+
}
201+
202+
destfile, err := os.Create(filename)
203+
if err != nil {
204+
return nil, err
205+
}
206+
return destfile, nil
207+
}
208+
162209
func (c *Client) getStatus(file File) journal.EntryStatus {
163210
if !c.isIncluded(file) {
164211
return journal.EntryStatusNotIncluded
@@ -201,32 +248,6 @@ func (c *Client) isExcluded(file File) bool {
201248
return false
202249
}
203250

204-
func (c *Client) fixPerms(filepath string) error {
205-
if runtime.GOOS == "windows" {
206-
return nil
207-
}
208-
209-
fileinfo, err := os.Stat(filepath)
210-
if err != nil {
211-
return err
212-
}
213-
214-
chmod := os.FileMode(c.config.ChmodFile)
215-
if fileinfo.IsDir() {
216-
chmod = os.FileMode(c.config.ChmodDir)
217-
}
218-
219-
if err := os.Chmod(filepath, chmod); err != nil {
220-
return err
221-
}
222-
223-
if err := os.Chown(filepath, c.config.UID, c.config.GID); err != nil {
224-
return err
225-
}
226-
227-
return nil
228-
}
229-
230251
// Close closes grabber
231252
func (c *Client) Close() {
232253
if err := c.db.Close(); err != nil {
@@ -235,4 +256,7 @@ func (c *Client) Close() {
235256
if err := c.server.Close(); err != nil {
236257
log.Warn().Err(err).Msg("Cannot close server connection")
237258
}
259+
if err := os.RemoveAll(c.tempdir); err != nil {
260+
log.Warn().Err(err).Msg("Cannot remove temp folder")
261+
}
238262
}

internal/grabber/move.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// +build !windows
2+
3+
package grabber
4+
5+
import (
6+
"os"
7+
)
8+
9+
func moveFile(oldpath, newpath string) error {
10+
return os.Rename(oldpath, newpath)
11+
}

internal/grabber/move_windows.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package grabber
2+
3+
import "syscall"
4+
5+
func moveFile(oldpath, newpath string) error {
6+
from, err := syscall.UTF16PtrFromString(oldpath)
7+
if err != nil {
8+
return err
9+
}
10+
to, err := syscall.UTF16PtrFromString(newpath)
11+
if err != nil {
12+
return err
13+
}
14+
return syscall.MoveFile(from, to)
15+
}

internal/grabber/perm.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// +build !windows
2+
3+
package grabber
4+
5+
import (
6+
"os"
7+
)
8+
9+
func (c *Client) fixPerms(filepath string) error {
10+
fileinfo, err := os.Stat(filepath)
11+
if err != nil {
12+
return err
13+
}
14+
15+
chmod := os.FileMode(c.config.ChmodFile)
16+
if fileinfo.IsDir() {
17+
chmod = os.FileMode(c.config.ChmodDir)
18+
}
19+
20+
if err = os.Chmod(filepath, chmod); err != nil {
21+
return err
22+
}
23+
24+
return os.Chown(filepath, c.config.UID, c.config.GID)
25+
}

internal/grabber/perm_windows.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package grabber
2+
3+
func (c *Client) fixPerms(filepath string) error {
4+
return nil
5+
}

0 commit comments

Comments
 (0)