ftpsync 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 2689b1b4483dd343c5134cf3b2ab3d9d4df23370
4
- data.tar.gz: c41e14145fb02388f9debb57851cb5c1344a393b
2
+ SHA256:
3
+ metadata.gz: 0ffec10f36bb727a2bf78b5f132b52c9e9ecf37b0f6e93e6af3fc0666135c4a1
4
+ data.tar.gz: 135414d933aa32bd53aab2326f07adbf91003a02687bb33e3e3b554c11448232
5
5
  SHA512:
6
- metadata.gz: 44611aafdd803254a9ee6d2eaacbbb04185642f54135d5d972c5ab2fd6a938cc2472adeac2578124bf59c7f46a5b30c4d4918b3fb5d772ee592f1505af4044db
7
- data.tar.gz: 040b442974299f24ed692a788804951c18e270891508830e794cfe6b37a3ad0d5b95ce086d61749e75f1e62c1b33701a207d778e57bf3c4ed6da6cb6da492b72
6
+ metadata.gz: 1e4d8864990232bd31db836e22f8afa6deacecac19a548d8575ea5d4e326cbd39c5a215df63889643f0ffc616daed52c968334a5de0ed9215e0d26df4a99de47
7
+ data.tar.gz: b8f91ae8839dab6182e94441096589f12ecc9341a1dd1e62ad9d5e23971bb9acead7d5b2da364e5170e6dc6feec3f08515c030ac35096293aab0e00f193e9d29
data/.rubocop.yml ADDED
@@ -0,0 +1,16 @@
1
+ plugins:
2
+ - rubocop-minitest
3
+ - rubocop-rake
4
+
5
+ AllCops:
6
+ NewCops: enable
7
+ TargetRubyVersion: 2.6
8
+
9
+ Metrics/AbcSize:
10
+ Max: 20
11
+
12
+ Metrics/ClassLength:
13
+ Max: 200
14
+
15
+ Metrics/MethodLength:
16
+ Max: 20
data/Gemfile CHANGED
@@ -1,4 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in ftpsync.gemspec
4
6
  gemspec
7
+
8
+ group :development, :test do
9
+ gem 'minitest', '~> 5.0'
10
+ gem 'rake', '~> 13.0'
11
+ gem 'rubocop', '~> 1.0'
12
+ gem 'rubocop-minitest', '~> 0.40'
13
+ gem 'rubocop-rake', '~> 0.7'
14
+ end
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # FtpSync
2
2
 
3
+ [![CI](https://github.com/Quintasan/ftpsync/actions/workflows/ci.yml/badge.svg)](https://github.com/Quintasan/ftpsync/actions/workflows/ci.yml)
4
+
3
5
  A simple library for synchronizing from/to FTP servers.
4
6
 
5
7
  ## Installation
@@ -25,22 +27,80 @@ example = FtpSync::Simple.new "ftp.example.com", "username", "password"
25
27
  example.pull_dir('remotepath', 'localpath')
26
28
  ```
27
29
 
30
+ ### Options
31
+
32
+ `FtpSync::Simple#new` accepts an options hash:
33
+
34
+ | Option | Default | Description |
35
+ |------------|---------|------------------------------------|
36
+ | `:port` | `21` | FTP port to connect to |
37
+ | `:passive` | `false` | Use passive mode |
38
+ | `:verbose` | `false` | Log progress to stderr |
39
+
40
+ `FtpSync::Simple#pull_dir` accepts an options hash:
41
+
42
+ | Option | Description |
43
+ |----------------|------------------------------------------------------------|
44
+ | `:since` | Skip files that already exist locally and are up to date |
45
+ | `:skip_errors` | Rescue `Net::FTPPermError` and continue instead of raising |
46
+
47
+ > **Note:** `:since` compares size and the mtime reported by the server's
48
+ > `LIST` output, which usually has minute precision. A remote file of the
49
+ > same size that was modified within the same minute as the local copy may
50
+ > therefore be skipped.
51
+
52
+ Passing a block to `pull_dir` invokes it with the local path of each
53
+ downloaded file:
54
+
55
+ ```ruby
56
+ example.pull_dir('remotepath', 'localpath', since: true) do |file|
57
+ process(file)
58
+ end
59
+ ```
60
+
28
61
  ## Development
29
62
 
30
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
63
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake` to run the tests and RuboCop (or `rake test` / `rake rubocop` individually). You can also run `bin/console` for an interactive prompt that will allow you to experiment.
31
64
 
32
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
65
+ ### Integration tests (requires Docker)
33
66
 
34
- ## Contributing
67
+ The integration suite runs against a real FTP server
68
+ ([garethflowers/ftp-server](https://garethflowers.dev/docker-ftp-server/),
69
+ vsftpd) in a Docker container:
70
+
71
+ $ rake test:integration
72
+
73
+ The container listens on `127.0.0.1:2121` (control) and
74
+ `127.0.0.1:40000-40009` (passive data). The tests are skipped automatically
75
+ if Docker is not available, and the container is created and removed for
76
+ each run. Override the credentials and control port with the
77
+ `FTPSYNC_TEST_USER`, `FTPSYNC_TEST_PASS` and `FTPSYNC_TEST_FTP_PORT`
78
+ environment variables.
79
+
80
+ To install this gem onto your local machine, run `bundle exec rake install`.
81
+
82
+ ### Releasing
35
83
 
36
- Bug reports and pull requests are welcome on GitHub at https://github.com/quintasan/ftpsync. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
84
+ Releases are published to [rubygems.org](https://rubygems.org) with
85
+ [trusted publishing](https://guides.rubygems.org/trusted-publishing/),
86
+ so no API key is needed. The `.github/workflows/publish.yml` workflow runs
87
+ the tests and RuboCop, builds the gem, and pushes it whenever you push a
88
+ `v*` tag.
37
89
 
90
+ 1. Bump the version in `lib/ftpsync/version.rb` and commit it.
91
+ 2. Create and push a tag:
92
+
93
+ $ git tag v0.1.0
94
+ $ git push --tags
95
+
96
+ ## Contributing
97
+
98
+ Bug reports and pull requests are welcome on GitHub at https://github.com/quintasan/ftpsync. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](https://www.contributor-covenant.org) code of conduct.
38
99
 
39
100
  ## License
40
101
 
41
102
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
42
103
 
43
-
44
104
  ## Credits
45
105
 
46
106
  This gem is a basically stripped down version of [orlando/ftp_sync](https://github.com/orlando/ftp_sync) which I couldn't get to work.
data/Rakefile CHANGED
@@ -1,10 +1,22 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+ require 'rubocop/rake_task'
3
6
 
4
7
  Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList['test/**/*_test.rb']
8
+ t.libs << 'test'
9
+ t.libs << 'lib'
10
+ t.test_files = FileList['test/*_test.rb']
11
+ end
12
+
13
+ desc 'Run integration tests against a Dockerized FTP server'
14
+ Rake::TestTask.new(:'test:integration') do |t|
15
+ t.libs << 'test'
16
+ t.libs << 'lib'
17
+ t.test_files = FileList['test/integration/**/*_test.rb']
8
18
  end
9
19
 
10
- task :default => :test
20
+ RuboCop::RakeTask.new
21
+
22
+ task default: %i[test rubocop]
data/bin/console CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "ftpsync"
4
+ require 'bundler/setup'
5
+ require 'ftpsync'
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
7
8
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +11,5 @@ require "ftpsync"
10
11
  # require "pry"
11
12
  # Pry.start
12
13
 
13
- require "irb"
14
+ require 'irb'
14
15
  IRB.start
data/ftpsync.gemspec CHANGED
@@ -1,26 +1,32 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'ftpsync/version'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/ftpsync/version'
5
4
 
6
5
  Gem::Specification.new do |spec|
7
- spec.name = "ftpsync"
8
- spec.version = FtpSync::VERSION
9
- spec.authors = ["Michał Zając"]
10
- spec.email = ["rubygems.org@quintasan.pl"]
6
+ spec.name = 'ftpsync'
7
+ spec.version = FtpSync::VERSION
8
+ spec.authors = ['Michał Zając']
9
+ spec.email = ['rubygems.org@quintasan.pl']
11
10
 
12
- spec.summary = %q{A simple library for synchronizing from/to FTP servers.}
13
- spec.homepage = "https://github.com/Quintasan/ftpsync"
14
- spec.license = "MIT"
11
+ spec.summary = 'A simple library for synchronizing from/to FTP servers.'
12
+ spec.description = 'Recursively pull directory trees from FTP servers, with support ' \
13
+ 'for incremental syncs and per-file callbacks.'
14
+ spec.homepage = 'https://github.com/Quintasan/ftpsync'
15
+ spec.license = 'MIT'
16
+ spec.required_ruby_version = '>= 2.6.0'
15
17
 
16
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
- spec.bindir = "exe"
18
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
- spec.require_paths = ["lib"]
18
+ spec.metadata = {
19
+ 'source_code_uri' => spec.homepage,
20
+ 'rubygems_mfa_required' => 'true'
21
+ }
20
22
 
21
- spec.add_dependency "net-ftp-list", ">= 3.2.8"
23
+ spec.files = Dir.chdir(__dir__) do
24
+ `git ls-files -z`.split("\x0").reject do |f|
25
+ f.match?(%r{^(test|spec|features)/|^\.github/}) || !File.file?(f)
26
+ end
27
+ end
28
+ spec.require_paths = ['lib']
22
29
 
23
- spec.add_development_dependency "bundler", "~> 1.10"
24
- spec.add_development_dependency "rake", "~> 10.0"
25
- spec.add_development_dependency "minitest"
30
+ spec.add_dependency 'net-ftp'
31
+ spec.add_dependency 'net-ftp-list', '>= 3.2.8'
26
32
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FtpSync
2
- VERSION = "0.0.3"
4
+ VERSION = '0.0.5'
3
5
  end
data/lib/ftpsync.rb CHANGED
@@ -1,92 +1,122 @@
1
- require "ftpsync/version"
2
- require "net/ftp"
3
- require "net/ftp/list"
1
+ # frozen_string_literal: true
2
+
4
3
  require 'fileutils'
4
+ require 'ftpsync/version'
5
+ require 'net/ftp'
6
+ require 'net/ftp/list'
5
7
 
6
8
  module FtpSync
9
+ # Synchronizes directories against an FTP server.
7
10
  class Simple
8
11
  attr_accessor :server, :port, :username, :password, :passive
9
12
 
10
13
  def initialize(server, username, password, options = {})
11
14
  @server = server
12
- @port = options[:port] || 21
15
+ @port = options.fetch(:port, 21)
13
16
  @username = username
14
17
  @password = password
15
- @passive = options[:passive] || false
16
- @verbose = options[:verbose] || false
18
+ @passive = options.fetch(:passive, false)
19
+ @verbose = options.fetch(:verbose, false)
17
20
 
18
21
  @connection = nil
19
22
  @level = 0
20
23
  end
21
24
 
25
+ # Recursively pulls +remotepath+ into +localpath+.
26
+ #
27
+ # Options:
28
+ # :since skip files that already exist locally and are up to date
29
+ # :skip_errors rescue Net::FTPPermError and keep going
30
+ #
31
+ # If a block is given, it is invoked with the local path of each
32
+ # downloaded file.
22
33
  def pull_dir(remotepath, localpath, options = {}, &block)
23
- FileUtils.mkpath(localpath) unless File.exist?(localpath) and log "Creating #{localpath} directory"
34
+ ensure_local_directory(localpath)
24
35
  connect! unless @connection
25
36
  @level += 1
26
37
 
27
- todelete = Dir.glob(File.join(localpath, '*'))
28
- directories = []
29
- files = []
30
-
31
- @connection.list(remotepath) do |e|
32
- entry = Net::FTP::List.parse(e)
33
-
34
- paths = [ "#{remotepath}/#{entry.basename}".gsub(/\/+/, '/'), File.join(localpath, entry.basename)]
38
+ directories, files = scan_directory(remotepath, localpath, options)
35
39
 
36
- if entry.dir?
37
- if entry.name == "." or entry.name == ".."
38
- next
39
- else
40
- directories << paths
41
- end
42
- elsif entry.file?
43
- if options[:since]
44
- files << paths unless File.exist?(paths[1]) and entry.mtime < File.mtime(paths[1]) and entry.filesize == File.size(paths[1])
45
- else
46
- files << paths
47
- end
48
- end
49
- todelete.delete paths[1]
50
- end
51
-
52
- directories.each do |paths|
53
- remotedir, localdir = paths
54
- pull_dir(remotedir, localdir, options, &block)
40
+ directories.each do |remote_dir, local_dir|
41
+ pull_dir(remote_dir, local_dir, options, &block)
55
42
  end
56
-
57
- files.each do |paths|
58
- remotefile, localfile = paths
59
- begin
60
- @connection.get(remotefile, localfile)
61
- log "#{remotefile} => #{localfile}"
62
- rescue Net::FTPPermError
63
- log "Error when reading #{remotefile}"
64
- raise Net::FTPPermError unless options[:skip_errors]
65
- end
43
+
44
+ files.each do |remote_file, local_file|
45
+ download(remote_file, local_file, options, &block)
66
46
  end
67
47
 
68
48
  @level -= 1
69
- close! if @level == 0
49
+ close! if @level.zero?
70
50
  end
71
51
 
72
52
  def connect!
73
53
  @connection = Net::FTP.new
74
- log "Connecting to #{@server}:#{@port} in #{@passive ? "passive" : "active"} mode"
54
+ log "Connecting to #{@server}:#{@port} in #{@passive ? 'passive' : 'active'} mode"
75
55
  @connection.connect(@server, @port)
76
56
  @connection.passive = @passive
77
- log "Logging in as #{@username}:#{@password}"
57
+ log "Logging in as #{@username}"
78
58
  @connection.login(@username, @password)
79
59
  log "Successfully opened connection to #{@server}:#{@port}"
80
60
  end
81
61
 
82
62
  def close!
83
63
  @connection.close
64
+ @connection = nil
84
65
  log "Closed connection to #{@server}:#{@port}"
85
66
  end
86
67
 
87
68
  def log(message)
88
- $stderr.puts message if @verbose
69
+ warn message if @verbose
70
+ end
71
+
72
+ private
73
+
74
+ def ensure_local_directory(localpath)
75
+ return if File.directory?(localpath)
76
+
77
+ FileUtils.mkpath(localpath)
78
+ log "Creating #{localpath} directory"
79
+ end
80
+
81
+ def scan_directory(remotepath, localpath, options)
82
+ directories = []
83
+ files = []
84
+
85
+ @connection.list(remotepath) do |line|
86
+ entry = Net::FTP::List.parse(line)
87
+ next if %w[. ..].include?(entry.name)
88
+
89
+ remote_child = join_remote(remotepath, entry.basename)
90
+ local_child = File.join(localpath, entry.basename)
91
+
92
+ if entry.dir?
93
+ directories << [remote_child, local_child]
94
+ elsif entry.file?
95
+ files << [remote_child, local_child] unless skip_file?(local_child, entry, options)
96
+ end
97
+ end
98
+
99
+ [directories, files]
100
+ end
101
+
102
+ def skip_file?(local_path, entry, options)
103
+ return false unless options[:since]
104
+ return false unless File.file?(local_path)
105
+
106
+ entry.mtime < File.mtime(local_path) && entry.filesize == File.size(local_path)
107
+ end
108
+
109
+ def download(remote_file, local_file, options)
110
+ log "#{remote_file} => #{local_file}"
111
+ @connection.get(remote_file, local_file)
112
+ yield(local_file) if block_given?
113
+ rescue Net::FTPPermError => e
114
+ log "Error when reading #{remote_file}"
115
+ raise e unless options[:skip_errors]
89
116
  end
90
117
 
118
+ def join_remote(remote_dir, basename)
119
+ "#{remote_dir}/#{basename}".gsub(%r{/+}, '/')
120
+ end
91
121
  end
92
122
  end
metadata CHANGED
@@ -1,72 +1,44 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ftpsync
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michał Zając
8
- autorequire:
9
- bindir: exe
8
+ bindir: bin
10
9
  cert_chain: []
11
- date: 2015-07-27 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
- name: net-ftp-list
13
+ name: net-ftp
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: 3.2.8
18
+ version: '0'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
- version: 3.2.8
27
- - !ruby/object:Gem::Dependency
28
- name: bundler
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '1.10'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '1.10'
41
- - !ruby/object:Gem::Dependency
42
- name: rake
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '10.0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '10.0'
25
+ version: '0'
55
26
  - !ruby/object:Gem::Dependency
56
- name: minitest
27
+ name: net-ftp-list
57
28
  requirement: !ruby/object:Gem::Requirement
58
29
  requirements:
59
30
  - - ">="
60
31
  - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
32
+ version: 3.2.8
33
+ type: :runtime
63
34
  prerelease: false
64
35
  version_requirements: !ruby/object:Gem::Requirement
65
36
  requirements:
66
37
  - - ">="
67
38
  - !ruby/object:Gem::Version
68
- version: '0'
69
- description:
39
+ version: 3.2.8
40
+ description: Recursively pull directory trees from FTP servers, with support for incremental
41
+ syncs and per-file callbacks.
70
42
  email:
71
43
  - rubygems.org@quintasan.pl
72
44
  executables: []
@@ -74,7 +46,7 @@ extensions: []
74
46
  extra_rdoc_files: []
75
47
  files:
76
48
  - ".gitignore"
77
- - ".travis.yml"
49
+ - ".rubocop.yml"
78
50
  - CODE_OF_CONDUCT.md
79
51
  - Gemfile
80
52
  - LICENSE.txt
@@ -88,8 +60,9 @@ files:
88
60
  homepage: https://github.com/Quintasan/ftpsync
89
61
  licenses:
90
62
  - MIT
91
- metadata: {}
92
- post_install_message:
63
+ metadata:
64
+ source_code_uri: https://github.com/Quintasan/ftpsync
65
+ rubygems_mfa_required: 'true'
93
66
  rdoc_options: []
94
67
  require_paths:
95
68
  - lib
@@ -97,16 +70,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
97
70
  requirements:
98
71
  - - ">="
99
72
  - !ruby/object:Gem::Version
100
- version: '0'
73
+ version: 2.6.0
101
74
  required_rubygems_version: !ruby/object:Gem::Requirement
102
75
  requirements:
103
76
  - - ">="
104
77
  - !ruby/object:Gem::Version
105
78
  version: '0'
106
79
  requirements: []
107
- rubyforge_project:
108
- rubygems_version: 2.4.6
109
- signing_key:
80
+ rubygems_version: 4.0.16
110
81
  specification_version: 4
111
82
  summary: A simple library for synchronizing from/to FTP servers.
112
83
  test_files: []
data/.travis.yml DELETED
@@ -1,4 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.2.1
4
- before_install: gem install bundler -v 1.10.5