imap-backup 2.0.0 → 2.2.2

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rspec-all +2 -0
  4. data/.rubocop.yml +15 -2
  5. data/.rubocop_todo.yml +58 -0
  6. data/.travis.yml +15 -2
  7. data/README.md +14 -22
  8. data/Rakefile +6 -3
  9. data/bin/imap-backup +5 -11
  10. data/imap-backup.gemspec +10 -6
  11. data/lib/email/mboxrd/message.rb +16 -16
  12. data/lib/imap/backup/account/connection.rb +38 -22
  13. data/lib/imap/backup/account/folder.rb +23 -7
  14. data/lib/imap/backup/configuration/account.rb +25 -21
  15. data/lib/imap/backup/configuration/asker.rb +3 -2
  16. data/lib/imap/backup/configuration/connection_tester.rb +1 -1
  17. data/lib/imap/backup/configuration/folder_chooser.rb +32 -5
  18. data/lib/imap/backup/configuration/list.rb +2 -0
  19. data/lib/imap/backup/configuration/setup.rb +2 -1
  20. data/lib/imap/backup/configuration/store.rb +3 -6
  21. data/lib/imap/backup/downloader.rb +8 -7
  22. data/lib/imap/backup/serializer/mbox.rb +44 -25
  23. data/lib/imap/backup/serializer/mbox_enumerator.rb +31 -0
  24. data/lib/imap/backup/serializer/mbox_store.rb +35 -32
  25. data/lib/imap/backup/uploader.rb +11 -2
  26. data/lib/imap/backup/utils.rb +11 -9
  27. data/lib/imap/backup/version.rb +2 -2
  28. data/spec/features/backup_spec.rb +6 -5
  29. data/spec/features/helper.rb +1 -1
  30. data/spec/features/restore_spec.rb +75 -27
  31. data/spec/features/support/backup_directory.rb +7 -7
  32. data/spec/features/support/email_server.rb +15 -11
  33. data/spec/features/support/shared/connection_context.rb +2 -2
  34. data/spec/features/support/shared/message_fixtures.rb +8 -0
  35. data/spec/spec_helper.rb +1 -1
  36. data/spec/support/fixtures.rb +2 -2
  37. data/spec/support/higline_test_helpers.rb +1 -1
  38. data/spec/unit/email/mboxrd/message_spec.rb +73 -53
  39. data/spec/unit/email/provider_spec.rb +3 -5
  40. data/spec/unit/imap/backup/account/connection_spec.rb +82 -59
  41. data/spec/unit/imap/backup/account/folder_spec.rb +75 -37
  42. data/spec/unit/imap/backup/configuration/account_spec.rb +95 -61
  43. data/spec/unit/imap/backup/configuration/asker_spec.rb +43 -45
  44. data/spec/unit/imap/backup/configuration/connection_tester_spec.rb +21 -22
  45. data/spec/unit/imap/backup/configuration/folder_chooser_spec.rb +66 -33
  46. data/spec/unit/imap/backup/configuration/list_spec.rb +32 -11
  47. data/spec/unit/imap/backup/configuration/setup_spec.rb +97 -56
  48. data/spec/unit/imap/backup/configuration/store_spec.rb +30 -25
  49. data/spec/unit/imap/backup/downloader_spec.rb +28 -26
  50. data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +45 -0
  51. data/spec/unit/imap/backup/serializer/mbox_spec.rb +109 -51
  52. data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +232 -20
  53. data/spec/unit/imap/backup/uploader_spec.rb +23 -9
  54. data/spec/unit/imap/backup/utils_spec.rb +14 -15
  55. data/spec/unit/imap/backup_spec.rb +28 -0
  56. metadata +13 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 334bd1417efce8e604aedbe1878d09bf2c79fce40c8efd3d2d70f2fa66a261bc
4
- data.tar.gz: e217a9925ea2a8afeb7d3d26d72e644bbb58a0012c06b6f2b01c4595d24c2ae6
3
+ metadata.gz: 726ef170419a3bcdc1a39517cdd81640ca0674f5d5f5452f8bc97ef819d74031
4
+ data.tar.gz: 8fc24ed36537a68899a45e22afa3fcf1dee90c27e8432853c2d88e5b4d5c5325
5
5
  SHA512:
6
- metadata.gz: b374fe448bf88411f2681c7c07159f34c06ed8c537f1dc8c18b41c60ca7c690693d0b1c672e7a31e85d38c0b50afbc9e1004344359273ef17f0ca148909a095f
7
- data.tar.gz: 9be2593d7b380bd0c3d1af1a85a79d9a3662de74d078d0564fff08884c336325f1bb33c08f10582e48e0728178d80cc6a8633a26782641ee66861ca313eec9cc
6
+ metadata.gz: e2b59fa22614c634169104d75ad6214a0d8c788da712a8c3545281e98666b4d23b17e1a10cc1744fcf615966bd63616daa3241f7e932d2af4d10fd4508391583
7
+ data.tar.gz: 9e73290e3684aadc443f449f870c7ed8d23506408439bb69dc41b57613e8e439e32a4017f8dbfaf462ea5dac5fd029a0d4691c861600d4aaeeaff290e94e9215
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
1
  --color
2
2
  --require spec_helper
3
- --tag ~docker
data/.rspec-all CHANGED
@@ -1,2 +1,4 @@
1
1
  --color
2
+ --format documentation
3
+ --order random
2
4
  --require spec_helper
@@ -1,8 +1,21 @@
1
- inherit_from: https://gitlab.com/snippets/1744945/raw
1
+ inherit_from:
2
+ - https://raw.githubusercontent.com/leanpanda-com/rubocop/0.89.1/rubocop-rspec.yml
3
+ - .rubocop_todo.yml
2
4
 
3
5
  AllCops:
4
- TargetRubyVersion: 2.1
5
6
  Exclude:
6
7
  - "bin/stubs/*"
7
8
  DisplayCopNames:
8
9
  Enabled: true
10
+
11
+ RSpec/ContextWording:
12
+ Exclude:
13
+ - "spec/features/**/*"
14
+ RSpec/MessageSpies:
15
+ Enabled: false
16
+ RSpec/NestedGroups:
17
+ Max: 4
18
+ RSpec/ReturnFromStub:
19
+ Enabled: false
20
+ Style/EmptyCaseCondition:
21
+ Enabled: false
@@ -0,0 +1,58 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2020-09-24 16:30:54 UTC using RuboCop version 0.89.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 10
10
+ # Configuration parameters: IgnoredMethods.
11
+ Metrics/AbcSize:
12
+ Max: 29
13
+
14
+ # Offense count: 2
15
+ # Configuration parameters: CountComments, CountAsOne, ExcludedMethods.
16
+ # ExcludedMethods: refine
17
+ Metrics/BlockLength:
18
+ Max: 133
19
+
20
+ # Offense count: 2
21
+ # Configuration parameters: CountComments, CountAsOne.
22
+ Metrics/ClassLength:
23
+ Max: 167
24
+
25
+ # Offense count: 14
26
+ # Configuration parameters: CountComments, CountAsOne, ExcludedMethods.
27
+ Metrics/MethodLength:
28
+ Max: 25
29
+
30
+ # Offense count: 2
31
+ # Configuration parameters: CountComments, CountAsOne.
32
+ Metrics/ModuleLength:
33
+ Max: 136
34
+
35
+ # Offense count: 181
36
+ # Configuration parameters: AllowSubject.
37
+ RSpec/MultipleMemoizedHelpers:
38
+ Max: 19
39
+
40
+ # Offense count: 1
41
+ # Cop supports --auto-correct.
42
+ Style/GlobalStdStream:
43
+ Exclude:
44
+ - 'lib/imap/backup.rb'
45
+
46
+ # Offense count: 3
47
+ # Cop supports --auto-correct.
48
+ Style/IfUnlessModifier:
49
+ Exclude:
50
+ - 'bin/imap-backup'
51
+ - 'lib/email/mboxrd/message.rb'
52
+ - 'lib/imap/backup/configuration/account.rb'
53
+
54
+ # Offense count: 1
55
+ # Cop supports --auto-correct.
56
+ Style/RedundantRegexpEscape:
57
+ Exclude:
58
+ - 'spec/features/backup_spec.rb'
@@ -1,12 +1,25 @@
1
1
  language: ruby
2
+
3
+ services:
4
+ - docker
5
+
2
6
  rvm:
3
- - 2.3
4
7
  - 2.4
5
8
  - 2.5
9
+ - 2.6
10
+ - 2.7
11
+ - jruby-19mode
12
+
6
13
  branches:
7
14
  only:
8
15
  - master
16
+
9
17
  before_install:
10
18
  - gem update --system
11
19
  - gem update bundler
12
- script: "bundle exec rspec --tag ~docker"
20
+
21
+ script:
22
+ - docker pull antespi/docker-imap-devel:latest
23
+ - docker run -d --env MAIL_ADDRESS=address@example.org --env MAIL_PASS=pass --env MAILNAME=example.org --publish 8993:993 antespi/docker-imap-devel:latest
24
+ - sleep 10
25
+ - bundle exec rake
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Build Status](https://secure.travis-ci.org/joeyates/imap-backup.png)][Continuous Integration]
1
+ [![Build Status](https://secure.travis-ci.org/joeyates/imap-backup.svg)][Continuous Integration]
2
2
  [![Source Analysis](https://codeclimate.com/github/joeyates/imap-backup/badges/gpa.svg)](https://codeclimate.com/github/joeyates/imap-backup)
3
3
  [![Test Coverage](https://codeclimate.com/github/joeyates/imap-backup/badges/coverage.svg)](https://codeclimate.com/github/joeyates/imap-backup/coverage)
4
4
 
@@ -46,12 +46,13 @@ specific folders.
46
46
 
47
47
  ## Configuration file
48
48
 
49
- `setup` creates ~/.imap-backup directory and configuration file. E.g.:
49
+ `setup` creates the file `~/.imap-backup/config.json`
50
+
51
+ E.g.:
50
52
 
51
53
  ```json
52
54
  {
53
- "accounts":
54
- [
55
+ "accounts": [
55
56
  {
56
57
  "username": "my.user@gmail.com",
57
58
  "password": "secret",
@@ -70,8 +71,7 @@ It connects to GMail by default, but you can also specify a server:
70
71
 
71
72
  ```json
72
73
  {
73
- "accounts":
74
- [
74
+ "accounts": [
75
75
  {
76
76
  "username": "my.user@gmail.com",
77
77
  "password": "secret",
@@ -96,8 +96,7 @@ Specifically, if you are using a self-signed certificate and get SSL errors, e.g
96
96
 
97
97
  ```json
98
98
  {
99
- "accounts":
100
- [
99
+ "accounts": [
101
100
  {
102
101
  "username": "my.user@gmail.com",
103
102
  "password": "secret",
@@ -194,9 +193,6 @@ $ imap-backup status
194
193
 
195
194
  # Similar Software
196
195
 
197
- * https://github.com/thefloweringash/gmail-imap-backup
198
- * https://github.com/mleonhard/imapbackup
199
- * https://github.com/rgrove/larch - copies between IMAP servers
200
196
  * https://github.com/OfflineIMAP/offlineimap
201
197
 
202
198
  # Testing
@@ -208,8 +204,8 @@ Integration tests (feature specs) are run against a Docker image
208
204
 
209
205
  In one shell, run the Docker image:
210
206
 
211
- ```
212
- docker run \
207
+ ```sh
208
+ $ docker run \
213
209
  --env MAIL_ADDRESS=address@example.org \
214
210
  --env MAIL_PASS=pass \
215
211
  --env MAILNAME=example.org \
@@ -217,18 +213,14 @@ docker run \
217
213
  antespi/docker-imap-devel:latest
218
214
  ```
219
215
 
220
- Currently, the integration tests with Docker are excluded from the CI run.
221
-
222
- To run **just** the Docker-based tests:
223
-
224
- ```
225
- rspec -t docker
216
+ ```sh
217
+ $ rake
226
218
  ```
227
219
 
228
- To run **all** specs, including the integration tests, do the following:
220
+ To exclude Docker-based tests:
229
221
 
230
- ```
231
- rspec -O .rspec-all
222
+ ```sh
223
+ $ rspec --tag ~docker
232
224
  ```
233
225
 
234
226
  ## Contributing
data/Rakefile CHANGED
@@ -1,9 +1,12 @@
1
- #!/usr/bin/env rake
2
1
  require "bundler/gem_tasks"
3
2
  require "rspec/core/rake_task"
4
-
5
- task default: :spec
3
+ require "rubocop/rake_task"
6
4
 
7
5
  RSpec::Core::RakeTask.new do |t|
8
6
  t.pattern = "spec/**/*_spec.rb"
9
7
  end
8
+
9
+ RuboCop::RakeTask.new
10
+
11
+ task default: :spec
12
+ task default: :rubocop
@@ -46,16 +46,14 @@ parser = OptionParser.new do |opts|
46
46
  end
47
47
  parser.parse!
48
48
 
49
- if ARGV.size > 0
50
- options[:command] = ARGV.shift
51
- end
49
+ options[:command] = ARGV.shift if !ARGV.empty?
52
50
 
53
51
  if KNOWN_COMMANDS.find { |c| c[:name] == options[:command] }.nil?
54
52
  raise "Unknown command '#{options[:command]}'"
55
53
  end
56
54
 
57
55
  if options[:command] == "help"
58
- puts opts
56
+ puts parser
59
57
  exit
60
58
  end
61
59
 
@@ -72,9 +70,7 @@ case options[:command]
72
70
  when "setup"
73
71
  Imap::Backup::Configuration::Setup.new.run
74
72
  when "backup"
75
- configuration.each_connection do |connection|
76
- connection.run_backup
77
- end
73
+ configuration.each_connection(&:run_backup)
78
74
  when "folders"
79
75
  configuration.each_connection do |connection|
80
76
  puts connection.username
@@ -83,12 +79,10 @@ when "folders"
83
79
  warn "Unable to list account folders"
84
80
  exit 1
85
81
  end
86
- folders.each { |f| puts "\t" + f.name }
82
+ folders.each { |f| puts "\t#{f.name}" }
87
83
  end
88
84
  when "restore"
89
- configuration.each_connection do |connection|
90
- connection.restore
91
- end
85
+ configuration.each_connection(&:restore)
92
86
  when "status"
93
87
  configuration.each_connection do |connection|
94
88
  puts connection.username
@@ -1,4 +1,4 @@
1
- $LOAD_PATH.unshift(File.expand_path("../lib", __FILE__))
1
+ $LOAD_PATH.unshift(File.expand_path("lib", __dir__))
2
2
  require "imap/backup/version"
3
3
 
4
4
  Gem::Specification.new do |gem|
@@ -9,12 +9,12 @@ Gem::Specification.new do |gem|
9
9
  gem.email = ["joe.g.yates@gmail.com"]
10
10
  gem.homepage = "https://github.com/joeyates/imap-backup"
11
11
 
12
- gem.files = `git ls-files`.split($\)
12
+ gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
13
13
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
14
14
  gem.test_files = gem.files.grep(%r{^spec/})
15
15
  gem.require_paths = ["lib"]
16
- gem.required_ruby_version = [">= 2.2.0"]
17
- gem.version = Imap::Backup::VERSION
16
+ gem.required_ruby_version = [">= 2.4.0"]
17
+ gem.version = Imap::Backup::VERSION
18
18
 
19
19
  gem.post_install_message = <<-MESSAGE.gsub(/^\s{4}/m, "")
20
20
  Note that, when upgrading #{gem.name} from version 1.x to 2.x,
@@ -24,12 +24,16 @@ Gem::Specification.new do |gem|
24
24
  **deleted** and a full new backup created.
25
25
  MESSAGE
26
26
 
27
- gem.add_runtime_dependency "rake"
28
27
  gem.add_runtime_dependency "highline"
29
28
  gem.add_runtime_dependency "mail"
29
+ gem.add_runtime_dependency "rake"
30
30
 
31
31
  gem.add_development_dependency "codeclimate-test-reporter", "~> 0.4.8"
32
- gem.add_development_dependency "pry-byebug"
32
+ if RUBY_ENGINE == "jruby"
33
+ gem.add_development_dependency "pry-debugger-jruby"
34
+ else
35
+ gem.add_development_dependency "pry-byebug"
36
+ end
33
37
  gem.add_development_dependency "rspec", ">= 3.0.0"
34
38
  gem.add_development_dependency "rubocop-rspec"
35
39
  gem.add_development_dependency "simplecov"
@@ -18,16 +18,15 @@ module Email::Mboxrd
18
18
 
19
19
  def initialize(supplied_body)
20
20
  @supplied_body = supplied_body.clone
21
- @supplied_body.force_encoding("binary")
22
21
  end
23
22
 
24
23
  def to_serialized
25
- "From " + from + "\n" + mboxrd_body
24
+ "From #{from}\n" + mboxrd_body
26
25
  end
27
26
 
28
27
  def date
29
28
  parsed.date
30
- rescue
29
+ rescue StandardError
31
30
  nil
32
31
  end
33
32
 
@@ -41,27 +40,28 @@ module Email::Mboxrd
41
40
  @parsed ||= Mail.new(supplied_body)
42
41
  end
43
42
 
44
- def best_from
45
- if parsed.from.is_a?(Enumerable)
46
- parsed.from.each do |addr|
47
- return addr if addr
43
+ def from
44
+ @from ||=
45
+ begin
46
+ from = best_from.dup
47
+ from << " #{asctime}" if asctime != ""
48
+ from
48
49
  end
49
- end
50
+ end
50
51
 
52
+ def best_from
53
+ return first_from if first_from
51
54
  return parsed.sender if parsed.sender
52
55
  return parsed.envelope_from if parsed.envelope_from
53
56
  return parsed.return_path if parsed.return_path
54
57
 
55
- return ""
58
+ ""
56
59
  end
57
60
 
58
- def from
59
- @from ||=
60
- begin
61
- from = best_from.dup
62
- from << " " + asctime if asctime != ""
63
- from
64
- end
61
+ def first_from
62
+ return nil if !parsed.from.is_a?(Enumerable)
63
+
64
+ parsed.from.find { |from| from }
65
65
  end
66
66
 
67
67
  def mboxrd_body
@@ -10,7 +10,8 @@ module Imap::Backup
10
10
  attr_reader :username
11
11
 
12
12
  def initialize(options)
13
- @username, @password = options[:username], options[:password]
13
+ @username = options[:username]
14
+ @password = options[:password]
14
15
  @local_path = options[:local_path]
15
16
  @backup_folders = options[:folders]
16
17
  @server = options[:server]
@@ -46,33 +47,17 @@ module Imap::Backup
46
47
  # start the connection so we get logging messages in the right order
47
48
  imap
48
49
  each_folder do |folder, serializer|
50
+ next if !folder.exist?
51
+
49
52
  Imap::Backup.logger.debug "[#{folder.name}] running backup"
50
- serializer.set_uid_validity(folder.uid_validity)
53
+ serializer.apply_uid_validity(folder.uid_validity)
51
54
  Downloader.new(folder, serializer).run
52
55
  end
53
56
  end
54
57
 
55
58
  def restore
56
59
  local_folders do |serializer, folder|
57
- exists = folder.exist?
58
- if exists
59
- new_name = serializer.set_uid_validity(folder.uid_validity)
60
- old_name = serializer.folder
61
- if new_name
62
- Imap::Backup.logger.debug "Backup '#{old_name}' renamed and restored to '#{new_name}'"
63
- new_serializer = Serializer::Mbox.new(local_path, new_name)
64
- new_folder = Account::Folder.new(self, new_name)
65
- new_folder.create
66
- new_serializer.force_uid_validity(new_folder.uid_validity)
67
- Uploader.new(new_folder, new_serializer).run
68
- else
69
- Uploader.new(folder, serializer).run
70
- end
71
- else
72
- folder.create
73
- serializer.force_uid_validity(folder.uid_validity)
74
- Uploader.new(folder, serializer).run
75
- end
60
+ restore_folder serializer, folder
76
61
  end
77
62
  end
78
63
 
@@ -87,6 +72,7 @@ module Imap::Backup
87
72
 
88
73
  def imap
89
74
  return @imap unless @imap.nil?
75
+
90
76
  options = provider_options
91
77
  Imap::Backup.logger.debug(
92
78
  "Creating IMAP instance: #{server}, options: #{options.inspect}"
@@ -108,6 +94,33 @@ module Imap::Backup
108
94
  end
109
95
  end
110
96
 
97
+ def restore_folder(serializer, folder)
98
+ existing_uids = folder.uids
99
+ if existing_uids.any?
100
+ Imap::Backup.logger.debug(
101
+ "There's already a '#{folder.name}' folder with emails"
102
+ )
103
+ new_name = serializer.apply_uid_validity(folder.uid_validity)
104
+ old_name = serializer.folder
105
+ if new_name
106
+ Imap::Backup.logger.debug(
107
+ "Backup '#{old_name}' renamed and restored to '#{new_name}'"
108
+ )
109
+ new_serializer = Serializer::Mbox.new(local_path, new_name)
110
+ new_folder = Account::Folder.new(self, new_name)
111
+ new_folder.create
112
+ new_serializer.force_uid_validity(new_folder.uid_validity)
113
+ Uploader.new(new_folder, new_serializer).run
114
+ else
115
+ Uploader.new(folder, serializer).run
116
+ end
117
+ else
118
+ folder.create
119
+ serializer.force_uid_validity(folder.uid_validity)
120
+ Uploader.new(folder, serializer).run
121
+ end
122
+ end
123
+
111
124
  def create_account_folder
112
125
  Utils.make_folder(
113
126
  File.dirname(local_path),
@@ -121,7 +134,8 @@ module Imap::Backup
121
134
  end
122
135
 
123
136
  def backup_folders
124
- return @backup_folders if @backup_folders && (@backup_folders.size > 0)
137
+ return @backup_folders if @backup_folders && !@backup_folders.empty?
138
+
125
139
  (folders || []).map { |f| {name: f.name} }
126
140
  end
127
141
 
@@ -143,6 +157,7 @@ module Imap::Backup
143
157
  def server
144
158
  return @server if @server
145
159
  return nil if provider.nil?
160
+
146
161
  @server = provider.host
147
162
  end
148
163
 
@@ -156,6 +171,7 @@ module Imap::Backup
156
171
  # in the reference.
157
172
  def provider_root
158
173
  return @provider_root if @provider_root
174
+
159
175
  root_info = imap.list("", "")[0]
160
176
  @provider_root = root_info.name
161
177
  end