imap-backup 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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
+