imap-backup 2.0.0 → 2.2.2

Sign up to get free protection for your applications and to get access to all the features.
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