imap-backup 1.0.17 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1abac89e145c96edf61e40e5d5efc035c26e71fd
4
- data.tar.gz: 793eb63a28cbf0e3fd3921c7ce72eeee4663ff2d
3
+ metadata.gz: b55658c2e579a76e29322a69048814b31069926e
4
+ data.tar.gz: 43aa7303ba81aaa1318bcb208be7a8ada52437a9
5
5
  SHA512:
6
- metadata.gz: a469154e6ea6767d045dafa2c52ca04abd649e682fcffb992f4ec9c2ce399a569589d0dd2dfd75957d0757ac165f8e1439915f2e2ed0e0df6b93739e1e2ffe4a
7
- data.tar.gz: 0372d03f497dcc3f2cfe1806bc27f530141ccb73618a99e1f4ec37e05d518b7bf6b067cfac466a94967a4753afb37ff52193612fef2446b9c9c44a068e96caa6
6
+ metadata.gz: 19480ec2c72149a8fefc0eeb6930b673072940b92fa6deae405b2318ab3491894dd1b1e29118ac59640561b8b2396d3a21fb4ff82cc8f40ad0830a375a5bed52
7
+ data.tar.gz: 1e9d3f8d900c3eae69e64484d88e506816a4a791b3f260caab01642ffe53f79ce171f3246cf762e05e81f4390108c663e9b4f4f2a5b0e44169325aeae0dc512f
data/.travis.yml CHANGED
@@ -2,8 +2,13 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
- - 2.1.1
5
+ - 2.1.5
6
+ - 2.2.4
7
+ - 2.3.0
6
8
  branches:
7
9
  only:
8
10
  - master
11
+ before_install:
12
+ - gem update --system
13
+ - gem update bundler
9
14
  script: "bundle exec rake spec"
data/README.md CHANGED
@@ -1,4 +1,8 @@
1
- # imap-backup [![Build Status](https://secure.travis-ci.org/joeyates/imap-backup.png)][Continuous Integration]
1
+ [![Build Status](https://secure.travis-ci.org/joeyates/imap-backup.png)][Continuous Integration]
2
+ [![Source Analysis](https://codeclimate.com/github/joeyates/imap-backup/badges/gpa.svg)](https://codeclimate.com/github/joeyates/imap-backup)
3
+ [![Test Coverage](https://codeclimate.com/github/joeyates/imap-backup/badges/coverage.svg)](https://codeclimate.com/github/joeyates/imap-backup/coverage)
4
+
5
+ # imap-backup
2
6
 
3
7
  *Backup GMail (or other IMAP) accounts to disk*
4
8
 
@@ -71,6 +75,35 @@ It connects to GMail by default, but you can also specify a server:
71
75
  }
72
76
  ```
73
77
 
78
+ ## Connection options
79
+
80
+ You can override the parameters passed to `Net::IMAP` with `connection_options`.
81
+
82
+ Specifically, if you are using a self-signed certificate and get SSL errors, e.g.
83
+ `certificate verify failed`, you can choose to not verify the TLS connection:
84
+
85
+ ```json
86
+ {
87
+ "accounts":
88
+ [
89
+ {
90
+ "username": "my.user@gmail.com",
91
+ "password": "secret",
92
+ "server": "my.imap.example.com",
93
+ "local_path": "/path/to/backup/root",
94
+ "folders": [
95
+ {"name": "[Gmail]/All Mail"},
96
+ {"name": "my_folder"}
97
+ ],
98
+ "connection_options": {
99
+ "ssl": {"verify_mode": 0},
100
+ "port": 993
101
+ }
102
+ }
103
+ ]
104
+ }
105
+ ```
106
+
74
107
  ## Google Apps
75
108
 
76
109
  * Enable IMAP access to your account via the GMail interface (Settings/Forwarding and POP/IMAP),
data/bin/imap-backup CHANGED
@@ -7,11 +7,11 @@ $:.unshift(File.expand_path('../../lib/', __FILE__))
7
7
  require 'imap/backup'
8
8
 
9
9
  KNOWN_COMMANDS = [
10
- {:name => 'help', :help => 'Show usage'},
11
10
  {:name => 'setup', :help => 'Create/edit the configuration file'},
12
11
  {:name => 'backup', :help => 'Do the backup (default)'},
13
12
  {:name => 'folders', :help => 'List folders for all (or selected) accounts'},
14
13
  {:name => 'status', :help => 'List count of non backed-up emails per folder'},
14
+ {:name => 'help', :help => 'Show usage'},
15
15
  ]
16
16
 
17
17
  options = {:command => 'backup'}
@@ -27,7 +27,7 @@ opts = OptionParser.new do |opts|
27
27
  opts.separator 'Common options:'
28
28
 
29
29
  opts.on('-a', '--accounts ACCOUNT1[,ACCOUNT2,...]', Array, 'only these accounts') do |account|
30
- options[:account] = account
30
+ options[:accounts] = account
31
31
  end
32
32
 
33
33
  opts.on_tail("-h", "--help", "Show usage") do
@@ -60,6 +60,8 @@ end
60
60
  configuration.setup_logging
61
61
 
62
62
  case options[:command]
63
+ when 'setup'
64
+ Imap::Backup::Configuration::Setup.new.run
63
65
  when 'backup'
64
66
  configuration.each_connection do |connection|
65
67
  connection.run_backup
@@ -74,8 +76,6 @@ when 'folders'
74
76
  end
75
77
  folders.each { |f| puts "\t" + f.name }
76
78
  end
77
- when 'setup'
78
- Imap::Backup::Configuration::Setup.new.run
79
79
  when 'status'
80
80
  configuration.each_connection do |connection|
81
81
  puts connection.username
data/imap-backup.gemspec CHANGED
@@ -18,8 +18,14 @@ Gem::Specification.new do |gem|
18
18
 
19
19
  gem.add_runtime_dependency 'rake'
20
20
  gem.add_runtime_dependency 'highline'
21
- gem.add_runtime_dependency 'mail'
21
+ if RUBY_VERSION > '2'
22
+ gem.add_runtime_dependency 'mail'
23
+ else
24
+ gem.add_runtime_dependency 'mime-types', '~> 2.6'
25
+ gem.add_runtime_dependency 'mail', '~> 2.6.3'
26
+ end
22
27
 
28
+ gem.add_development_dependency 'codeclimate-test-reporter', '~> 0.4.8'
23
29
  gem.add_development_dependency 'rspec', '>= 3.0.0'
24
30
  gem.add_development_dependency 'simplecov'
25
31
  end
@@ -0,0 +1,40 @@
1
+ module Email; end
2
+
3
+ class Email::Provider
4
+ def self.for_address(address)
5
+ case
6
+ when address.end_with?('@gmail.com')
7
+ new(:gmail)
8
+ when address.end_with?('@fastmail.fm')
9
+ new(:fastmail)
10
+ else
11
+ new(:default)
12
+ end
13
+ end
14
+
15
+ attr_reader :provider
16
+
17
+ def initialize(provider)
18
+ @provider = provider
19
+ end
20
+
21
+ def options
22
+ case provider
23
+ when :gmail
24
+ {port: 993, ssl: true}
25
+ when :fastmail
26
+ {port: 993, ssl: true}
27
+ else
28
+ {port: 993, ssl: true}
29
+ end
30
+ end
31
+
32
+ def host
33
+ case provider
34
+ when :gmail
35
+ 'imap.gmail.com'
36
+ when :fastmail
37
+ 'mail.messagingengine.com'
38
+ end
39
+ end
40
+ end
data/lib/imap/backup.rb CHANGED
@@ -15,6 +15,7 @@ require 'imap/backup/serializer/base'
15
15
  require 'imap/backup/serializer/directory'
16
16
  require 'imap/backup/serializer/mbox'
17
17
  require 'imap/backup/version'
18
+ require 'email/provider'
18
19
 
19
20
  require 'logger'
20
21
 
@@ -6,21 +6,23 @@ module Imap::Backup
6
6
  class Account::Connection
7
7
  attr_reader :username
8
8
  attr_reader :local_path
9
+ attr_reader :connection_options
9
10
 
10
11
  def initialize(options)
11
12
  @username, @password = options[:username], options[:password]
12
13
  @local_path = options[:local_path]
13
14
  @backup_folders = options[:folders]
14
15
  @server = options[:server]
16
+ @connection_options = options[:connection_options] || {}
15
17
  @folders = nil
18
+ create_account_folder
16
19
  end
17
20
 
18
21
  def folders
19
22
  return @folders if @folders
20
- root = root_for(username)
21
- @folders = imap.list(root, '*')
23
+ @folders = imap.list('', '*')
22
24
  if @folders.nil?
23
- Imap::Backup.logger.warn "Unable to get folder list for account #{username}, (root '#{root}'"
25
+ Imap::Backup.logger.warn "Unable to get folder list for account #{username}"
24
26
  end
25
27
  @folders
26
28
  end
@@ -37,12 +39,9 @@ module Imap::Backup
37
39
  Imap::Backup.logger.debug "Running backup of account: #{username}"
38
40
  # start the connection so we get logging messages in the right order
39
41
  imap
40
- backup_folders.each do |folder|
41
- Imap::Backup.logger.debug "[#{folder[:name]}] running backup"
42
- f = Account::Folder.new(self, folder[:name])
43
- s = Serializer::Mbox.new(local_path, folder[:name])
44
- d = Downloader.new(f, s)
45
- d.run
42
+ each_folder do |folder, serializer|
43
+ Imap::Backup.logger.debug "[#{folder.name}] running backup"
44
+ Downloader.new(folder, serializer).run
46
45
  end
47
46
  end
48
47
 
@@ -50,13 +49,9 @@ module Imap::Backup
50
49
  imap.disconnect
51
50
  end
52
51
 
53
- def server
54
- @server ||= host_for(username)
55
- end
56
-
57
52
  def imap
58
53
  return @imap unless @imap.nil?
59
- options = options_for(server)
54
+ options = provider_options
60
55
  Imap::Backup.logger.debug "Creating IMAP instance: #{server}, options: #{options.inspect}"
61
56
  @imap = Net::IMAP.new(server, options)
62
57
  Imap::Backup.logger.debug "Logging in: #{username}/#{masked_password}"
@@ -67,6 +62,22 @@ module Imap::Backup
67
62
 
68
63
  private
69
64
 
65
+ def each_folder
66
+ backup_folders.each do |folder_info|
67
+ folder = Account::Folder.new(self, folder_info[:name])
68
+ serializer = Serializer::Mbox.new(local_path, folder_info[:name])
69
+ yield folder, serializer
70
+ end
71
+ end
72
+
73
+ def create_account_folder
74
+ Utils.make_folder(
75
+ Configuration::Store::CONFIGURATION_DIRECTORY,
76
+ File.basename(local_path),
77
+ Serializer::DIRECTORY_PERMISSIONS
78
+ )
79
+ end
80
+
70
81
  def password
71
82
  @password
72
83
  end
@@ -80,35 +91,18 @@ module Imap::Backup
80
91
  (folders || []).map { |f| {:name => f.name} }
81
92
  end
82
93
 
83
- def host_for(username)
84
- case username
85
- when /@gmail\.com/
86
- 'imap.gmail.com'
87
- when /@fastmail\.fm/
88
- 'mail.messagingengine.com'
89
- end
94
+ def provider
95
+ @provider ||= Email::Provider.for_address(username)
90
96
  end
91
97
 
92
- def root_for(username)
93
- case username
94
- when /@gmail\.com/
95
- '/'
96
- when /@fastmail\.fm/
97
- 'INBOX'
98
- else
99
- '/'
100
- end
98
+ def server
99
+ return @server if @server
100
+ return nil if provider.nil?
101
+ @server = provider.host
101
102
  end
102
103
 
103
- def options_for(server)
104
- case server
105
- when 'imap.gmail.com'
106
- {:port => 993, :ssl => true}
107
- when 'mail.messagingengine.com'
108
- {:port => 993, :ssl => true}
109
- else
110
- {:port => 993, :ssl => true}
111
- end
104
+ def provider_options
105
+ provider.options.merge(connection_options)
112
106
  end
113
107
  end
114
108
  end
@@ -138,12 +138,12 @@ Account:
138
138
  end
139
139
 
140
140
  def default_server(username)
141
- case username
142
- when /@gmail\.com/
143
- 'imap.gmail.com'
144
- when /@fastmail\.fm/
145
- 'mail.messagingengine.com'
141
+ provider = Email::Provider.for_address(username)
142
+ if provider.provider == :default
143
+ puts "Can't decide provider for email address '#{username}'"
144
+ return nil
146
145
  end
146
+ provider.host
147
147
  end
148
148
  end
149
149
  end
@@ -27,9 +27,6 @@ module Imap::Backup
27
27
  end
28
28
 
29
29
  def save
30
- if File.directory?(path)
31
- Utils.check_permissions path, 0700
32
- end
33
30
  mkdir_private path
34
31
  remove_modified_flags
35
32
  remove_deleted_accounts
@@ -1,6 +1,4 @@
1
1
  # encoding: utf-8
2
- require 'rubygems' if RUBY_VERSION < '1.9'
3
- require 'json'
4
2
 
5
3
  module Imap::Backup
6
4
  class Downloader
@@ -33,6 +33,10 @@ module Imap::Backup
33
33
  Imap::Backup.logger.debug "[#{folder}] message #{uid} already downloaded - skipping"
34
34
  return
35
35
  end
36
+
37
+ # invalidate cache
38
+ @uids = nil
39
+
36
40
  body = message['RFC822']
37
41
  mboxrd_message = Email::Mboxrd::Message.new(body)
38
42
  mbox = imap = nil
@@ -2,7 +2,7 @@ module Imap; end
2
2
 
3
3
  module Imap::Backup
4
4
  MAJOR = 1
5
- MINOR = 0
6
- REVISION = 17
5
+ MINOR = 1
6
+ REVISION = 0
7
7
  VERSION = [MAJOR, MINOR, REVISION].map(&:to_s).join('.')
8
8
  end
data/spec/spec_helper.rb CHANGED
@@ -1,18 +1,17 @@
1
+ require "codeclimate-test-reporter"
1
2
  require 'rspec'
3
+
4
+ CodeClimate::TestReporter.start
5
+
2
6
  spec_path = File.dirname(__FILE__)
3
7
  $LOAD_PATH << File.expand_path('../lib', spec_path)
4
8
 
5
9
  support_glob = File.join(spec_path, 'support', '**', '*.rb')
6
10
  Dir[support_glob].each { |f| require f }
7
11
 
8
- if RUBY_VERSION < '1.9'
9
- require 'rspec/autorun'
10
- else
11
- require 'simplecov'
12
- SimpleCov.start do
13
- add_filter '/spec/'
14
- add_filter '/vendor/'
15
- end
12
+ require 'simplecov'
13
+ SimpleCov.start do
14
+ add_filter '/spec/'
16
15
  end
17
16
 
18
17
  require 'imap/backup'
@@ -10,7 +10,7 @@ describe Imap::Backup::Account::Connection do
10
10
  {:name => backup_folder}
11
11
  end
12
12
 
13
- let(:imap) { double('Net::IMAP', :login => nil, :list => imap_folders) }
13
+ let(:imap) { double('Net::IMAP', :login => nil, :list => imap_folders, :disconnect => nil) }
14
14
  let(:imap_folders) { [] }
15
15
  let(:options) do
16
16
  {
@@ -26,21 +26,18 @@ describe Imap::Backup::Account::Connection do
26
26
 
27
27
  before do
28
28
  allow(Net::IMAP).to receive(:new).and_return(imap)
29
+ allow(Imap::Backup::Utils).to receive(:make_folder)
29
30
  end
30
31
 
31
32
  subject { described_class.new(options) }
32
33
 
33
- shared_examples 'connects to IMAP' do |options|
34
- options ||= {}
35
- username = options[:username] || 'username@gmail.com'
36
- server = options[:server] || 'imap.gmail.com'
37
-
34
+ shared_examples 'connects to IMAP' do
38
35
  it 'sets up the IMAP connection' do
39
- expect(Net::IMAP).to have_received(:new).with(server, {:port => 993, :ssl => true})
36
+ expect(Net::IMAP).to have_received(:new)
40
37
  end
41
38
 
42
39
  it 'logs in to the imap server' do
43
- expect(imap).to have_received(:login).with(username, 'password')
40
+ expect(imap).to have_received(:login)
44
41
  end
45
42
  end
46
43
 
@@ -55,37 +52,20 @@ describe Imap::Backup::Account::Connection do
55
52
  end
56
53
  end
57
54
 
58
- context 'server' do
59
- context 'with a supplied value' do
60
- before do
61
- options.merge!(:server => 'imap.example.com')
62
- end
63
-
64
- it 'uses the supplied value' do
65
- expect(subject.server).to eq('imap.example.com')
66
- end
67
- end
68
-
69
- context 'without a supplied value' do
70
- it 'uses the guesses the value' do
71
- expect(subject.server).to eq('imap.gmail.com')
72
- end
73
- end
55
+ it 'creates the path' do
56
+ subject.username
57
+ expect(Imap::Backup::Utils).to have_received(:make_folder)
74
58
  end
75
59
  end
76
60
 
77
- [
78
- ['GMail', 'user@gmail.com', 'imap.gmail.com'],
79
- ['Fastmail', 'user@fastmail.fm', 'mail.messagingengine.com'],
80
- ].each do |service, email_username, server|
81
- context service do
82
- let(:username) { email_username }
61
+ describe '#imap' do
62
+ before { @result = subject.imap }
83
63
 
84
- before { allow(imap).to receive(:disconnect) }
85
- before { subject.disconnect }
86
-
87
- include_examples 'connects to IMAP', {:username => email_username, :server => server}
64
+ it 'returns the IMAP connection' do
65
+ expect(@result).to eq(imap)
88
66
  end
67
+
68
+ include_examples 'connects to IMAP'
89
69
  end
90
70
 
91
71
  context '#folders' do
@@ -122,19 +102,8 @@ describe Imap::Backup::Account::Connection do
122
102
  end
123
103
  end
124
104
 
125
- context '#disconnect' do
126
- before { allow(imap).to receive(:disconnect) }
127
- before { subject.disconnect }
128
-
129
- it 'disconnects from the server' do
130
- expect(imap).to have_received(:disconnect)
131
- end
132
-
133
- include_examples 'connects to IMAP'
134
- end
135
-
136
105
  context '#run_backup' do
137
- let(:folder) { double('folder') }
106
+ let(:folder) { double('folder', name: 'folder') }
138
107
  let(:serializer) { double('serializer') }
139
108
  let(:downloader) { double('downloader', :run => nil) }
140
109
 
@@ -197,4 +166,14 @@ describe Imap::Backup::Account::Connection do
197
166
  end
198
167
  end
199
168
  end
169
+
170
+ context '#disconnect' do
171
+ before { subject.disconnect }
172
+
173
+ it 'disconnects from the server' do
174
+ expect(imap).to have_received(:disconnect)
175
+ end
176
+
177
+ include_examples 'connects to IMAP'
178
+ end
200
179
  end
@@ -48,7 +48,7 @@ module Imap::Backup
48
48
  it 'requires 1 parameter' do
49
49
  expect do
50
50
  described_class.new
51
- end.to raise_error(ArgumentError, /0 for 1/)
51
+ end.to raise_error(ArgumentError, /wrong number/)
52
52
  end
53
53
 
54
54
  it 'expects a higline' do
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Email::Provider do
4
+ describe '.for_address' do
5
+ context 'known providers' do
6
+ [['gmail.com', :gmail], ['fastmail.fm', :fastmail]].each do |domain, provider|
7
+ it "recognizes #{provider}" do
8
+ address = "foo@#{domain}"
9
+ expect(described_class.for_address(address).provider).to eq(provider)
10
+ end
11
+ end
12
+ end
13
+
14
+ context 'with unknown providers' do
15
+ it 'returns a default provider' do
16
+ expect(described_class.for_address('foo@unknown.com').provider).to eq(:default)
17
+ end
18
+ end
19
+ end
20
+
21
+ subject { described_class.new(:gmail) }
22
+
23
+ describe '#options' do
24
+ it 'returns options' do
25
+ expect(subject.options).to eq(port: 993, ssl: true)
26
+ end
27
+ end
28
+
29
+ describe '#host' do
30
+ it 'returns host' do
31
+ expect(subject.host).to eq('imap.gmail.com')
32
+ end
33
+ end
34
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imap-backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.17
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Yates
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-14 00:00:00.000000000 Z
11
+ date: 2016-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: codeclimate-test-reporter
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.4.8
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.4.8
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rspec
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -97,6 +111,7 @@ files:
97
111
  - bin/imap-backup
98
112
  - imap-backup.gemspec
99
113
  - lib/email/mboxrd/message.rb
114
+ - lib/email/provider.rb
100
115
  - lib/imap/backup.rb
101
116
  - lib/imap/backup/account/connection.rb
102
117
  - lib/imap/backup/account/folder.rb
@@ -129,6 +144,7 @@ files:
129
144
  - spec/unit/configuration/store_spec.rb
130
145
  - spec/unit/downloader_spec.rb
131
146
  - spec/unit/email/mboxrd/message_spec.rb
147
+ - spec/unit/email/provider_spec.rb
132
148
  - spec/unit/serializer/base_spec.rb
133
149
  - spec/unit/serializer/directory_spec.rb
134
150
  - spec/unit/serializer/mbox_spec.rb
@@ -152,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
168
  version: '0'
153
169
  requirements: []
154
170
  rubyforge_project:
155
- rubygems_version: 2.2.2
171
+ rubygems_version: 2.5.1
156
172
  signing_key:
157
173
  specification_version: 4
158
174
  summary: Backup GMail (or other IMAP) accounts to disk
@@ -173,6 +189,7 @@ test_files:
173
189
  - spec/unit/configuration/store_spec.rb
174
190
  - spec/unit/downloader_spec.rb
175
191
  - spec/unit/email/mboxrd/message_spec.rb
192
+ - spec/unit/email/provider_spec.rb
176
193
  - spec/unit/serializer/base_spec.rb
177
194
  - spec/unit/serializer/directory_spec.rb
178
195
  - spec/unit/serializer/mbox_spec.rb