imap-backup 2.1.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rubocop.yml +5 -2
  4. data/.rubocop_todo.yml +24 -0
  5. data/.travis.yml +13 -1
  6. data/README.md +9 -21
  7. data/Rakefile +6 -2
  8. data/imap-backup.gemspec +5 -1
  9. data/lib/email/mboxrd/message.rb +13 -12
  10. data/lib/imap/backup/account/connection.rb +3 -2
  11. data/lib/imap/backup/account/folder.rb +2 -0
  12. data/lib/imap/backup/configuration/account.rb +20 -16
  13. data/lib/imap/backup/configuration/asker.rb +1 -1
  14. data/lib/imap/backup/serializer/mbox.rb +39 -25
  15. data/lib/imap/backup/serializer/mbox_enumerator.rb +31 -0
  16. data/lib/imap/backup/serializer/mbox_store.rb +6 -27
  17. data/lib/imap/backup/version.rb +1 -1
  18. data/spec/features/support/email_server.rb +3 -0
  19. data/spec/support/fixtures.rb +1 -1
  20. data/spec/unit/email/mboxrd/message_spec.rb +20 -3
  21. data/spec/unit/email/provider_spec.rb +1 -1
  22. data/spec/unit/imap/backup/account/connection_spec.rb +10 -9
  23. data/spec/unit/imap/backup/account/folder_spec.rb +24 -10
  24. data/spec/unit/imap/backup/configuration/account_spec.rb +47 -22
  25. data/spec/unit/imap/backup/configuration/asker_spec.rb +5 -9
  26. data/spec/unit/imap/backup/configuration/connection_tester_spec.rb +6 -6
  27. data/spec/unit/imap/backup/configuration/folder_chooser_spec.rb +6 -6
  28. data/spec/unit/imap/backup/configuration/list_spec.rb +24 -1
  29. data/spec/unit/imap/backup/configuration/setup_spec.rb +29 -9
  30. data/spec/unit/imap/backup/configuration/store_spec.rb +5 -5
  31. data/spec/unit/imap/backup/downloader_spec.rb +11 -13
  32. data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +40 -0
  33. data/spec/unit/imap/backup/serializer/mbox_spec.rb +63 -24
  34. data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +162 -9
  35. data/spec/unit/imap/backup/utils_spec.rb +3 -3
  36. data/spec/unit/imap/backup_spec.rb +28 -0
  37. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f5903e540e8d72a63c70ee1f8f5799710bdef2ffaac58bc6bac617942da6e6da
4
- data.tar.gz: d4ad4a77a5a7d86146768a889f697f281c344ca5e4db6f29fc792a0894b2fedd
3
+ metadata.gz: 78c6ccd7ca7a6f60ae02c24c76c121470372ffbadf268eb6f52b8f6eac633e6d
4
+ data.tar.gz: d5da356a60a6046301c2e66a990f24948c3dc63f6c7f757ca3da43f62772468d
5
5
  SHA512:
6
- metadata.gz: fff913ad0270445d6783430d39672e9abe34a67d20546ad03db5ec0c8c6179c5f98524a83546d6e1790b1550284902bf0a161c998a82301ddbbbfb33d845a708
7
- data.tar.gz: d0c64c701e183d1bb01b28af7577c237a5ec0d26607ed1856e1c2b6748efe48479c0f71b502db83a8e68c22faab220372541b16edf5d3e5b7ad0af86e933460e
6
+ metadata.gz: 82fc5680a656d79012690e7da5038f0b8e56b1c5b14168142c222cb2c336bf44c0ec5570fbed8c50b2b2c2ea445ec17bed286d452795aa0c692d61c71d497274
7
+ data.tar.gz: 7eda772c5b9815dfbbe679981a0a281cecd7e3cc970c95345437d35ddc01b40f5fedb25eabef043271bdd7cfc7084bf7caad6bbd037f5926fabee0d23958252a
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
1
  --color
2
2
  --require spec_helper
3
- --tag ~docker
@@ -1,4 +1,6 @@
1
- inherit_from: https://gitlab.com/snippets/1744945/raw
1
+ inherit_from:
2
+ - https://gitlab.com/snippets/1744945/raw
3
+ - .rubocop_todo.yml
2
4
 
3
5
  AllCops:
4
6
  TargetRubyVersion: 2.3
@@ -8,7 +10,8 @@ AllCops:
8
10
  Enabled: true
9
11
 
10
12
  RSpec/ContextWording:
11
- Enabled: false
13
+ Exclude:
14
+ - "spec/features/**/*"
12
15
  RSpec/NestedGroups:
13
16
  Max: 4
14
17
  RSpec/ReturnFromStub:
@@ -0,0 +1,24 @@
1
+ # Offense count: 8
2
+ Metrics/AbcSize:
3
+ Max: 29
4
+
5
+ # Offense count: 2
6
+ # Configuration parameters: CountComments, ExcludedMethods.
7
+ # ExcludedMethods: refine
8
+ Metrics/BlockLength:
9
+ Max: 133
10
+
11
+ # Offense count: 2
12
+ # Configuration parameters: CountComments.
13
+ Metrics/ClassLength:
14
+ Max: 154
15
+
16
+ # Offense count: 11
17
+ # Configuration parameters: CountComments, ExcludedMethods.
18
+ Metrics/MethodLength:
19
+ Max: 25
20
+
21
+ # Offense count: 1
22
+ # Configuration parameters: CountComments.
23
+ Metrics/ModuleLength:
24
+ Max: 136
@@ -1,13 +1,25 @@
1
1
  language: ruby
2
+
3
+ services:
4
+ - docker
5
+
2
6
  rvm:
3
7
  - 2.3
4
8
  - 2.4
5
9
  - 2.5
6
10
  - 2.6
11
+ - jruby-19mode
12
+
7
13
  branches:
8
14
  only:
9
15
  - master
16
+
10
17
  before_install:
11
18
  - gem update --system
12
19
  - gem update bundler
13
- 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
@@ -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
@@ -217,18 +213,10 @@ 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.
216
+ To exclude Docker-based tests:
221
217
 
222
- To run **just** the Docker-based tests:
223
-
224
- ```
225
- rspec -t docker
226
- ```
227
-
228
- To run **all** specs, including the integration tests, do the following:
229
-
230
- ```
231
- rspec -O .rspec-all
218
+ ```sh
219
+ $ rspec --tag ~docker
232
220
  ```
233
221
 
234
222
  ## Contributing
data/Rakefile CHANGED
@@ -1,8 +1,12 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
-
4
- task default: :spec
3
+ require "rubocop/rake_task"
5
4
 
6
5
  RSpec::Core::RakeTask.new do |t|
7
6
  t.pattern = "spec/**/*_spec.rb"
8
7
  end
8
+
9
+ RuboCop::RakeTask.new
10
+
11
+ task default: :spec
12
+ task default: :rubocop
@@ -29,7 +29,11 @@ Gem::Specification.new do |gem|
29
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"
@@ -41,13 +41,17 @@ module Email::Mboxrd
41
41
  @parsed ||= Mail.new(supplied_body)
42
42
  end
43
43
 
44
- def best_from
45
- if parsed.from.is_a?(Enumerable)
46
- parsed.from.each do |addr|
47
- return addr if addr
44
+ def from
45
+ @from ||=
46
+ begin
47
+ from = best_from.dup
48
+ from << " " + asctime if asctime != ""
49
+ from
48
50
  end
49
- end
51
+ end
50
52
 
53
+ def best_from
54
+ return first_from if first_from
51
55
  return parsed.sender if parsed.sender
52
56
  return parsed.envelope_from if parsed.envelope_from
53
57
  return parsed.return_path if parsed.return_path
@@ -55,13 +59,10 @@ module Email::Mboxrd
55
59
  ""
56
60
  end
57
61
 
58
- def from
59
- @from ||=
60
- begin
61
- from = best_from.dup
62
- from << " " + asctime if asctime != ""
63
- from
64
- end
62
+ def first_from
63
+ return nil if !parsed.from.is_a?(Enumerable)
64
+
65
+ parsed.from.find { |from| from }
65
66
  end
66
67
 
67
68
  def mboxrd_body
@@ -48,8 +48,9 @@ module Imap::Backup
48
48
  imap
49
49
  each_folder do |folder, serializer|
50
50
  next if !folder.exist?
51
+
51
52
  Imap::Backup.logger.debug "[#{folder.name}] running backup"
52
- serializer.set_uid_validity(folder.uid_validity)
53
+ serializer.apply_uid_validity(folder.uid_validity)
53
54
  Downloader.new(folder, serializer).run
54
55
  end
55
56
  end
@@ -58,7 +59,7 @@ module Imap::Backup
58
59
  local_folders do |serializer, folder|
59
60
  exists = folder.exist?
60
61
  if exists
61
- new_name = serializer.set_uid_validity(folder.uid_validity)
62
+ new_name = serializer.apply_uid_validity(folder.uid_validity)
62
63
  old_name = serializer.folder
63
64
  if new_name
64
65
  Imap::Backup.logger.debug(
@@ -61,6 +61,8 @@ module Imap::Backup
61
61
 
62
62
  fetch_data_item = fetch_data_items[0]
63
63
  attributes = fetch_data_item.attr
64
+ return nil if !attributes.key?("RFC822")
65
+
64
66
  attributes["RFC822"].force_encoding("utf-8")
65
67
  attributes
66
68
  rescue FolderNotFound
@@ -1,7 +1,7 @@
1
1
  module Imap::Backup
2
2
  module Configuration; end
3
3
 
4
- class Configuration::Account < Struct.new(:store, :account, :highline)
4
+ Configuration::Account = Struct.new(:store, :account, :highline) do
5
5
  def initialize(store, account, highline)
6
6
  super
7
7
  end
@@ -51,7 +51,9 @@ module Imap::Backup
51
51
  others = other_accounts.map { |a| a[:username] }
52
52
  Kernel.puts "others: #{others.inspect}"
53
53
  if others.include?(username)
54
- Kernel.puts "There is already an account set up with that email address"
54
+ Kernel.puts(
55
+ "There is already an account set up with that email address"
56
+ )
55
57
  else
56
58
  account[:username] = username
57
59
  if account[:server].nil? || (account[:server] == "")
@@ -82,23 +84,25 @@ module Imap::Backup
82
84
  end
83
85
  end
84
86
 
87
+ def path_modification_validator(path)
88
+ same = store.accounts.find do |a|
89
+ a[:username] != account[:username] && a[:local_path] == path
90
+ end
91
+ if same
92
+ Kernel.puts "The path '#{path}' is used to backup " \
93
+ "the account '#{same[:username]}'"
94
+ false
95
+ else
96
+ true
97
+ end
98
+ end
99
+
85
100
  def modify_backup_path(menu)
86
101
  menu.choice("modify backup path") do
87
- validator = ->(p) do
88
- same = store.accounts.find do |a|
89
- a[:username] != account[:username] && a[:local_path] == p
90
- end
91
- if same
92
- Kernel.puts "The path '#{p}' is used to backup " \
93
- "the account '#{same[:username]}'"
94
- false
95
- else
96
- true
97
- end
98
- end
99
102
  existing = account[:local_path].clone
100
- account[:local_path] =
101
- Configuration::Asker.backup_path(account[:local_path], validator)
103
+ account[:local_path] = Configuration::Asker.backup_path(
104
+ account[:local_path], ->(path) { path_modification_validator(path) }
105
+ )
102
106
  account[:modified] = true if existing != account[:local_path]
103
107
  end
104
108
  end
@@ -1,7 +1,7 @@
1
1
  module Imap::Backup
2
2
  module Configuration; end
3
3
 
4
- class Configuration::Asker < Struct.new(:highline)
4
+ Configuration::Asker = Struct.new(:highline) do
5
5
  EMAIL_MATCHER = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]+$/i.freeze
6
6
 
7
7
  def initialize(highline)
@@ -10,31 +10,16 @@ module Imap::Backup
10
10
  @folder = folder
11
11
  end
12
12
 
13
- def set_uid_validity(value)
14
- existing_uid_validity = store.uid_validity
13
+ def apply_uid_validity(value)
15
14
  case
16
- when existing_uid_validity.nil?
15
+ when store.uid_validity.nil?
17
16
  store.uid_validity = value
18
17
  nil
19
- when existing_uid_validity == value
18
+ when store.uid_validity == value
20
19
  # NOOP
21
20
  nil
22
21
  else
23
- digit = nil
24
- new_name = nil
25
- loop do
26
- extra = digit ? ".#{digit}" : ""
27
- new_name = "#{folder}.#{existing_uid_validity}#{extra}"
28
- test_store = Serializer::MboxStore.new(path, new_name)
29
- break if !test_store.exist?
30
-
31
- digit ||= 0
32
- digit += 1
33
- end
34
- store.rename new_name
35
- @store = nil
36
- store.uid_validity = value
37
- new_name
22
+ apply_new_uid_validity value
38
23
  end
39
24
  end
40
25
 
@@ -73,18 +58,47 @@ module Imap::Backup
73
58
  end
74
59
  end
75
60
 
76
- def create_containing_directory
77
- relative_path = File.dirname(folder)
78
- containing_directory = File.join(path, relative_path)
79
- full_path = File.expand_path(containing_directory)
61
+ def apply_new_uid_validity(value)
62
+ digit = 0
63
+ new_name = nil
64
+ loop do
65
+ extra = digit.zero? ? "" : ".#{digit}"
66
+ new_name = "#{folder}.#{store.uid_validity}#{extra}"
67
+ test_store = Serializer::MboxStore.new(path, new_name)
68
+ break if !test_store.exist?
80
69
 
70
+ digit += 1
71
+ end
72
+ rename_store new_name, value
73
+ end
74
+
75
+ def rename_store(new_name, value)
76
+ store.rename new_name
77
+ @store = nil
78
+ store.uid_validity = value
79
+ new_name
80
+ end
81
+
82
+ def relative_path
83
+ File.dirname(folder)
84
+ end
85
+
86
+ def containing_directory
87
+ File.join(path, relative_path)
88
+ end
89
+
90
+ def full_path
91
+ File.expand_path(containing_directory)
92
+ end
93
+
94
+ def create_containing_directory
81
95
  if !File.directory?(full_path)
82
- Imap::Backup::Utils.make_folder(
96
+ Utils.make_folder(
83
97
  path, relative_path, Serializer::DIRECTORY_PERMISSIONS
84
98
  )
85
99
  end
86
100
 
87
- if Imap::Backup::Utils.mode(full_path) !=
101
+ if Utils.mode(full_path) !=
88
102
  Serializer::DIRECTORY_PERMISSIONS
89
103
  FileUtils.chmod Serializer::DIRECTORY_PERMISSIONS, full_path
90
104
  end
@@ -0,0 +1,31 @@
1
+ module Imap::Backup
2
+ class Serializer::MboxEnumerator
3
+ attr_reader :mbox_pathname
4
+
5
+ def initialize(mbox_pathname)
6
+ @mbox_pathname = mbox_pathname
7
+ end
8
+
9
+ def each
10
+ return enum_for(:each) if !block_given?
11
+
12
+ File.open(mbox_pathname) do |f|
13
+ lines = []
14
+
15
+ loop do
16
+ line = f.gets
17
+ break if !line
18
+
19
+ if line.start_with?("From ")
20
+ yield lines.join if lines.count.positive?
21
+ lines = [line]
22
+ else
23
+ lines << line
24
+ end
25
+ end
26
+
27
+ yield lines.join if lines.count.positive?
28
+ end
29
+ end
30
+ end
31
+ end