imap-backup 2.0.0 → 2.1.0

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec-all +2 -0
  3. data/.rubocop.yml +10 -1
  4. data/.travis.yml +1 -0
  5. data/README.md +1 -1
  6. data/Rakefile +0 -1
  7. data/bin/imap-backup +3 -9
  8. data/imap-backup.gemspec +5 -5
  9. data/lib/email/mboxrd/message.rb +2 -2
  10. data/lib/imap/backup/account/connection.rb +11 -3
  11. data/lib/imap/backup/account/folder.rb +11 -6
  12. data/lib/imap/backup/configuration/account.rb +7 -7
  13. data/lib/imap/backup/configuration/asker.rb +2 -1
  14. data/lib/imap/backup/configuration/connection_tester.rb +1 -1
  15. data/lib/imap/backup/configuration/folder_chooser.rb +32 -5
  16. data/lib/imap/backup/configuration/list.rb +2 -0
  17. data/lib/imap/backup/configuration/setup.rb +2 -1
  18. data/lib/imap/backup/configuration/store.rb +3 -6
  19. data/lib/imap/backup/downloader.rb +8 -7
  20. data/lib/imap/backup/serializer/mbox.rb +2 -1
  21. data/lib/imap/backup/serializer/mbox_store.rb +14 -6
  22. data/lib/imap/backup/uploader.rb +1 -0
  23. data/lib/imap/backup/utils.rb +11 -9
  24. data/lib/imap/backup/version.rb +1 -1
  25. data/spec/features/backup_spec.rb +6 -5
  26. data/spec/features/support/backup_directory.rb +5 -5
  27. data/spec/features/support/email_server.rb +11 -8
  28. data/spec/features/support/shared/connection_context.rb +2 -2
  29. data/spec/support/fixtures.rb +1 -1
  30. data/spec/support/higline_test_helpers.rb +1 -1
  31. data/spec/unit/email/mboxrd/message_spec.rb +51 -42
  32. data/spec/unit/email/provider_spec.rb +0 -2
  33. data/spec/unit/imap/backup/account/connection_spec.rb +18 -11
  34. data/spec/unit/imap/backup/account/folder_spec.rb +26 -12
  35. data/spec/unit/imap/backup/configuration/account_spec.rb +22 -19
  36. data/spec/unit/imap/backup/configuration/asker_spec.rb +30 -31
  37. data/spec/unit/imap/backup/configuration/connection_tester_spec.rb +16 -13
  38. data/spec/unit/imap/backup/configuration/folder_chooser_spec.rb +45 -18
  39. data/spec/unit/imap/backup/configuration/list_spec.rb +8 -13
  40. data/spec/unit/imap/backup/configuration/setup_spec.rb +36 -30
  41. data/spec/unit/imap/backup/configuration/store_spec.rb +7 -4
  42. data/spec/unit/imap/backup/downloader_spec.rb +11 -7
  43. data/spec/unit/imap/backup/serializer/mbox_spec.rb +2 -5
  44. data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +4 -4
  45. data/spec/unit/imap/backup/uploader_spec.rb +0 -2
  46. data/spec/unit/imap/backup/utils_spec.rb +1 -3
  47. metadata +6 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 334bd1417efce8e604aedbe1878d09bf2c79fce40c8efd3d2d70f2fa66a261bc
4
- data.tar.gz: e217a9925ea2a8afeb7d3d26d72e644bbb58a0012c06b6f2b01c4595d24c2ae6
3
+ metadata.gz: f5903e540e8d72a63c70ee1f8f5799710bdef2ffaac58bc6bac617942da6e6da
4
+ data.tar.gz: d4ad4a77a5a7d86146768a889f697f281c344ca5e4db6f29fc792a0894b2fedd
5
5
  SHA512:
6
- metadata.gz: b374fe448bf88411f2681c7c07159f34c06ed8c537f1dc8c18b41c60ca7c690693d0b1c672e7a31e85d38c0b50afbc9e1004344359273ef17f0ca148909a095f
7
- data.tar.gz: 9be2593d7b380bd0c3d1af1a85a79d9a3662de74d078d0564fff08884c336325f1bb33c08f10582e48e0728178d80cc6a8633a26782641ee66861ca313eec9cc
6
+ metadata.gz: fff913ad0270445d6783430d39672e9abe34a67d20546ad03db5ec0c8c6179c5f98524a83546d6e1790b1550284902bf0a161c998a82301ddbbbfb33d845a708
7
+ data.tar.gz: d0c64c701e183d1bb01b28af7577c237a5ec0d26607ed1856e1c2b6748efe48479c0f71b502db83a8e68c22faab220372541b16edf5d3e5b7ad0af86e933460e
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,17 @@
1
1
  inherit_from: https://gitlab.com/snippets/1744945/raw
2
2
 
3
3
  AllCops:
4
- TargetRubyVersion: 2.1
4
+ TargetRubyVersion: 2.3
5
5
  Exclude:
6
6
  - "bin/stubs/*"
7
7
  DisplayCopNames:
8
8
  Enabled: true
9
+
10
+ RSpec/ContextWording:
11
+ Enabled: false
12
+ RSpec/NestedGroups:
13
+ Max: 4
14
+ RSpec/ReturnFromStub:
15
+ Enabled: false
16
+ Style/EmptyCaseCondition:
17
+ Enabled: false
@@ -3,6 +3,7 @@ rvm:
3
3
  - 2.3
4
4
  - 2.4
5
5
  - 2.5
6
+ - 2.6
6
7
  branches:
7
8
  only:
8
9
  - master
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
 
data/Rakefile CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env rake
2
1
  require "bundler/gem_tasks"
3
2
  require "rspec/core/rake_task"
4
3
 
@@ -46,9 +46,7 @@ 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]}'"
@@ -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
@@ -86,9 +82,7 @@ when "folders"
86
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.3.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,9 +24,9 @@ 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
32
  gem.add_development_dependency "pry-byebug"
@@ -27,7 +27,7 @@ module Email::Mboxrd
27
27
 
28
28
  def date
29
29
  parsed.date
30
- rescue
30
+ rescue StandardError
31
31
  nil
32
32
  end
33
33
 
@@ -52,7 +52,7 @@ module Email::Mboxrd
52
52
  return parsed.envelope_from if parsed.envelope_from
53
53
  return parsed.return_path if parsed.return_path
54
54
 
55
- return ""
55
+ ""
56
56
  end
57
57
 
58
58
  def from
@@ -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,6 +47,7 @@ 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?
49
51
  Imap::Backup.logger.debug "[#{folder.name}] running backup"
50
52
  serializer.set_uid_validity(folder.uid_validity)
51
53
  Downloader.new(folder, serializer).run
@@ -59,7 +61,9 @@ module Imap::Backup
59
61
  new_name = serializer.set_uid_validity(folder.uid_validity)
60
62
  old_name = serializer.folder
61
63
  if new_name
62
- Imap::Backup.logger.debug "Backup '#{old_name}' renamed and restored to '#{new_name}'"
64
+ Imap::Backup.logger.debug(
65
+ "Backup '#{old_name}' renamed and restored to '#{new_name}'"
66
+ )
63
67
  new_serializer = Serializer::Mbox.new(local_path, new_name)
64
68
  new_folder = Account::Folder.new(self, new_name)
65
69
  new_folder.create
@@ -87,6 +91,7 @@ module Imap::Backup
87
91
 
88
92
  def imap
89
93
  return @imap unless @imap.nil?
94
+
90
95
  options = provider_options
91
96
  Imap::Backup.logger.debug(
92
97
  "Creating IMAP instance: #{server}, options: #{options.inspect}"
@@ -121,7 +126,8 @@ module Imap::Backup
121
126
  end
122
127
 
123
128
  def backup_folders
124
- return @backup_folders if @backup_folders && (@backup_folders.size > 0)
129
+ return @backup_folders if @backup_folders && !@backup_folders.empty?
130
+
125
131
  (folders || []).map { |f| {name: f.name} }
126
132
  end
127
133
 
@@ -143,6 +149,7 @@ module Imap::Backup
143
149
  def server
144
150
  return @server if @server
145
151
  return nil if provider.nil?
152
+
146
153
  @server = provider.host
147
154
  end
148
155
 
@@ -156,6 +163,7 @@ module Imap::Backup
156
163
  # in the reference.
157
164
  def provider_root
158
165
  return @provider_root if @provider_root
166
+
159
167
  root_info = imap.list("", "")[0]
160
168
  @provider_root = root_info.name
161
169
  end
@@ -3,10 +3,12 @@ require "forwardable"
3
3
  module Imap::Backup
4
4
  module Account; end
5
5
 
6
+ class FolderNotFound < StandardError; end
7
+
6
8
  class Account::Folder
7
9
  extend Forwardable
8
10
 
9
- REQUESTED_ATTRIBUTES = ["RFC822", "FLAGS", "INTERNALDATE"].freeze
11
+ REQUESTED_ATTRIBUTES = %w[RFC822 FLAGS INTERNALDATE].freeze
10
12
 
11
13
  attr_reader :connection
12
14
  attr_reader :name
@@ -27,12 +29,13 @@ module Imap::Backup
27
29
  def exist?
28
30
  examine
29
31
  true
30
- rescue Net::IMAP::NoResponseError => e
32
+ rescue FolderNotFound
31
33
  false
32
34
  end
33
35
 
34
36
  def create
35
37
  return if exist?
38
+
36
39
  imap.create(name)
37
40
  end
38
41
 
@@ -47,8 +50,7 @@ module Imap::Backup
47
50
  def uids
48
51
  examine
49
52
  imap.uid_search(["ALL"]).sort
50
- rescue Net::IMAP::NoResponseError
51
- Imap::Backup.logger.warn "Folder '#{name}' does not exist"
53
+ rescue FolderNotFound
52
54
  []
53
55
  end
54
56
 
@@ -56,12 +58,12 @@ module Imap::Backup
56
58
  examine
57
59
  fetch_data_items = imap.uid_fetch([uid.to_i], REQUESTED_ATTRIBUTES)
58
60
  return nil if fetch_data_items.nil?
61
+
59
62
  fetch_data_item = fetch_data_items[0]
60
63
  attributes = fetch_data_item.attr
61
64
  attributes["RFC822"].force_encoding("utf-8")
62
65
  attributes
63
- rescue Net::IMAP::NoResponseError
64
- Imap::Backup.logger.warn "Folder '#{name}' does not exist"
66
+ rescue FolderNotFound
65
67
  nil
66
68
  end
67
69
 
@@ -76,6 +78,9 @@ module Imap::Backup
76
78
 
77
79
  def examine
78
80
  imap.examine(name)
81
+ rescue Net::IMAP::NoResponseError
82
+ Imap::Backup.logger.warn "Folder '#{name}' does not exist"
83
+ raise FolderNotFound, "Folder '#{name}' does not exist"
79
84
  end
80
85
 
81
86
  def extract_uid(response)
@@ -9,7 +9,7 @@ module Imap::Backup
9
9
  def run
10
10
  catch :done do
11
11
  loop do
12
- system("clear")
12
+ Kernel.system("clear")
13
13
  create_menu
14
14
  end
15
15
  end
@@ -46,12 +46,12 @@ module Imap::Backup
46
46
  def modify_email(menu)
47
47
  menu.choice("modify email") do
48
48
  username = Configuration::Asker.email(username)
49
- puts "username: #{username}"
49
+ Kernel.puts "username: #{username}"
50
50
  other_accounts = store.accounts.reject { |a| a == account }
51
51
  others = other_accounts.map { |a| a[:username] }
52
- puts "others: #{others.inspect}"
52
+ Kernel.puts "others: #{others.inspect}"
53
53
  if others.include?(username)
54
- puts "There is already an account set up with that email address"
54
+ Kernel.puts "There is already an account set up with that email address"
55
55
  else
56
56
  account[:username] = username
57
57
  if account[:server].nil? || (account[:server] == "")
@@ -89,7 +89,7 @@ module Imap::Backup
89
89
  a[:username] != account[:username] && a[:local_path] == p
90
90
  end
91
91
  if same
92
- puts "The path '#{p}' is used to backup " \
92
+ Kernel.puts "The path '#{p}' is used to backup " \
93
93
  "the account '#{same[:username]}'"
94
94
  false
95
95
  else
@@ -112,7 +112,7 @@ module Imap::Backup
112
112
  def test_connection(menu)
113
113
  menu.choice("test connection") do
114
114
  result = Configuration::ConnectionTester.test(account)
115
- puts result
115
+ Kernel.puts result
116
116
  highline.ask "Press a key "
117
117
  end
118
118
  end
@@ -141,7 +141,7 @@ module Imap::Backup
141
141
  def default_server(username)
142
142
  provider = Email::Provider.for_address(username)
143
143
  if provider.provider == :default
144
- puts "Can't decide provider for email address '#{username}'"
144
+ Kernel.puts "Can't decide provider for email address '#{username}'"
145
145
  return nil
146
146
  end
147
147
  provider.host
@@ -2,7 +2,7 @@ module Imap::Backup
2
2
  module Configuration; end
3
3
 
4
4
  class Configuration::Asker < Struct.new(:highline)
5
- EMAIL_MATCHER = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]+$/i
5
+ EMAIL_MATCHER = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]+$/i.freeze
6
6
 
7
7
  def initialize(highline)
8
8
  super
@@ -24,6 +24,7 @@ module Imap::Backup
24
24
  return nil if !highline.agree(
25
25
  "the password and confirmation did not match.\nContinue? (y/n) "
26
26
  )
27
+
27
28
  return self.password
28
29
  end
29
30
  password
@@ -7,7 +7,7 @@ module Imap::Backup
7
7
  "Connection successful"
8
8
  rescue Net::IMAP::NoResponseError
9
9
  "No response"
10
- rescue Exception => e
10
+ rescue StandardError => e
11
11
  "Unexpected error: #{e}"
12
12
  end
13
13
  end
@@ -21,9 +21,11 @@ module Imap::Backup
21
21
  return
22
22
  end
23
23
 
24
+ remove_missing
25
+
24
26
  catch :done do
25
27
  loop do
26
- system("clear")
28
+ Kernel.system("clear")
27
29
  show_menu
28
30
  end
29
31
  end
@@ -44,21 +46,46 @@ module Imap::Backup
44
46
  def add_folders(menu)
45
47
  folders.each do |folder|
46
48
  name = folder.name
47
- mark = is_selected?(name) ? "+" : "-"
49
+ mark = selected?(name) ? "+" : "-"
48
50
  menu.choice("#{mark} #{name}") do
49
51
  toggle_selection name
50
52
  end
51
53
  end
52
54
  end
53
55
 
54
- def is_selected?(folder_name)
56
+ def selected?(folder_name)
55
57
  backup_folders = account[:folders]
56
58
  return false if backup_folders.nil?
59
+
57
60
  backup_folders.find { |f| f[:name] == folder_name }
58
61
  end
59
62
 
63
+ def remove_missing
64
+ removed = []
65
+ backup_folders = []
66
+ account[:folders].each do |f|
67
+ found = folders.find { |folder| folder.name == f[:name] }
68
+ if found
69
+ backup_folders << f
70
+ else
71
+ removed << f[:name]
72
+ end
73
+ end
74
+
75
+ return if removed.empty?
76
+
77
+ account[:folders] = backup_folders
78
+ account[:modified] = true
79
+
80
+ Kernel.puts <<~MESSAGE
81
+ The following folders have been removed: #{removed.join(', ')}
82
+ MESSAGE
83
+
84
+ highline.ask "Press a key "
85
+ end
86
+
60
87
  def toggle_selection(folder_name)
61
- if is_selected?(folder_name)
88
+ if selected?(folder_name)
62
89
  changed = account[:folders].reject! { |f| f[:name] == folder_name }
63
90
  account[:modified] = true if changed
64
91
  else
@@ -70,7 +97,7 @@ module Imap::Backup
70
97
 
71
98
  def connection
72
99
  @connection ||= Account::Connection.new(account)
73
- rescue
100
+ rescue StandardError
74
101
  nil
75
102
  end
76
103
 
@@ -10,6 +10,7 @@ module Imap::Backup
10
10
 
11
11
  def setup_logging
12
12
  return if !config_exists?
13
+
13
14
  Imap::Backup.setup_logging config
14
15
  end
15
16
 
@@ -25,6 +26,7 @@ module Imap::Backup
25
26
 
26
27
  def config
27
28
  return @config if @config
29
+
28
30
  if !config_exists?
29
31
  path = Configuration::Store.default_pathname
30
32
  raise ConfigurationNotFound, "Configuration file '#{path}' not found"
@@ -13,7 +13,7 @@ module Imap::Backup
13
13
  Imap::Backup.setup_logging config
14
14
  catch :done do
15
15
  loop do
16
- system("clear")
16
+ Kernel.system("clear")
17
17
  show_menu
18
18
  end
19
19
  end
@@ -40,6 +40,7 @@ module Imap::Backup
40
40
  def account_items(menu)
41
41
  config.accounts.each do |account|
42
42
  next if account[:delete]
43
+
43
44
  item = account[:username].clone
44
45
  item << " *" if account[:modified]
45
46
  menu.choice(item) do