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.
- checksums.yaml +7 -0
- data/.travis.yml +1 -0
- data/Rakefile +0 -5
- data/imap-backup.gemspec +5 -5
- data/lib/email/mboxrd/message.rb +1 -1
- data/lib/imap/backup.rb +3 -1
- data/lib/imap/backup/account/connection.rb +9 -7
- data/lib/imap/backup/account/folder.rb +6 -4
- data/lib/imap/backup/configuration/account.rb +9 -7
- data/lib/imap/backup/configuration/asker.rb +7 -5
- data/lib/imap/backup/configuration/connection_tester.rb +5 -3
- data/lib/imap/backup/configuration/folder_chooser.rb +10 -8
- data/lib/imap/backup/configuration/list.rb +8 -6
- data/lib/imap/backup/configuration/setup.rb +7 -5
- data/lib/imap/backup/configuration/store.rb +7 -5
- data/lib/imap/backup/serializer/base.rb +2 -1
- data/lib/imap/backup/serializer/directory.rb +5 -3
- data/lib/imap/backup/serializer/mbox.rb +10 -5
- data/lib/imap/backup/utils.rb +23 -30
- data/lib/imap/backup/version.rb +7 -7
- data/spec/spec_helper.rb +10 -27
- data/spec/support/higline_test_helpers.rb +8 -0
- data/spec/support/silence_logging.rb +7 -0
- data/spec/unit/account/connection_spec.rb +1 -1
- data/spec/unit/account/folder_spec.rb +33 -52
- data/spec/unit/configuration/asker_spec.rb +46 -5
- data/spec/unit/configuration/folder_chooser_spec.rb +62 -102
- data/spec/unit/configuration/list_spec.rb +40 -48
- data/spec/unit/configuration/setup_spec.rb +41 -62
- data/spec/unit/configuration/store_spec.rb +107 -99
- data/spec/unit/downloader_spec.rb +8 -8
- data/spec/unit/email/mboxrd/message_spec.rb +9 -19
- data/spec/unit/serializer/base_spec.rb +7 -5
- data/spec/unit/serializer/directory_spec.rb +30 -38
- data/spec/unit/serializer/mbox_spec.rb +64 -58
- data/spec/unit/utils_spec.rb +67 -41
- metadata +107 -141
checksums.yaml
ADDED
@@ -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
|
data/.travis.yml
CHANGED
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
|
data/imap-backup.gemspec
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
|
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.
|
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
|
|
data/lib/email/mboxrd/message.rb
CHANGED
data/lib/imap/backup.rb
CHANGED
@@ -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
|
-
|
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
|
4
|
-
|
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 =
|
21
|
-
s =
|
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 =
|
29
|
-
s =
|
30
|
-
d =
|
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
|
4
|
-
|
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
|
4
|
-
|
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
|
4
|
-
|
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
|
4
|
-
module
|
3
|
+
module Imap::Backup
|
4
|
+
module Configuration; end
|
5
|
+
|
6
|
+
module Configuration::ConnectionTester
|
5
7
|
def self.test(account)
|
6
|
-
|
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
|
4
|
-
|
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 =
|
13
|
+
@connection = Account::Connection.new(@account)
|
12
14
|
rescue => e
|
13
|
-
|
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
|
-
|
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
|
4
|
-
|
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
|
9
|
-
raise
|
10
|
+
if not Configuration::Store.exist?
|
11
|
+
raise ConfigurationNotFound.new("Configuration file '#{Configuration::Store.default_pathname}' not found")
|
10
12
|
end
|
11
|
-
@config =
|
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 =
|
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
|
6
|
-
|
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 ||=
|
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,
|
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
|
6
|
-
|
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
|
-
|
25
|
+
Utils.check_permissions path, 0700
|
24
26
|
end
|
25
27
|
if File.exist?(@pathname)
|
26
|
-
|
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
|
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
|
-
|
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
|
5
|
-
|
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
|
-
|
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
|
6
|
-
|
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
|
-
|
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
|
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
|
-
|
60
|
+
Utils.make_folder(@path, mbox_relative_path, Serializer::DIRECTORY_PERMISSIONS)
|
56
61
|
end
|
57
62
|
|
58
63
|
def exist?
|