imap-backup 1.0.9 → 1.0.10

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 (37) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +1 -0
  3. data/Rakefile +0 -5
  4. data/imap-backup.gemspec +5 -5
  5. data/lib/email/mboxrd/message.rb +1 -1
  6. data/lib/imap/backup.rb +3 -1
  7. data/lib/imap/backup/account/connection.rb +9 -7
  8. data/lib/imap/backup/account/folder.rb +6 -4
  9. data/lib/imap/backup/configuration/account.rb +9 -7
  10. data/lib/imap/backup/configuration/asker.rb +7 -5
  11. data/lib/imap/backup/configuration/connection_tester.rb +5 -3
  12. data/lib/imap/backup/configuration/folder_chooser.rb +10 -8
  13. data/lib/imap/backup/configuration/list.rb +8 -6
  14. data/lib/imap/backup/configuration/setup.rb +7 -5
  15. data/lib/imap/backup/configuration/store.rb +7 -5
  16. data/lib/imap/backup/serializer/base.rb +2 -1
  17. data/lib/imap/backup/serializer/directory.rb +5 -3
  18. data/lib/imap/backup/serializer/mbox.rb +10 -5
  19. data/lib/imap/backup/utils.rb +23 -30
  20. data/lib/imap/backup/version.rb +7 -7
  21. data/spec/spec_helper.rb +10 -27
  22. data/spec/support/higline_test_helpers.rb +8 -0
  23. data/spec/support/silence_logging.rb +7 -0
  24. data/spec/unit/account/connection_spec.rb +1 -1
  25. data/spec/unit/account/folder_spec.rb +33 -52
  26. data/spec/unit/configuration/asker_spec.rb +46 -5
  27. data/spec/unit/configuration/folder_chooser_spec.rb +62 -102
  28. data/spec/unit/configuration/list_spec.rb +40 -48
  29. data/spec/unit/configuration/setup_spec.rb +41 -62
  30. data/spec/unit/configuration/store_spec.rb +107 -99
  31. data/spec/unit/downloader_spec.rb +8 -8
  32. data/spec/unit/email/mboxrd/message_spec.rb +9 -19
  33. data/spec/unit/serializer/base_spec.rb +7 -5
  34. data/spec/unit/serializer/directory_spec.rb +30 -38
  35. data/spec/unit/serializer/mbox_spec.rb +64 -58
  36. data/spec/unit/utils_spec.rb +67 -41
  37. metadata +107 -141
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 21c07957777ccdca22ec2b9b98aa5ee23e03f9d4
4
+ data.tar.gz: 7f341ff59529f01e6d7c4ccb7eefa73610b68a57
5
+ SHA512:
6
+ metadata.gz: d9cbe70b6dbe617645a4a3fadbaa75a9fa73870b889d361adeb9a3cd9bcee91143736327db1050e6dc4c9383fabe9161413bb6061b5ce26831ed1c0897fa7c2f
7
+ data.tar.gz: ce740410c22cfea3f5119cd7a65196db97296d56416b662b575f049bba4e45669a66bccca78af52dcb915ab51ced4690e760246237df1ec85aa3ec10c7fb3669
@@ -4,4 +4,5 @@ rvm:
4
4
  - 1.9.2
5
5
  - 1.9.3
6
6
  - 2.0.0
7
+ - 2.1.1
7
8
  script: "bundle exec rake spec"
data/Rakefile CHANGED
@@ -14,9 +14,4 @@ if RUBY_VERSION < '1.9'
14
14
  t.rcov = true
15
15
  t.rcov_opts = ['--exclude', 'spec/,/gems/,vendor/']
16
16
  end
17
- else
18
- desc 'Run specs and create coverage output'
19
- RSpec::Core::RakeTask.new('spec:coverage') do |t|
20
- t.pattern = ['spec/gather_rspec_coverage.rb', 'spec/**/*_spec.rb']
21
- end
22
17
  end
@@ -1,18 +1,18 @@
1
1
  # -*- encoding: utf-8 -*-
2
- $:.unshift(File.expand_path('../lib/', __FILE__))
2
+ $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
3
3
  require 'imap/backup/version'
4
4
 
5
5
  Gem::Specification.new do |gem|
6
- gem.authors = ['Joe Yates']
7
- gem.email = ['joe.g.yates@gmail.com']
6
+ gem.name = 'imap-backup'
8
7
  gem.description = %q{Backup GMail, or any other IMAP email service, to disk.}
9
8
  gem.summary = %q{Backup GMail (or other IMAP) accounts to disk}
9
+ gem.authors = ['Joe Yates']
10
+ gem.email = ['joe.g.yates@gmail.com']
10
11
  gem.homepage = 'https://github.com/joeyates/imap-backup'
11
12
 
12
13
  gem.files = `git ls-files`.split($\)
13
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
14
15
  gem.test_files = gem.files.grep(%r{^spec/})
15
- gem.name = 'imap-backup'
16
16
  gem.require_paths = ['lib']
17
17
  gem.version = Imap::Backup::VERSION
18
18
 
@@ -25,7 +25,7 @@ module Email::Mboxrd
25
25
 
26
26
  def body
27
27
  mbox = @body.gsub(/\n(>*From)/, "\n>\\1")
28
- mbox + "\n" unless mbox.end_with?("\n")
28
+ mbox += "\n" unless mbox.end_with?("\n")
29
29
  mbox
30
30
  end
31
31
 
@@ -1,3 +1,5 @@
1
+ module Imap; end
2
+
1
3
  require 'imap/backup/utils'
2
4
  require 'imap/backup/account/connection'
3
5
  require 'imap/backup/account/folder'
@@ -30,6 +32,6 @@ module Imap::Backup
30
32
  end
31
33
 
32
34
  def self.logger
33
- Imap::Backup::Logger.instance.logger
35
+ Logger.instance.logger
34
36
  end
35
37
  end
@@ -1,7 +1,9 @@
1
1
  require 'net/imap'
2
2
 
3
- module Imap::Backup::Account
4
- class Connection
3
+ module Imap::Backup
4
+ module Account; end
5
+
6
+ class Account::Connection
5
7
  attr_reader :username, :local_path, :backup_folders, :server
6
8
 
7
9
  def initialize(options)
@@ -17,17 +19,17 @@ module Imap::Backup::Account
17
19
 
18
20
  def status
19
21
  backup_folders.map do |folder|
20
- f = Imap::Backup::Account::Folder.new(self, folder[:name])
21
- s = Imap::Backup::Serializer::Directory.new(local_path, folder[:name])
22
+ f = Account::Folder.new(self, folder[:name])
23
+ s = Serializer::Directory.new(local_path, folder[:name])
22
24
  {:name => folder[:name], :local => s.uids, :remote => f.uids}
23
25
  end
24
26
  end
25
27
 
26
28
  def run_backup
27
29
  backup_folders.each do |folder|
28
- f = Imap::Backup::Account::Folder.new(self, folder[:name])
29
- s = Imap::Backup::Serializer::Mbox.new(local_path, folder[:name])
30
- d = Imap::Backup::Downloader.new(f, s)
30
+ f = Account::Folder.new(self, folder[:name])
31
+ s = Serializer::Mbox.new(local_path, folder[:name])
32
+ d = Downloader.new(f, s)
31
33
  d.run
32
34
  end
33
35
  end
@@ -1,7 +1,9 @@
1
1
  # encoding: utf-8
2
2
 
3
- module Imap::Backup::Account
4
- class Folder
3
+ module Imap::Backup
4
+ module Account; end
5
+
6
+ class Account::Folder
5
7
  REQUESTED_ATTRIBUTES = ['RFC822', 'FLAGS', 'INTERNALDATE']
6
8
 
7
9
  def initialize(connection, folder)
@@ -12,7 +14,7 @@ module Imap::Backup::Account
12
14
  @connection.imap.examine(@folder)
13
15
  @connection.imap.uid_search(['ALL']).sort
14
16
  rescue Net::IMAP::NoResponseError => e
15
- warn "Folder '#{@folder}' does not exist"
17
+ Imap::Backup.logger.warn "Folder '#{@folder}' does not exist"
16
18
  []
17
19
  end
18
20
 
@@ -22,7 +24,7 @@ module Imap::Backup::Account
22
24
  message['RFC822'].force_encoding('utf-8') if RUBY_VERSION > '1.9'
23
25
  message
24
26
  rescue Net::IMAP::NoResponseError => e
25
- warn "Folder '#{@folder}' does not exist"
27
+ Imap::Backup.logger.warn "Folder '#{@folder}' does not exist"
26
28
  nil
27
29
  end
28
30
  end
@@ -1,7 +1,9 @@
1
1
  # encoding: utf-8
2
2
 
3
- module Imap::Backup::Configuration
4
- class Account < Struct.new(:store, :account, :highline)
3
+ module Imap::Backup
4
+ module Configuration; end
5
+
6
+ class Configuration::Account < Struct.new(:store, :account, :highline)
5
7
  def run
6
8
  catch :done do
7
9
  loop do
@@ -41,7 +43,7 @@ Account:
41
43
 
42
44
  def modify_email(menu)
43
45
  menu.choice('modify email') do
44
- username = Asker.email(username)
46
+ username = Configuration::Asker.email(username)
45
47
  puts "username: #{username}"
46
48
  others = store.data[:accounts].select { |a| a != account}.map { |a| a[:username] }
47
49
  puts "others: #{others.inspect}"
@@ -58,7 +60,7 @@ Account:
58
60
 
59
61
  def modify_password(menu)
60
62
  menu.choice('modify password') do
61
- password = Asker.password
63
+ password = Configuration::Asker.password
62
64
  if ! password.nil?
63
65
  account[:password] = password
64
66
  end
@@ -87,19 +89,19 @@ Account:
87
89
  true
88
90
  end
89
91
  end
90
- account[:local_path] = Asker.backup_path(account[:local_path], validator)
92
+ account[:local_path] = Configuration::Asker.backup_path(account[:local_path], validator)
91
93
  end
92
94
  end
93
95
 
94
96
  def choose_folders(menu)
95
97
  menu.choice('choose backup folders') do
96
- FolderChooser.new(account).run
98
+ Configuration::FolderChooser.new(account).run
97
99
  end
98
100
  end
99
101
 
100
102
  def test_connection(menu)
101
103
  menu.choice('test connection') do
102
- result = ConnectionTester.test(account)
104
+ result = Configuration::ConnectionTester.test(account)
103
105
  puts result
104
106
  highline.ask 'Press a key '
105
107
  end
@@ -1,7 +1,9 @@
1
1
  # encoding: utf-8
2
2
 
3
- module Imap::Backup::Configuration
4
- class Asker < Struct.new(:highline)
3
+ module Imap::Backup
4
+ module Configuration; end
5
+
6
+ class Configuration::Asker < Struct.new(:highline)
5
7
  EMAIL_MATCHER = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i
6
8
 
7
9
  def email(default = '')
@@ -33,15 +35,15 @@ module Imap::Backup::Configuration
33
35
  end
34
36
 
35
37
  def self.email(default = '')
36
- new(Setup.highline).email(default)
38
+ new(Configuration::Setup.highline).email(default)
37
39
  end
38
40
 
39
41
  def self.password
40
- new(Setup.highline).password
42
+ new(Configuration::Setup.highline).password
41
43
  end
42
44
 
43
45
  def self.backup_path(default, validator)
44
- new(Setup.highline).backup_path(default, validator)
46
+ new(Configuration::Setup.highline).backup_path(default, validator)
45
47
  end
46
48
  end
47
49
  end
@@ -1,9 +1,11 @@
1
1
  # encoding: utf-8
2
2
 
3
- module Imap::Backup::Configuration
4
- module ConnectionTester
3
+ module Imap::Backup
4
+ module Configuration; end
5
+
6
+ module Configuration::ConnectionTester
5
7
  def self.test(account)
6
- Imap::Backup::Account::Connection.new(account).imap
8
+ Account::Connection.new(account).imap
7
9
  return 'Connection successful'
8
10
  rescue Net::IMAP::NoResponseError
9
11
  return 'No response'
@@ -1,28 +1,30 @@
1
1
  # encoding: utf-8
2
2
 
3
- module Imap::Backup::Configuration
4
- class FolderChooser
3
+ module Imap::Backup
4
+ module Configuration; end
5
+
6
+ class Configuration::FolderChooser
5
7
  def initialize(account)
6
8
  @account = account
7
9
  end
8
10
 
9
11
  def run
10
12
  begin
11
- @connection = Imap::Backup::Account::Connection.new(@account)
13
+ @connection = Account::Connection.new(@account)
12
14
  rescue => e
13
- puts 'Connection failed'
14
- Setup.highline.ask 'Press a key '
15
+ Imap::Backup.logger.warn 'Connection failed'
16
+ Configuration::Setup.highline.ask 'Press a key '
15
17
  return
16
18
  end
17
19
  @folders = @connection.folders
18
20
  if @folders.nil?
19
- puts 'Unable to get folder list'
20
- Setup.highline.ask 'Press a key '
21
+ Imap::Backup.logger.warn 'Unable to get folder list'
22
+ Configuration::Setup.highline.ask 'Press a key '
21
23
  return
22
24
  end
23
25
  loop do
24
26
  system('clear')
25
- Setup.highline.choose do |menu|
27
+ Configuration::Setup.highline.choose do |menu|
26
28
  menu.header = 'Add/remove folders'
27
29
  menu.index = :number
28
30
  @folders.each do |folder|
@@ -1,14 +1,16 @@
1
1
  # encoding: utf-8
2
2
 
3
- module Imap::Backup::Configuration
4
- class List
3
+ module Imap::Backup
4
+ module Configuration; end
5
+
6
+ class Configuration::List
5
7
  attr_reader :accounts
6
8
 
7
9
  def initialize(accounts = nil)
8
- if not Imap::Backup::Configuration::Store.exist?
9
- raise Imap::Backup::ConfigurationNotFound.new("Configuration file '#{Imap::Backup::Configuration::Store.default_pathname}' not found")
10
+ if not Configuration::Store.exist?
11
+ raise ConfigurationNotFound.new("Configuration file '#{Configuration::Store.default_pathname}' not found")
10
12
  end
11
- @config = Imap::Backup::Configuration::Store.new
13
+ @config = Configuration::Store.new
12
14
 
13
15
  if accounts.nil?
14
16
  @accounts = @config.data[:accounts]
@@ -19,7 +21,7 @@ module Imap::Backup::Configuration
19
21
 
20
22
  def each_connection
21
23
  @accounts.each do |account|
22
- connection = Imap::Backup::Account::Connection.new(account)
24
+ connection = Account::Connection.new(account)
23
25
  yield connection
24
26
  connection.disconnect
25
27
  end
@@ -2,8 +2,10 @@
2
2
  require 'rubygems' if RUBY_VERSION < '1.9'
3
3
  require 'highline'
4
4
 
5
- module Imap::Backup::Configuration
6
- class Setup
5
+ module Imap::Backup
6
+ module Configuration; end
7
+
8
+ class Configuration::Setup
7
9
  class << self
8
10
  attr_accessor :highline
9
11
  end
@@ -21,7 +23,7 @@ module Imap::Backup::Configuration
21
23
  end
22
24
  end
23
25
  menu.choice('add account') do
24
- username = Asker.email
26
+ username = Configuration::Asker.email
25
27
  edit_account username
26
28
  end
27
29
  menu.choice('save and exit') do
@@ -38,7 +40,7 @@ module Imap::Backup::Configuration
38
40
  private
39
41
 
40
42
  def config
41
- @config ||= Imap::Backup::Configuration::Store.new
43
+ @config ||= Configuration::Store.new
42
44
  end
43
45
 
44
46
  def setup_logging
@@ -66,7 +68,7 @@ module Imap::Backup::Configuration
66
68
  if account.nil?
67
69
  account = add_account(username)
68
70
  end
69
- Account.new(config, account, Imap::Backup::Configuration::Setup.highline).run
71
+ Configuration::Account.new(config, account, Configuration::Setup.highline).run
70
72
  end
71
73
  end
72
74
  end
@@ -2,8 +2,10 @@
2
2
  require 'rubygems' if RUBY_VERSION < '1.9'
3
3
  require 'json'
4
4
 
5
- module Imap::Backup::Configuration
6
- class Store
5
+ module Imap::Backup
6
+ module Configuration; end
7
+
8
+ class Configuration::Store
7
9
  CONFIGURATION_DIRECTORY = File.expand_path('~/.imap-backup')
8
10
 
9
11
  attr_reader :data
@@ -20,10 +22,10 @@ module Imap::Backup::Configuration
20
22
  def initialize(pathname = self.class.default_pathname)
21
23
  @pathname = pathname
22
24
  if File.directory?(path)
23
- Imap::Backup::Utils.check_permissions path, 0700
25
+ Utils.check_permissions path, 0700
24
26
  end
25
27
  if File.exist?(@pathname)
26
- Imap::Backup::Utils.check_permissions @pathname, 0600
28
+ Utils.check_permissions @pathname, 0600
27
29
  @data = JSON.parse(File.read(@pathname), :symbolize_names => true)
28
30
  else
29
31
  @data = {:accounts => []}
@@ -57,7 +59,7 @@ module Imap::Backup::Configuration
57
59
  if ! File.directory?(path)
58
60
  FileUtils.mkdir path
59
61
  end
60
- if Imap::Backup::Utils::stat(path) != 0700
62
+ if Utils::stat(path) != 0700
61
63
  FileUtils.chmod 0700, path
62
64
  end
63
65
  end
@@ -4,10 +4,11 @@ module Imap::Backup
4
4
  module Serializer
5
5
  DIRECTORY_PERMISSIONS = 0700
6
6
  FILE_PERMISSIONS = 0600
7
+
7
8
  class Base
8
9
  def initialize(path, folder)
9
10
  @path, @folder = path, folder
10
- Imap::Backup::Utils.check_permissions(@path, DIRECTORY_PERMISSIONS)
11
+ Utils.check_permissions(@path, DIRECTORY_PERMISSIONS)
11
12
  end
12
13
  end
13
14
  end
@@ -1,11 +1,13 @@
1
1
  # encoding: utf-8
2
2
  require 'fileutils'
3
3
 
4
- module Imap::Backup::Serializer
5
- class Directory < Base
4
+ module Imap::Backup
5
+ module Serializer; end
6
+
7
+ class Serializer::Directory < Serializer::Base
6
8
  def initialize(path, folder)
7
9
  super
8
- Imap::Backup::Utils.make_folder(@path, @folder, DIRECTORY_PERMISSIONS)
10
+ Utils.make_folder(@path, @folder, Serializer::DIRECTORY_PERMISSIONS)
9
11
  end
10
12
 
11
13
  def uids
@@ -2,8 +2,10 @@
2
2
  require 'csv'
3
3
  require 'email/mboxrd/message'
4
4
 
5
- module Imap::Backup::Serializer
6
- class Mbox < Base
5
+ module Imap::Backup
6
+ module Serializer; end
7
+
8
+ class Serializer::Mbox < Serializer::Base
7
9
  def initialize(path, folder)
8
10
  super
9
11
  create_containing_directory
@@ -27,13 +29,16 @@ module Imap::Backup::Serializer
27
29
  def save(uid, message)
28
30
  uid = uid.to_s
29
31
  return if uids.include?(uid)
30
- message = Email::Mboxrd::Message.new(message['RFC822'])
32
+ body = message['RFC822']
33
+ mboxrd_message = Email::Mboxrd::Message.new(body)
31
34
  mbox = imap = nil
32
35
  begin
33
36
  mbox = File.open(mbox_pathname, 'ab')
34
37
  imap = File.open(imap_pathname, 'ab')
35
- mbox.write message.to_s
38
+ mbox.write mboxrd_message.to_s
36
39
  imap.write uid + "\n"
40
+ rescue ArgumentError => e
41
+ Imap::Backup.logger.warn "Failed to save message #{uid}:\n#{body}. #{e}"
37
42
  ensure
38
43
  mbox.close if mbox
39
44
  imap.close if imap
@@ -52,7 +57,7 @@ module Imap::Backup::Serializer
52
57
  def create_containing_directory
53
58
  mbox_relative_path = File.dirname(mbox_relative_pathname)
54
59
  return if mbox_relative_path == '.'
55
- Imap::Backup::Utils.make_folder(@path, mbox_relative_path, DIRECTORY_PERMISSIONS)
60
+ Utils.make_folder(@path, mbox_relative_path, Serializer::DIRECTORY_PERMISSIONS)
56
61
  end
57
62
 
58
63
  def exist?