imap-backup 0.0.3 → 0.0.4

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.
data/bin/imap-backup CHANGED
@@ -7,10 +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
+ {:name => 'setup', :help => 'Create/edit the configuration file'},
10
12
  {:name => 'backup', :help => 'Do the backup (default)'},
11
13
  {:name => 'folders', :help => 'List folders for all (or selected) accounts'},
12
14
  {:name => 'status', :help => 'List count of non backed-up emails per folder'},
13
- {:name => 'help', :help => 'Show usage'},
14
15
  ]
15
16
 
16
17
  options = {:command => 'backup'}
@@ -45,22 +46,32 @@ if KNOWN_COMMANDS.find{|c| c[:name] == options[:command] }.nil?
45
46
  raise "Unknown command '#{options[:command]}'"
46
47
  end
47
48
 
48
- settings = Imap::Backup::Settings.new(options[:accounts])
49
+ if options[:command] == 'help'
50
+ puts opts
51
+ exit
52
+ end
53
+
54
+ begin
55
+ configuration = Imap::Backup::Configuration::List.new(options[:accounts])
56
+ rescue Imap::Backup::ConfigurationNotFound
57
+ Imap::Backup::Configuration::Setup.new(options).run
58
+ exit
59
+ end
49
60
 
50
61
  case options[:command]
51
62
  when 'backup'
52
- settings.each_connection do |connection|
63
+ configuration.each_connection do |connection|
53
64
  connection.run_backup
54
65
  end
55
- when 'help'
56
- puts opts
57
66
  when 'folders'
58
- settings.each_connection do |connection|
67
+ configuration.each_connection do |connection|
59
68
  puts connection.username
60
69
  connection.folders.each { |f| puts "\t" + f.name }
61
70
  end
71
+ when 'setup'
72
+ Imap::Backup::Configuration::Setup.new(options).run
62
73
  when 'status'
63
- settings.each_connection do |connection|
74
+ configuration.each_connection do |connection|
64
75
  puts connection.username
65
76
  folders = connection.status
66
77
  folders.each do |f|
data/imap-backup.gemspec CHANGED
@@ -17,6 +17,7 @@ Gem::Specification.new do |gem|
17
17
  gem.version = Imap::Backup::VERSION
18
18
 
19
19
  gem.add_runtime_dependency 'rake'
20
+ gem.add_runtime_dependency 'highline'
20
21
  if RUBY_VERSION < '1.9'
21
22
  gem.add_runtime_dependency 'json'
22
23
  end
data/lib/imap/backup.rb CHANGED
@@ -1,8 +1,20 @@
1
1
  require 'imap/backup/utils'
2
2
  require 'imap/backup/account/connection'
3
3
  require 'imap/backup/account/folder'
4
+ require 'imap/backup/configuration/account'
5
+ require 'imap/backup/configuration/asker'
6
+ require 'imap/backup/configuration/connection_tester'
7
+ require 'imap/backup/configuration/folder_chooser'
8
+ require 'imap/backup/configuration/list'
9
+ require 'imap/backup/configuration/setup'
10
+ require 'imap/backup/configuration/store'
4
11
  require 'imap/backup/downloader'
5
12
  require 'imap/backup/serializer/directory'
6
- require 'imap/backup/settings'
7
13
  require 'imap/backup/version'
8
14
 
15
+ module Imap
16
+ module Backup
17
+ class ConfigurationNotFound < StandardError; end
18
+ end
19
+ end
20
+
@@ -0,0 +1,85 @@
1
+ # encoding: utf-8
2
+
3
+ module Imap
4
+ module Backup
5
+ module Configuration
6
+ class Account
7
+
8
+ def initialize(store, account)
9
+ @store, @account = store, account
10
+ end
11
+
12
+ def run
13
+ loop do
14
+ Setup.highline.choose do |menu|
15
+ password =
16
+ if @account[:password] == ''
17
+ '(unset)'
18
+ else
19
+ @account[:password].gsub(/./, 'x')
20
+ end
21
+ menu.header = <<EOT
22
+ Account:
23
+ email: #{@account[:username]}
24
+ path: #{@account[:local_path]}
25
+ folders: #{@account[:folders].map { |f| f[:name] }.join(', ')}
26
+ password: #{password}
27
+ EOT
28
+ menu.choice('modify email') do
29
+ username = Asker.email(username)
30
+ others = @store.data[:accounts].select { |a| a != @account}.map { |a| a[:username] }
31
+ if others.include?(username)
32
+ puts 'There is already an account set up with that email address'
33
+ else
34
+ @account[:username] = username
35
+ end
36
+ end
37
+ menu.choice('modify password') do
38
+ password = Asker.password
39
+ if ! password.nil?
40
+ @account[:password] = password
41
+ end
42
+ end
43
+ menu.choice('modify backup path') do
44
+ validator = lambda do |p|
45
+ same = @store.data[:accounts].find do |a|
46
+ a[:username] != @account[:username] && a[:local_path] == p
47
+ end
48
+ if same
49
+ puts "The path '#{p}' is used to backup the account '#{same[:username]}'"
50
+ false
51
+ else
52
+ true
53
+ end
54
+ end
55
+ @account[:local_path] = Asker.backup_path(@account[:local_path], validator)
56
+ end
57
+ menu.choice('choose backup folders') do
58
+ FolderChooser.new(@account).run
59
+ end
60
+ menu.choice 'test authentication' do
61
+ result = ConnectionTester.test(@account)
62
+ puts result
63
+ Setup.highline.ask 'Press a key '
64
+ end
65
+ menu.choice(:delete) do
66
+ if Setup.highline.agree("Are you sure? (y/n) ")
67
+ @store.data[:accounts].reject! { |a| a[:username] == @account[:username] }
68
+ return
69
+ end
70
+ end
71
+ menu.choice('return to main menu') do
72
+ return
73
+ end
74
+ menu.hidden('quit') do
75
+ return
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ end
82
+ end
83
+ end
84
+ end
85
+
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ module Imap
4
+ module Backup
5
+ module Configuration
6
+ module Asker
7
+
8
+ EMAIL_MATCHER = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i
9
+
10
+ def self.email(default = '')
11
+ Setup.highline.ask('email address: ') do |q|
12
+ q.default = default
13
+ q.readline = true
14
+ q.validate = EMAIL_MATCHER
15
+ q.responses[:not_valid] = 'Enter a valid email address '
16
+ end
17
+ end
18
+
19
+ def self.password
20
+ password = Setup.highline.ask('password: ') { |q| q.echo = false }
21
+ confirmation = Setup.highline.ask('repeat password: ') { |q| q.echo = false }
22
+ if password != confirmation
23
+ return nil unless Setup.highline.agree("the password and confirmation did not match.\nContinue? (y/n) ")
24
+ return self.password
25
+ end
26
+ password
27
+ end
28
+
29
+ def self.backup_path(default, validator)
30
+ Setup.highline.ask('backup directory: ') do |q|
31
+ q.default = default
32
+ q.readline = true
33
+ q.validate = validator
34
+ q.responses[:not_valid] = 'Choose a different directory '
35
+ end
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
42
+
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ module Imap
4
+ module Backup
5
+ module Configuration
6
+ module ConnectionTester
7
+ def self.test(account)
8
+ Imap::Backup::Account::Connection.new(account)
9
+ return 'Connection successful'
10
+ rescue Net::IMAP::NoResponseError
11
+ return 'No response'
12
+ rescue Exception => e
13
+ return "Unexpected error: #{e}"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ module Imap
4
+ module Backup
5
+ module Configuration
6
+ class FolderChooser
7
+
8
+ def initialize(account)
9
+ @account = account
10
+ end
11
+
12
+ def run
13
+ begin
14
+ @connection = Imap::Backup::Account::Connection.new(@account)
15
+ rescue => e
16
+ puts 'Connection failed'
17
+ Setup.highline.ask 'Press a key '
18
+ return
19
+ end
20
+ @folders = @connection.folders
21
+ loop do
22
+ Setup.highline.choose do |menu|
23
+ menu.header = 'Add/remove folders'
24
+ menu.index = :number
25
+ @folders.each do |folder|
26
+ name = folder.name
27
+ found = @account[:folders].find { |f| f[:name] == name }
28
+ mark =
29
+ if found
30
+ '+'
31
+ else
32
+ '-'
33
+ end
34
+ menu.choice("#{mark} #{name}") do
35
+ if found
36
+ @account[:folders].reject! { |f| f[:name] == name }
37
+ else
38
+ @account[:folders] << { :name => name }
39
+ end
40
+ end
41
+ end
42
+ menu.choice('return to the account menu') do
43
+ return
44
+ end
45
+ menu.hidden('quit') do
46
+ return
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+ end
55
+ end
56
+
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ module Imap
4
+ module Backup
5
+ module Configuration
6
+ class List
7
+
8
+ attr_reader :accounts
9
+
10
+ def initialize(accounts = nil)
11
+ @config = Imap::Backup::Configuration::Store.new
12
+
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
19
+
20
+ def each_connection
21
+ @accounts.each do |account|
22
+ connection = Imap::Backup::Account::Connection.new(account)
23
+ yield connection
24
+ connection.disconnect
25
+ end
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,68 @@
1
+ # encoding: utf-8
2
+ require 'rubygems' if RUBY_VERSION < '1.9'
3
+ require 'highline'
4
+
5
+ module Imap
6
+ module Backup
7
+ module Configuration
8
+ class Setup
9
+
10
+ class << self
11
+ attr_accessor :highline
12
+ end
13
+ self.highline = HighLine.new
14
+
15
+ def initialize
16
+ @config = Imap::Backup::Configuration::Store.new(false)
17
+ end
18
+
19
+ def run
20
+ loop do
21
+ self.class.highline.choose do |menu|
22
+ menu.header = 'Choose an action'
23
+ @config.data[:accounts].each do |account|
24
+ menu.choice("#{account[:username]}") do
25
+ Account.new(@config, account[:username]).run
26
+ end
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
39
+ end
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def add_account(username)
46
+ account = {
47
+ :username => username,
48
+ :password => '',
49
+ :local_path => File.join(@config.path, username.gsub('@', '_')),
50
+ :folders => []
51
+ }
52
+ @config.data[:accounts] << account
53
+ account
54
+ end
55
+
56
+ def edit_account(username)
57
+ account = @config.data[:accounts].find { |a| a[:username] == username }
58
+ if account.nil?
59
+ account = add_account(username)
60
+ end
61
+ Account.new @config, account[:username]
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+ end
68
+
@@ -0,0 +1,64 @@
1
+ # encoding: utf-8
2
+ require 'rubygems' if RUBY_VERSION < '1.9'
3
+ require 'json'
4
+
5
+ module Imap
6
+ module Backup
7
+ module Configuration
8
+ class Store
9
+
10
+ CONFIGURATION_DIRECTORY = File.expand_path('~/.imap-backup')
11
+
12
+ attr_reader :data
13
+ attr_reader :path
14
+
15
+ def initialize(fail_if_missing = true)
16
+ @path = CONFIGURATION_DIRECTORY
17
+ @pathname = File.join(@path, 'config.json')
18
+ if File.directory?(@path)
19
+ Imap::Backup::Utils.check_permissions @path, 0700
20
+ end
21
+ if File.exist?(@pathname)
22
+ Imap::Backup::Utils.check_permissions @pathname, 0600
23
+ @data = JSON.parse(File.read(@pathname), :symbolize_names => true)
24
+ else
25
+ if fail_if_missing
26
+ raise ConfigurationNotFound.new("Configuration file '#{@pathname}' not found")
27
+ end
28
+ @data = {:accounts => []}
29
+ end
30
+ end
31
+
32
+ def save
33
+ mkdir_private @path
34
+ File.open(@pathname, 'w') { |f| f.write(JSON.pretty_generate(@data)) }
35
+ FileUtils.chmod 0600, @pathname
36
+ @data[:accounts].each do |account|
37
+ mkdir_private account[:local_path]
38
+ account[:folders].each do |f|
39
+ parts = f[:name].split('/')
40
+ path = account[:local_path].clone
41
+ parts.each do |part|
42
+ path = File.join(path, part)
43
+ mkdir_private path
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def mkdir_private(path)
52
+ if ! File.directory?(path)
53
+ FileUtils.mkdir path
54
+ end
55
+ if Imap::Backup::Utils::stat(path) != 0700
56
+ FileUtils.chmod 0700, path
57
+ end
58
+ end
59
+
60
+ end
61
+ end
62
+ end
63
+ end
64
+