imap-backup 1.0.5 → 1.0.6
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 +4 -4
- data/.gitignore +0 -1
- data/.travis.yml +1 -1
- data/Gemfile +0 -1
- data/README.md +0 -1
- data/Rakefile +4 -9
- data/bin/imap-backup +0 -1
- data/imap-backup.gemspec +0 -1
- data/lib/email/mboxrd/message.rb +31 -32
- data/lib/imap/backup.rb +17 -4
- data/lib/imap/backup/account/connection.rb +70 -64
- data/lib/imap/backup/account/folder.rb +21 -26
- data/lib/imap/backup/configuration/account.rb +127 -73
- data/lib/imap/backup/configuration/asker.rb +38 -31
- data/lib/imap/backup/configuration/connection_tester.rb +9 -14
- data/lib/imap/backup/configuration/folder_chooser.rb +43 -48
- data/lib/imap/backup/configuration/list.rb +19 -24
- data/lib/imap/backup/configuration/setup.rb +56 -51
- data/lib/imap/backup/configuration/store.rb +47 -52
- data/lib/imap/backup/downloader.rb +11 -14
- data/lib/imap/backup/serializer/base.rb +8 -11
- data/lib/imap/backup/serializer/directory.rb +36 -41
- data/lib/imap/backup/serializer/mbox.rb +83 -88
- data/lib/imap/backup/utils.rb +0 -3
- data/lib/imap/backup/version.rb +1 -2
- data/spec/gather_rspec_coverage.rb +0 -1
- data/spec/spec_helper.rb +2 -7
- data/spec/unit/account/connection_spec.rb +0 -1
- data/spec/unit/account/folder_spec.rb +0 -1
- data/spec/unit/configuration/account_spec.rb +207 -136
- data/spec/unit/configuration/asker_spec.rb +59 -85
- data/spec/unit/configuration/connection_tester_spec.rb +36 -26
- data/spec/unit/configuration/folder_chooser_spec.rb +3 -6
- data/spec/unit/configuration/list_spec.rb +0 -1
- data/spec/unit/configuration/setup_spec.rb +8 -9
- data/spec/unit/configuration/store_spec.rb +1 -4
- data/spec/unit/downloader_spec.rb +0 -1
- data/spec/unit/email/mboxrd/message_spec.rb +0 -1
- data/spec/unit/serializer/base_spec.rb +0 -1
- data/spec/unit/serializer/directory_spec.rb +0 -1
- data/spec/unit/serializer/mbox_spec.rb +0 -1
- data/spec/unit/utils_spec.rb +0 -1
- metadata +2 -2
@@ -1,40 +1,47 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
module Imap
|
4
|
-
|
5
|
-
|
6
|
-
module Asker
|
7
|
-
EMAIL_MATCHER = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i
|
3
|
+
module Imap::Backup::Configuration
|
4
|
+
class Asker < Struct.new(:highline)
|
5
|
+
EMAIL_MATCHER = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
7
|
+
def email(default = '')
|
8
|
+
highline.ask('email address: ') do |q|
|
9
|
+
q.default = default
|
10
|
+
q.readline = true
|
11
|
+
q.validate = EMAIL_MATCHER
|
12
|
+
q.responses[:not_valid] = 'Enter a valid email address '
|
13
|
+
end
|
14
|
+
end
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
16
|
+
def password
|
17
|
+
password = highline.ask('password: ') { |q| q.echo = false }
|
18
|
+
confirmation = highline.ask('repeat password: ') { |q| q.echo = false }
|
19
|
+
if password != confirmation
|
20
|
+
return nil unless highline.agree("the password and confirmation did not match.\nContinue? (y/n) ")
|
21
|
+
return self.password
|
22
|
+
end
|
23
|
+
password
|
24
|
+
end
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
35
|
-
end
|
26
|
+
def backup_path(default, validator)
|
27
|
+
highline.ask('backup directory: ') do |q|
|
28
|
+
q.default = default
|
29
|
+
q.readline = true
|
30
|
+
q.validate = validator
|
31
|
+
q.responses[:not_valid] = 'Choose a different directory '
|
36
32
|
end
|
37
33
|
end
|
34
|
+
|
35
|
+
def self.email(default = '')
|
36
|
+
new(Setup.highline).email(default)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.password
|
40
|
+
new(Setup.highline).password
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.backup_path(default, validator)
|
44
|
+
new(Setup.highline).backup_path(default, validator)
|
45
|
+
end
|
38
46
|
end
|
39
47
|
end
|
40
|
-
|
@@ -1,19 +1,14 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
module Imap
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
rescue Exception => e
|
13
|
-
return "Unexpected error: #{e}"
|
14
|
-
end
|
15
|
-
end
|
3
|
+
module Imap::Backup::Configuration
|
4
|
+
module ConnectionTester
|
5
|
+
def self.test(account)
|
6
|
+
Imap::Backup::Account::Connection.new(account).imap
|
7
|
+
return 'Connection successful'
|
8
|
+
rescue Net::IMAP::NoResponseError
|
9
|
+
return 'No response'
|
10
|
+
rescue Exception => e
|
11
|
+
return "Unexpected error: #{e}"
|
16
12
|
end
|
17
13
|
end
|
18
14
|
end
|
19
|
-
|
@@ -1,60 +1,55 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
module Imap
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
@account = account
|
9
|
-
end
|
3
|
+
module Imap::Backup::Configuration
|
4
|
+
class FolderChooser
|
5
|
+
def initialize(account)
|
6
|
+
@account = account
|
7
|
+
end
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
39
|
-
menu.choice("#{mark} #{name}") do
|
40
|
-
if found
|
41
|
-
@account[:folders].reject! { |f| f[:name] == name }
|
42
|
-
else
|
43
|
-
@account[:folders] << { :name => name }
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
menu.choice('return to the account menu') do
|
48
|
-
return
|
9
|
+
def run
|
10
|
+
begin
|
11
|
+
@connection = Imap::Backup::Account::Connection.new(@account)
|
12
|
+
rescue => e
|
13
|
+
puts 'Connection failed'
|
14
|
+
Setup.highline.ask 'Press a key '
|
15
|
+
return
|
16
|
+
end
|
17
|
+
@folders = @connection.folders
|
18
|
+
if @folders.nil?
|
19
|
+
puts 'Unable to get folder list'
|
20
|
+
Setup.highline.ask 'Press a key '
|
21
|
+
return
|
22
|
+
end
|
23
|
+
loop do
|
24
|
+
system('clear')
|
25
|
+
Setup.highline.choose do |menu|
|
26
|
+
menu.header = 'Add/remove folders'
|
27
|
+
menu.index = :number
|
28
|
+
@folders.each do |folder|
|
29
|
+
name = folder.name
|
30
|
+
found = @account[:folders].find { |f| f[:name] == name }
|
31
|
+
mark =
|
32
|
+
if found
|
33
|
+
'+'
|
34
|
+
else
|
35
|
+
'-'
|
49
36
|
end
|
50
|
-
|
51
|
-
|
37
|
+
menu.choice("#{mark} #{name}") do
|
38
|
+
if found
|
39
|
+
@account[:folders].reject! { |f| f[:name] == name }
|
40
|
+
else
|
41
|
+
@account[:folders] << { :name => name }
|
52
42
|
end
|
53
43
|
end
|
54
44
|
end
|
45
|
+
menu.choice('return to the account menu') do
|
46
|
+
return
|
47
|
+
end
|
48
|
+
menu.hidden('quit') do
|
49
|
+
return
|
50
|
+
end
|
55
51
|
end
|
56
52
|
end
|
57
53
|
end
|
58
54
|
end
|
59
55
|
end
|
60
|
-
|
@@ -1,33 +1,28 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
module Imap
|
4
|
-
|
5
|
-
|
6
|
-
class List
|
7
|
-
attr_reader :accounts
|
3
|
+
module Imap::Backup::Configuration
|
4
|
+
class List
|
5
|
+
attr_reader :accounts
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
7
|
+
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
|
+
end
|
11
|
+
@config = Imap::Backup::Configuration::Store.new
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
13
|
+
if accounts.nil?
|
14
|
+
@accounts = @config.data[:accounts]
|
15
|
+
else
|
16
|
+
@accounts = @config.data[:accounts].select{ |account| accounts.include?(account[:username]) }
|
17
|
+
end
|
18
|
+
end
|
21
19
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
28
|
-
end
|
20
|
+
def each_connection
|
21
|
+
@accounts.each do |account|
|
22
|
+
connection = Imap::Backup::Account::Connection.new(account)
|
23
|
+
yield connection
|
24
|
+
connection.disconnect
|
29
25
|
end
|
30
26
|
end
|
31
27
|
end
|
32
28
|
end
|
33
|
-
|
@@ -2,66 +2,71 @@
|
|
2
2
|
require 'rubygems' if RUBY_VERSION < '1.9'
|
3
3
|
require 'highline'
|
4
4
|
|
5
|
-
module Imap
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
12
|
-
self.highline = HighLine.new
|
13
|
-
|
14
|
-
def initialize
|
15
|
-
@config = Imap::Backup::Configuration::Store.new
|
16
|
-
end
|
5
|
+
module Imap::Backup::Configuration
|
6
|
+
class Setup
|
7
|
+
class << self
|
8
|
+
attr_accessor :highline
|
9
|
+
end
|
10
|
+
self.highline = HighLine.new
|
17
11
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
28
|
-
menu.choice('add account') do
|
29
|
-
username = Asker.email
|
30
|
-
edit_account username
|
31
|
-
end
|
32
|
-
menu.choice('save and exit') do
|
33
|
-
@config.save
|
34
|
-
return
|
35
|
-
end
|
36
|
-
menu.choice(:quit) do
|
37
|
-
return
|
38
|
-
end
|
12
|
+
def run
|
13
|
+
setup_logging
|
14
|
+
loop do
|
15
|
+
system('clear')
|
16
|
+
self.class.highline.choose do |menu|
|
17
|
+
menu.header = 'Choose an action'
|
18
|
+
config.data[:accounts].each do |account|
|
19
|
+
menu.choice("#{account[:username]}") do
|
20
|
+
edit_account account[:username]
|
39
21
|
end
|
40
22
|
end
|
23
|
+
menu.choice('add account') do
|
24
|
+
username = Asker.email
|
25
|
+
edit_account username
|
26
|
+
end
|
27
|
+
menu.choice('save and exit') do
|
28
|
+
config.save
|
29
|
+
return
|
30
|
+
end
|
31
|
+
menu.choice(:quit) do
|
32
|
+
return
|
33
|
+
end
|
41
34
|
end
|
35
|
+
end
|
36
|
+
end
|
42
37
|
|
43
|
-
|
38
|
+
private
|
44
39
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
:password => '',
|
49
|
-
:local_path => File.join(@config.path, username.gsub('@', '_')),
|
50
|
-
:folders => []
|
51
|
-
}
|
52
|
-
@config.data[:accounts] << account
|
53
|
-
account
|
54
|
-
end
|
40
|
+
def config
|
41
|
+
@config ||= Imap::Backup::Configuration::Store.new
|
42
|
+
end
|
55
43
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
44
|
+
def setup_logging
|
45
|
+
Imap::Backup.logger.level =
|
46
|
+
if config.data[:debug]
|
47
|
+
::Logger::Severity::DEBUG
|
48
|
+
else
|
49
|
+
::Logger::Severity::ERROR
|
62
50
|
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_account(username)
|
54
|
+
account = {
|
55
|
+
:username => username,
|
56
|
+
:password => '',
|
57
|
+
:local_path => File.join(config.path, username.gsub('@', '_')),
|
58
|
+
:folders => []
|
59
|
+
}
|
60
|
+
config.data[:accounts] << account
|
61
|
+
account
|
62
|
+
end
|
63
|
+
|
64
|
+
def edit_account(username)
|
65
|
+
account = config.data[:accounts].find { |a| a[:username] == username }
|
66
|
+
if account.nil?
|
67
|
+
account = add_account(username)
|
63
68
|
end
|
69
|
+
Account.new(config, account, Imap::Backup::Configuration::Setup.highline).run
|
64
70
|
end
|
65
71
|
end
|
66
72
|
end
|
67
|
-
|
@@ -2,69 +2,64 @@
|
|
2
2
|
require 'rubygems' if RUBY_VERSION < '1.9'
|
3
3
|
require 'json'
|
4
4
|
|
5
|
-
module Imap
|
6
|
-
|
7
|
-
|
8
|
-
class Store
|
9
|
-
CONFIGURATION_DIRECTORY = File.expand_path('~/.imap-backup')
|
5
|
+
module Imap::Backup::Configuration
|
6
|
+
class Store
|
7
|
+
CONFIGURATION_DIRECTORY = File.expand_path('~/.imap-backup')
|
10
8
|
|
11
|
-
|
12
|
-
|
9
|
+
attr_reader :data
|
10
|
+
attr_reader :path
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
def self.default_pathname
|
13
|
+
File.join(CONFIGURATION_DIRECTORY, 'config.json')
|
14
|
+
end
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
def self.exist?(pathname = default_pathname)
|
17
|
+
File.exist?(pathname)
|
18
|
+
end
|
21
19
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
20
|
+
def initialize(pathname = self.class.default_pathname)
|
21
|
+
@pathname = pathname
|
22
|
+
if File.directory?(path)
|
23
|
+
Imap::Backup::Utils.check_permissions path, 0700
|
24
|
+
end
|
25
|
+
if File.exist?(@pathname)
|
26
|
+
Imap::Backup::Utils.check_permissions @pathname, 0600
|
27
|
+
@data = JSON.parse(File.read(@pathname), :symbolize_names => true)
|
28
|
+
else
|
29
|
+
@data = {:accounts => []}
|
30
|
+
end
|
31
|
+
end
|
34
32
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
48
|
-
end
|
33
|
+
def save
|
34
|
+
mkdir_private path
|
35
|
+
File.open(@pathname, 'w') { |f| f.write(JSON.pretty_generate(@data)) }
|
36
|
+
FileUtils.chmod 0600, @pathname
|
37
|
+
@data[:accounts].each do |account|
|
38
|
+
mkdir_private account[:local_path]
|
39
|
+
account[:folders].each do |f|
|
40
|
+
parts = f[:name].split('/')
|
41
|
+
p = account[:local_path].clone
|
42
|
+
parts.each do |part|
|
43
|
+
p = File.join(p, part)
|
44
|
+
mkdir_private p
|
49
45
|
end
|
50
46
|
end
|
47
|
+
end
|
48
|
+
end
|
51
49
|
|
52
|
-
|
53
|
-
|
54
|
-
|
50
|
+
def path
|
51
|
+
File.dirname(@pathname)
|
52
|
+
end
|
55
53
|
|
56
|
-
|
54
|
+
private
|
57
55
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
65
|
-
end
|
56
|
+
def mkdir_private(path)
|
57
|
+
if ! File.directory?(path)
|
58
|
+
FileUtils.mkdir path
|
59
|
+
end
|
60
|
+
if Imap::Backup::Utils::stat(path) != 0700
|
61
|
+
FileUtils.chmod 0700, path
|
66
62
|
end
|
67
63
|
end
|
68
64
|
end
|
69
65
|
end
|
70
|
-
|