imap-backup 1.2.2 → 1.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -0
- data/Gemfile +1 -1
- data/Rakefile +4 -4
- data/bin/imap-backup +23 -25
- data/imap-backup.gemspec +14 -14
- data/lib/email/mboxrd/message.rb +23 -5
- data/lib/email/provider.rb +4 -4
- data/lib/imap/backup.rb +18 -18
- data/lib/imap/backup/account/connection.rb +6 -6
- data/lib/imap/backup/account/folder.rb +4 -5
- data/lib/imap/backup/configuration/account.rb +20 -22
- data/lib/imap/backup/configuration/asker.rb +8 -10
- data/lib/imap/backup/configuration/connection_tester.rb +3 -5
- data/lib/imap/backup/configuration/folder_chooser.rb +10 -12
- data/lib/imap/backup/configuration/list.rb +1 -3
- data/lib/imap/backup/configuration/setup.rb +13 -14
- data/lib/imap/backup/configuration/store.rb +7 -8
- data/lib/imap/backup/downloader.rb +0 -2
- data/lib/imap/backup/serializer/base.rb +0 -2
- data/lib/imap/backup/serializer/directory.rb +3 -4
- data/lib/imap/backup/serializer/mbox.rb +11 -12
- data/lib/imap/backup/utils.rb +2 -3
- data/lib/imap/backup/version.rb +2 -2
- data/spec/spec_helper.rb +6 -6
- data/spec/support/higline_test_helpers.rb +1 -1
- data/spec/support/shared_examples/account_flagging.rb +6 -6
- data/spec/unit/account/connection_spec.rb +50 -51
- data/spec/unit/account/folder_spec.rb +18 -19
- data/spec/unit/configuration/account_spec.rb +96 -97
- data/spec/unit/configuration/asker_spec.rb +33 -34
- data/spec/unit/configuration/connection_tester_spec.rb +18 -19
- data/spec/unit/configuration/folder_chooser_spec.rb +34 -35
- data/spec/unit/configuration/list_spec.rb +13 -14
- data/spec/unit/configuration/setup_spec.rb +46 -47
- data/spec/unit/configuration/store_spec.rb +56 -57
- data/spec/unit/downloader_spec.rb +18 -19
- data/spec/unit/email/mboxrd/message_spec.rb +55 -11
- data/spec/unit/email/provider_spec.rb +12 -12
- data/spec/unit/serializer/base_spec.rb +7 -9
- data/spec/unit/serializer/directory_spec.rb +18 -19
- data/spec/unit/serializer/mbox_spec.rb +35 -37
- data/spec/unit/utils_spec.rb +26 -27
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 55d07b2ed9eae209a118323f3e8c494b831183468ef92e05cfe2ae139177f82b
|
4
|
+
data.tar.gz: c76ad04b8363dd146fc2abdc5ba6243fe3cd446a107f946187bf45bf16689906
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd396bf6b70d293ef00ac83bc4e3f3ac0efdfdbf56c835484abf5c503ec84d5690537c0f36c5d23cf9e18f27f64372b66157b45491954fa6dfd0c2779b8b7aac
|
7
|
+
data.tar.gz: 1af9c675e2641debc9e897b4a4ae1a13834550e234887bcdd8ef75cafd3aadde58b296fdd903c377bddf142b340f9ed32fd8e8d6305d673e7fde5a7acb4437a5
|
data/.rubocop.yml
ADDED
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
|
-
require
|
3
|
-
require
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require "rspec/core/rake_task"
|
4
4
|
|
5
|
-
task :
|
5
|
+
task default: :spec
|
6
6
|
|
7
7
|
RSpec::Core::RakeTask.new do |t|
|
8
|
-
t.pattern =
|
8
|
+
t.pattern = "spec/**/*_spec.rb"
|
9
9
|
end
|
data/bin/imap-backup
CHANGED
@@ -1,32 +1,30 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
2
|
+
require "optparse"
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
$:.unshift(File.expand_path('../../lib/', __FILE__))
|
7
|
-
require 'imap/backup'
|
4
|
+
$LOAD_PATH.unshift(File.expand_path("../../lib/", __FILE__))
|
5
|
+
require "imap/backup"
|
8
6
|
|
9
7
|
KNOWN_COMMANDS = [
|
10
|
-
{:
|
11
|
-
{:
|
12
|
-
{:
|
13
|
-
{:
|
14
|
-
{:
|
8
|
+
{name: "setup", help: "Create/edit the configuration file"},
|
9
|
+
{name: "backup", help: "Do the backup (default)"},
|
10
|
+
{name: "folders", help: "List folders for all (or selected) accounts"},
|
11
|
+
{name: "status", help: "List count of non backed-up emails per folder"},
|
12
|
+
{name: "help", help: "Show usage"}
|
15
13
|
]
|
16
14
|
|
17
|
-
options = {:
|
15
|
+
options = {command: "backup"}
|
18
16
|
opts = OptionParser.new do |opts|
|
19
|
-
opts.banner = "Usage: #{$
|
17
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} [options] COMMAND"
|
20
18
|
|
21
|
-
opts.separator
|
22
|
-
opts.separator
|
19
|
+
opts.separator ""
|
20
|
+
opts.separator "Commands:"
|
23
21
|
KNOWN_COMMANDS.each do |command|
|
24
22
|
opts.separator "\t%- 20s %s" % [command[:name], command[:help]]
|
25
23
|
end
|
26
|
-
opts.separator
|
27
|
-
opts.separator
|
24
|
+
opts.separator ""
|
25
|
+
opts.separator "Common options:"
|
28
26
|
|
29
|
-
opts.on(
|
27
|
+
opts.on("-a", "--accounts ACCOUNT1[,ACCOUNT2,...]", Array, "only these accounts") do |account|
|
30
28
|
options[:accounts] = account
|
31
29
|
end
|
32
30
|
|
@@ -35,7 +33,7 @@ opts = OptionParser.new do |opts|
|
|
35
33
|
exit
|
36
34
|
end
|
37
35
|
|
38
|
-
opts.on_tail("--version", "Show version"
|
36
|
+
opts.on_tail("--version", "Show version") do
|
39
37
|
puts Imap::Backup::VERSION
|
40
38
|
exit
|
41
39
|
end
|
@@ -46,11 +44,11 @@ if ARGV.size > 0
|
|
46
44
|
options[:command] = ARGV.shift
|
47
45
|
end
|
48
46
|
|
49
|
-
if KNOWN_COMMANDS.find{ |c| c[:name] == options[:command] }.nil?
|
47
|
+
if KNOWN_COMMANDS.find { |c| c[:name] == options[:command] }.nil?
|
50
48
|
raise "Unknown command '#{options[:command]}'"
|
51
49
|
end
|
52
50
|
|
53
|
-
if options[:command] ==
|
51
|
+
if options[:command] == "help"
|
54
52
|
puts opts
|
55
53
|
exit
|
56
54
|
end
|
@@ -65,23 +63,23 @@ end
|
|
65
63
|
configuration.setup_logging
|
66
64
|
|
67
65
|
case options[:command]
|
68
|
-
when
|
66
|
+
when "setup"
|
69
67
|
Imap::Backup::Configuration::Setup.new.run
|
70
|
-
when
|
68
|
+
when "backup"
|
71
69
|
configuration.each_connection do |connection|
|
72
70
|
connection.run_backup
|
73
71
|
end
|
74
|
-
when
|
72
|
+
when "folders"
|
75
73
|
configuration.each_connection do |connection|
|
76
74
|
puts connection.username
|
77
75
|
folders = connection.folders
|
78
76
|
if folders.nil?
|
79
|
-
warn
|
77
|
+
warn "Unable to list account folders"
|
80
78
|
exit 1
|
81
79
|
end
|
82
80
|
folders.each { |f| puts "\t" + f.name }
|
83
81
|
end
|
84
|
-
when
|
82
|
+
when "status"
|
85
83
|
configuration.each_connection do |connection|
|
86
84
|
puts connection.username
|
87
85
|
folders = connection.status
|
data/imap-backup.gemspec
CHANGED
@@ -1,26 +1,26 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'imap/backup/version'
|
1
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __FILE__))
|
2
|
+
require "imap/backup/version"
|
4
3
|
|
5
4
|
Gem::Specification.new do |gem|
|
6
|
-
gem.name =
|
5
|
+
gem.name = "imap-backup"
|
7
6
|
gem.description = %q{Backup GMail, or any other IMAP email service, to disk.}
|
8
7
|
gem.summary = %q{Backup GMail (or other IMAP) accounts to disk}
|
9
|
-
gem.authors = [
|
10
|
-
gem.email = [
|
11
|
-
gem.homepage =
|
8
|
+
gem.authors = ["Joe Yates"]
|
9
|
+
gem.email = ["joe.g.yates@gmail.com"]
|
10
|
+
gem.homepage = "https://github.com/joeyates/imap-backup"
|
12
11
|
|
13
12
|
gem.files = `git ls-files`.split($\)
|
14
13
|
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
15
14
|
gem.test_files = gem.files.grep(%r{^spec/})
|
16
|
-
gem.require_paths = [
|
15
|
+
gem.require_paths = ["lib"]
|
17
16
|
gem.version = Imap::Backup::VERSION
|
18
17
|
|
19
|
-
gem.add_runtime_dependency
|
20
|
-
gem.add_runtime_dependency
|
21
|
-
gem.add_runtime_dependency
|
18
|
+
gem.add_runtime_dependency "rake"
|
19
|
+
gem.add_runtime_dependency "highline"
|
20
|
+
gem.add_runtime_dependency "mail"
|
22
21
|
|
23
|
-
gem.add_development_dependency
|
24
|
-
gem.add_development_dependency
|
25
|
-
gem.add_development_dependency
|
22
|
+
gem.add_development_dependency "codeclimate-test-reporter", "~> 0.4.8"
|
23
|
+
gem.add_development_dependency "rspec", ">= 3.0.0"
|
24
|
+
gem.add_development_dependency "rubocop-rspec"
|
25
|
+
gem.add_development_dependency "simplecov"
|
26
26
|
end
|
data/lib/email/mboxrd/message.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "mail"
|
2
2
|
|
3
3
|
module Email; end
|
4
4
|
|
@@ -8,11 +8,11 @@ module Email::Mboxrd
|
|
8
8
|
|
9
9
|
def initialize(supplied_body)
|
10
10
|
@supplied_body = supplied_body.clone
|
11
|
-
@supplied_body.force_encoding(
|
11
|
+
@supplied_body.force_encoding("binary")
|
12
12
|
end
|
13
13
|
|
14
14
|
def to_s
|
15
|
-
|
15
|
+
"From " + from + "\n" + mboxrd_body + "\n"
|
16
16
|
end
|
17
17
|
|
18
18
|
private
|
@@ -21,8 +21,26 @@ module Email::Mboxrd
|
|
21
21
|
@parsed ||= Mail.new(supplied_body)
|
22
22
|
end
|
23
23
|
|
24
|
+
def best_from
|
25
|
+
if parsed.from.is_a? Enumerable
|
26
|
+
parsed.from.each do |addr|
|
27
|
+
if addr
|
28
|
+
return addr
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
if parsed.envelope_from
|
33
|
+
return parsed.envelope_from
|
34
|
+
end
|
35
|
+
if parsed.return_path
|
36
|
+
return parsed.return_path
|
37
|
+
end
|
38
|
+
|
39
|
+
return ''
|
40
|
+
end
|
41
|
+
|
24
42
|
def from
|
25
|
-
|
43
|
+
best_from + " " + asctime
|
26
44
|
end
|
27
45
|
|
28
46
|
def mboxrd_body
|
@@ -33,7 +51,7 @@ module Email::Mboxrd
|
|
33
51
|
end
|
34
52
|
|
35
53
|
def asctime
|
36
|
-
date ? date.asctime :
|
54
|
+
date ? date.asctime : ""
|
37
55
|
end
|
38
56
|
|
39
57
|
def date
|
data/lib/email/provider.rb
CHANGED
@@ -3,9 +3,9 @@ module Email; end
|
|
3
3
|
class Email::Provider
|
4
4
|
def self.for_address(address)
|
5
5
|
case
|
6
|
-
when address.end_with?(
|
6
|
+
when address.end_with?("@gmail.com")
|
7
7
|
new(:gmail)
|
8
|
-
when address.end_with?(
|
8
|
+
when address.end_with?("@fastmail.fm")
|
9
9
|
new(:fastmail)
|
10
10
|
else
|
11
11
|
new(:default)
|
@@ -32,9 +32,9 @@ class Email::Provider
|
|
32
32
|
def host
|
33
33
|
case provider
|
34
34
|
when :gmail
|
35
|
-
|
35
|
+
"imap.gmail.com"
|
36
36
|
when :fastmail
|
37
|
-
|
37
|
+
"mail.messagingengine.com"
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
data/lib/imap/backup.rb
CHANGED
@@ -1,23 +1,23 @@
|
|
1
1
|
module Imap; end
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
16
|
-
require
|
17
|
-
require
|
18
|
-
require
|
19
|
-
|
20
|
-
require
|
3
|
+
require "imap/backup/utils"
|
4
|
+
require "imap/backup/account/connection"
|
5
|
+
require "imap/backup/account/folder"
|
6
|
+
require "imap/backup/configuration/account"
|
7
|
+
require "imap/backup/configuration/asker"
|
8
|
+
require "imap/backup/configuration/connection_tester"
|
9
|
+
require "imap/backup/configuration/folder_chooser"
|
10
|
+
require "imap/backup/configuration/list"
|
11
|
+
require "imap/backup/configuration/setup"
|
12
|
+
require "imap/backup/configuration/store"
|
13
|
+
require "imap/backup/downloader"
|
14
|
+
require "imap/backup/serializer/base"
|
15
|
+
require "imap/backup/serializer/directory"
|
16
|
+
require "imap/backup/serializer/mbox"
|
17
|
+
require "imap/backup/version"
|
18
|
+
require "email/provider"
|
19
|
+
|
20
|
+
require "logger"
|
21
21
|
|
22
22
|
module Imap::Backup
|
23
23
|
class ConfigurationNotFound < StandardError; end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "net/imap"
|
2
2
|
|
3
3
|
module Imap::Backup
|
4
4
|
module Account; end
|
@@ -20,7 +20,7 @@ module Imap::Backup
|
|
20
20
|
|
21
21
|
def folders
|
22
22
|
return @folders if @folders
|
23
|
-
@folders = imap.list(
|
23
|
+
@folders = imap.list("", "*")
|
24
24
|
if @folders.nil?
|
25
25
|
Imap::Backup.logger.warn "Unable to get folder list for account #{username}"
|
26
26
|
end
|
@@ -31,7 +31,7 @@ module Imap::Backup
|
|
31
31
|
backup_folders.map do |folder|
|
32
32
|
f = Account::Folder.new(self, folder[:name])
|
33
33
|
s = Serializer::Directory.new(local_path, folder[:name])
|
34
|
-
{:
|
34
|
+
{name: folder[:name], local: s.uids, remote: f.uids}
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
@@ -83,12 +83,12 @@ module Imap::Backup
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def masked_password
|
86
|
-
password.gsub(/./,
|
86
|
+
password.gsub(/./, "x")
|
87
87
|
end
|
88
88
|
|
89
89
|
def backup_folders
|
90
|
-
return @backup_folders if @backup_folders
|
91
|
-
(folders || []).map { |f| {:
|
90
|
+
return @backup_folders if @backup_folders && (@backup_folders.size > 0)
|
91
|
+
(folders || []).map { |f| {name: f.name} }
|
92
92
|
end
|
93
93
|
|
94
94
|
def provider
|
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
require 'forwardable'
|
1
|
+
require "forwardable"
|
3
2
|
|
4
3
|
module Imap::Backup
|
5
4
|
module Account; end
|
@@ -7,7 +6,7 @@ module Imap::Backup
|
|
7
6
|
class Account::Folder
|
8
7
|
extend Forwardable
|
9
8
|
|
10
|
-
REQUESTED_ATTRIBUTES = [
|
9
|
+
REQUESTED_ATTRIBUTES = ["RFC822", "FLAGS", "INTERNALDATE"]
|
11
10
|
|
12
11
|
attr_reader :connection
|
13
12
|
attr_reader :name
|
@@ -25,7 +24,7 @@ module Imap::Backup
|
|
25
24
|
|
26
25
|
def uids
|
27
26
|
imap.examine(name)
|
28
|
-
imap.uid_search([
|
27
|
+
imap.uid_search(["ALL"]).sort
|
29
28
|
rescue Net::IMAP::NoResponseError => e
|
30
29
|
Imap::Backup.logger.warn "Folder '#{name}' does not exist"
|
31
30
|
[]
|
@@ -37,7 +36,7 @@ module Imap::Backup
|
|
37
36
|
return nil if fetch_data_items.nil?
|
38
37
|
fetch_data_item = fetch_data_items[0]
|
39
38
|
attributes = fetch_data_item.attr
|
40
|
-
attributes[
|
39
|
+
attributes["RFC822"].force_encoding("utf-8")
|
41
40
|
attributes
|
42
41
|
rescue Net::IMAP::NoResponseError => e
|
43
42
|
Imap::Backup.logger.warn "Folder '#{name}' does not exist"
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
module Imap::Backup
|
4
2
|
module Configuration; end
|
5
3
|
|
@@ -11,7 +9,7 @@ module Imap::Backup
|
|
11
9
|
def run
|
12
10
|
catch :done do
|
13
11
|
loop do
|
14
|
-
system(
|
12
|
+
system("clear")
|
15
13
|
create_menu
|
16
14
|
end
|
17
15
|
end
|
@@ -29,8 +27,8 @@ module Imap::Backup
|
|
29
27
|
choose_folders menu
|
30
28
|
test_connection menu
|
31
29
|
delete_account menu
|
32
|
-
menu.choice(
|
33
|
-
menu.hidden(
|
30
|
+
menu.choice("return to main menu") { throw :done }
|
31
|
+
menu.hidden("quit") { throw :done }
|
34
32
|
end
|
35
33
|
end
|
36
34
|
|
@@ -46,16 +44,16 @@ Account:
|
|
46
44
|
end
|
47
45
|
|
48
46
|
def modify_email(menu)
|
49
|
-
menu.choice(
|
47
|
+
menu.choice("modify email") do
|
50
48
|
username = Configuration::Asker.email(username)
|
51
49
|
puts "username: #{username}"
|
52
|
-
others
|
50
|
+
others = store.accounts.select { |a| a != account }.map { |a| a[:username] }
|
53
51
|
puts "others: #{others.inspect}"
|
54
52
|
if others.include?(username)
|
55
|
-
puts
|
53
|
+
puts "There is already an account set up with that email address"
|
56
54
|
else
|
57
55
|
account[:username] = username
|
58
|
-
if account[:server].nil?
|
56
|
+
if account[:server].nil? || (account[:server] == "")
|
59
57
|
account[:server] = default_server(username)
|
60
58
|
end
|
61
59
|
account[:modified] = true
|
@@ -64,9 +62,9 @@ Account:
|
|
64
62
|
end
|
65
63
|
|
66
64
|
def modify_password(menu)
|
67
|
-
menu.choice(
|
65
|
+
menu.choice("modify password") do
|
68
66
|
password = Configuration::Asker.password
|
69
|
-
if !
|
67
|
+
if !password.nil?
|
70
68
|
account[:password] = password
|
71
69
|
account[:modified] = true
|
72
70
|
end
|
@@ -74,9 +72,9 @@ Account:
|
|
74
72
|
end
|
75
73
|
|
76
74
|
def modify_server(menu)
|
77
|
-
menu.choice(
|
78
|
-
server = highline.ask(
|
79
|
-
if !
|
75
|
+
menu.choice("modify server") do
|
76
|
+
server = highline.ask("server: ")
|
77
|
+
if !server.nil?
|
80
78
|
account[:server] = server
|
81
79
|
account[:modified] = true
|
82
80
|
end
|
@@ -84,7 +82,7 @@ Account:
|
|
84
82
|
end
|
85
83
|
|
86
84
|
def modify_backup_path(menu)
|
87
|
-
menu.choice(
|
85
|
+
menu.choice("modify backup path") do
|
88
86
|
validator = lambda do |p|
|
89
87
|
same = store.accounts.find do |a|
|
90
88
|
a[:username] != account[:username] && a[:local_path] == p
|
@@ -103,21 +101,21 @@ Account:
|
|
103
101
|
end
|
104
102
|
|
105
103
|
def choose_folders(menu)
|
106
|
-
menu.choice(
|
104
|
+
menu.choice("choose backup folders") do
|
107
105
|
Configuration::FolderChooser.new(account).run
|
108
106
|
end
|
109
107
|
end
|
110
108
|
|
111
109
|
def test_connection(menu)
|
112
|
-
menu.choice(
|
110
|
+
menu.choice("test connection") do
|
113
111
|
result = Configuration::ConnectionTester.test(account)
|
114
112
|
puts result
|
115
|
-
highline.ask
|
113
|
+
highline.ask "Press a key "
|
116
114
|
end
|
117
115
|
end
|
118
116
|
|
119
117
|
def delete_account(menu)
|
120
|
-
menu.choice(
|
118
|
+
menu.choice("delete") do
|
121
119
|
if highline.agree("Are you sure? (y/n) ")
|
122
120
|
account[:delete] = true
|
123
121
|
throw :done
|
@@ -130,10 +128,10 @@ Account:
|
|
130
128
|
end
|
131
129
|
|
132
130
|
def masked_password
|
133
|
-
if account[:password] ==
|
134
|
-
|
131
|
+
if (account[:password] == "") || account[:password].nil?
|
132
|
+
"(unset)"
|
135
133
|
else
|
136
|
-
account[:password].gsub(/./,
|
134
|
+
account[:password].gsub(/./, "x")
|
137
135
|
end
|
138
136
|
end
|
139
137
|
|