imap-backup 0.0.1
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/.gitignore +8 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +64 -0
- data/Rakefile +27 -0
- data/bin/imap-backup +21 -0
- data/imap-backup.gemspec +29 -0
- data/lib/imap/backup.rb +6 -0
- data/lib/imap/backup/account.rb +42 -0
- data/lib/imap/backup/downloader.rb +32 -0
- data/lib/imap/backup/settings.rb +28 -0
- data/lib/imap/backup/utils.rb +34 -0
- data/lib/imap/backup/version.rb +9 -0
- data/spec/gather_rspec_coverage.rb +2 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/unit/account_spec.rb +95 -0
- data/spec/unit/downloader_spec.rb +136 -0
- data/spec/unit/settings_spec.rb +84 -0
- data/spec/unit/utils_spec.rb +73 -0
- metadata +143 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Joe Yates
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
imap-backup [][Continuous Integration]
|
2
|
+
===========
|
3
|
+
|
4
|
+
*Backup GMail (or other IMAP) accounts to disk*
|
5
|
+
|
6
|
+
* [Source Code]
|
7
|
+
* [API documentation]
|
8
|
+
* [Rubygem]
|
9
|
+
* [Continuous Integration]
|
10
|
+
|
11
|
+
[Source Code]: https://github.com/joeyates/imap-backup "Source code at GitHub"
|
12
|
+
[API documentation]: http://rubydoc.info/gems/imap-backup/frames "RDoc API Documentation at Rubydoc.info"
|
13
|
+
[Rubygem]: http://rubygems.org/gems/imap-backup "Ruby gem at rubygems.org"
|
14
|
+
[Continuous Integration]: http://travis-ci.org/joeyates/imap-backup "Build status by Travis-CI"
|
15
|
+
|
16
|
+
Installation
|
17
|
+
============
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
gem 'imap/backup'
|
22
|
+
|
23
|
+
And then execute:
|
24
|
+
|
25
|
+
$ bundle
|
26
|
+
|
27
|
+
Or install it yourself as:
|
28
|
+
|
29
|
+
gem install 'imap-backup'
|
30
|
+
|
31
|
+
Basic Usage
|
32
|
+
===========
|
33
|
+
|
34
|
+
* Create ~/.imap-backup
|
35
|
+
* Run
|
36
|
+
|
37
|
+
imap-backup
|
38
|
+
|
39
|
+
Usage
|
40
|
+
=====
|
41
|
+
|
42
|
+
Check connection:
|
43
|
+
|
44
|
+
imap-backup --check
|
45
|
+
|
46
|
+
List IMAP folders:
|
47
|
+
|
48
|
+
imap-backup --list
|
49
|
+
|
50
|
+
Design Goals
|
51
|
+
============
|
52
|
+
|
53
|
+
* Secure - use a local file protected by permissions
|
54
|
+
* Restartable - calculate start point based on alreadt downloaded messages
|
55
|
+
* Standards compliant - save emails in a standard format
|
56
|
+
* Standalone - does not rely on an email client or MTA
|
57
|
+
|
58
|
+
Similar Software
|
59
|
+
================
|
60
|
+
|
61
|
+
* https://github.com/thefloweringash/gmail-imap-backup
|
62
|
+
* https://github.com/mleonhard/imapbackup
|
63
|
+
* https://github.com/rgrove/larch - copies between IMAP servers
|
64
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
task :default => :spec
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new do | t |
|
8
|
+
t.pattern = 'spec/**/*_spec.rb'
|
9
|
+
end
|
10
|
+
|
11
|
+
if RUBY_VERSION < '1.9'
|
12
|
+
|
13
|
+
RSpec::Core::RakeTask.new( 'spec:coverage' ) do |t|
|
14
|
+
t.pattern = 'spec/**/*_spec.rb'
|
15
|
+
t.rcov = true
|
16
|
+
t.rcov_opts = [ '--exclude', 'spec/,/gems/,vendor/' ]
|
17
|
+
end
|
18
|
+
|
19
|
+
else
|
20
|
+
|
21
|
+
desc 'Run specs and create coverage output'
|
22
|
+
RSpec::Core::RakeTask.new( 'spec:coverage' ) do |t|
|
23
|
+
t.pattern = ['spec/gather_rspec_coverage.rb', 'spec/**/*_spec.rb']
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
data/bin/imap-backup
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- encoding: utf-8 -*-
|
3
|
+
|
4
|
+
$:.unshift(File.expand_path('../../lib/', __FILE__))
|
5
|
+
require 'imap/backup'
|
6
|
+
|
7
|
+
settings = Imap::Backup::Settings.new
|
8
|
+
|
9
|
+
if ARGV[0] == '--list'
|
10
|
+
settings.each_account do |account|
|
11
|
+
account.folders.each { |f| puts f.name }
|
12
|
+
end
|
13
|
+
else
|
14
|
+
settings.each_account do |account|
|
15
|
+
account.backup_folders.each do |folder|
|
16
|
+
d = Imap::Backup::Downloader.new(account, folder['name'])
|
17
|
+
d.run
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
data/imap-backup.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.unshift(File.expand_path('../lib/', __FILE__))
|
3
|
+
require 'imap/backup/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.authors = ['Joe Yates']
|
7
|
+
gem.email = ['joe.g.yates@gmail.com']
|
8
|
+
gem.description = %q{Backup GMail, or any other IMAP email service, to disk.}
|
9
|
+
gem.summary = %q{Backup GMail (or other IMAP) accounts to disk}
|
10
|
+
gem.homepage = 'https://github.com/joeyates/imap-backup'
|
11
|
+
|
12
|
+
gem.files = `git ls-files`.split($\)
|
13
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
14
|
+
gem.test_files = gem.files.grep(%r{^spec/})
|
15
|
+
gem.name = 'imap-backup'
|
16
|
+
gem.require_paths = ['lib']
|
17
|
+
gem.version = Imap::Backup::VERSION
|
18
|
+
|
19
|
+
gem.add_development_dependency 'pry'
|
20
|
+
gem.add_development_dependency 'pry-doc'
|
21
|
+
gem.add_development_dependency 'rspec', '>= 2.3.0'
|
22
|
+
if RUBY_VERSION < '1.9'
|
23
|
+
gem.add_development_dependency 'rcov'
|
24
|
+
else
|
25
|
+
gem.add_development_dependency 'simplecov'
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
data/lib/imap/backup.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'net/imap'
|
2
|
+
|
3
|
+
module Imap
|
4
|
+
module Backup
|
5
|
+
class Account
|
6
|
+
|
7
|
+
REQUESTED_ATTRIBUTES = ['RFC822', 'FLAGS', 'INTERNALDATE']
|
8
|
+
|
9
|
+
attr_accessor :local_path
|
10
|
+
attr_accessor :backup_folders
|
11
|
+
|
12
|
+
def initialize(options)
|
13
|
+
@local_path, @backup_folders = options['local_path'], options['folders']
|
14
|
+
@imap = Net::IMAP.new('imap.gmail.com', 993, true)
|
15
|
+
@imap.login(options['username'], options['password'])
|
16
|
+
end
|
17
|
+
|
18
|
+
def disconnect
|
19
|
+
@imap.disconnect
|
20
|
+
end
|
21
|
+
|
22
|
+
def folders
|
23
|
+
@imap.list('/', '*')
|
24
|
+
end
|
25
|
+
|
26
|
+
def each_uid(folder)
|
27
|
+
@imap.examine(folder)
|
28
|
+
@imap.uid_search(['ALL']).each do |uid|
|
29
|
+
yield uid
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def fetch(uid)
|
34
|
+
message = @imap.uid_fetch([uid], REQUESTED_ATTRIBUTES)[0][1]
|
35
|
+
message['RFC822'].force_encoding('utf-8') if RUBY_VERSION > '1.9'
|
36
|
+
message
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Imap
|
4
|
+
module Backup
|
5
|
+
class Downloader
|
6
|
+
|
7
|
+
include Imap::Backup::Utils
|
8
|
+
|
9
|
+
def initialize(account, folder)
|
10
|
+
@account, @folder = account, folder
|
11
|
+
|
12
|
+
check_permissions(@account.local_path, 0700)
|
13
|
+
end
|
14
|
+
|
15
|
+
def run
|
16
|
+
make_folder(@account.local_path, @folder, 'g-wrx,o-wrx')
|
17
|
+
destination_path = File.join(@account.local_path, @folder)
|
18
|
+
@account.each_uid(@folder) do |uid|
|
19
|
+
message_filename = "#{destination_path}/%012u.json" % uid.to_i
|
20
|
+
next if File.exist?(message_filename)
|
21
|
+
|
22
|
+
message = @account.fetch(uid)
|
23
|
+
|
24
|
+
File.open(message_filename, 'w') { |f| f.write message.to_json }
|
25
|
+
FileUtils.chmod 0600, message_filename
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Imap
|
4
|
+
module Backup
|
5
|
+
|
6
|
+
class Settings
|
7
|
+
|
8
|
+
include Imap::Backup::Utils
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
config_pathname = File.expand_path('~/.imap-backup/config.json')
|
12
|
+
raise "Configuration file '#{config_pathname}' not found" if ! File.exist?(config_pathname)
|
13
|
+
check_permissions(config_pathname, 0600)
|
14
|
+
@settings = JSON.load(File.open(config_pathname))
|
15
|
+
end
|
16
|
+
|
17
|
+
def each_account
|
18
|
+
@settings['accounts'].each do |account|
|
19
|
+
a = Account.new(account)
|
20
|
+
yield a
|
21
|
+
a.disconnect
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Imap
|
4
|
+
module Backup
|
5
|
+
module Utils
|
6
|
+
|
7
|
+
def check_permissions(filename, limit)
|
8
|
+
stat = File.stat(filename)
|
9
|
+
actual = stat.mode & 0777
|
10
|
+
mask = ~limit & 0777
|
11
|
+
if actual & mask != 0
|
12
|
+
raise "Permissions on '#{filename}' should be #{oct(limit)}, not #{oct(actual)}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def make_folder(base_path, path, permissions)
|
17
|
+
parts = path.split('/')
|
18
|
+
return if parts.size == 0
|
19
|
+
full_path = File.join(base_path, path)
|
20
|
+
FileUtils.mkdir_p(full_path)
|
21
|
+
first_directory = File.join(base_path, parts[0])
|
22
|
+
FileUtils.chmod_R(permissions, first_directory)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def oct(permissions)
|
28
|
+
"0%o" % permissions
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
if RUBY_VERSION < '1.9'
|
4
|
+
require 'rspec/autorun'
|
5
|
+
else
|
6
|
+
require 'simplecov'
|
7
|
+
if defined?( GATHER_RSPEC_COVERAGE )
|
8
|
+
SimpleCov.start do
|
9
|
+
add_filter "/spec/"
|
10
|
+
add_filter "/vendor/"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
require File.expand_path( File.dirname(__FILE__) + '/../lib/imap/backup' )
|
16
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
load File.expand_path( '../spec_helper.rb', File.dirname(__FILE__) )
|
3
|
+
|
4
|
+
describe Imap::Backup::Account do
|
5
|
+
|
6
|
+
context '#initialize' do
|
7
|
+
|
8
|
+
it 'should login to the imap server' do
|
9
|
+
imap = stub('Net::IMAP')
|
10
|
+
Net::IMAP.should_receive(:new).with('imap.gmail.com', 993, true).and_return(imap)
|
11
|
+
imap.should_receive('login').with('myuser', 'secret')
|
12
|
+
|
13
|
+
Imap::Backup::Account.new('username' => 'myuser', 'password' => 'secret')
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'with imap' do
|
19
|
+
before :each do
|
20
|
+
@imap = stub('Net::IMAP', :login => nil)
|
21
|
+
Net::IMAP.stub!(:new).and_return(@imap)
|
22
|
+
end
|
23
|
+
|
24
|
+
subject { Imap::Backup::Account.new('username' => 'myuser', 'password' => 'secret') }
|
25
|
+
|
26
|
+
context '#disconnect' do
|
27
|
+
it 'should disconnect from the server' do
|
28
|
+
@imap.should_receive(:disconnect)
|
29
|
+
|
30
|
+
subject.disconnect
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context '#folders' do
|
35
|
+
it 'should list all folders' do
|
36
|
+
@imap.should_receive(:list).with('/', '*')
|
37
|
+
|
38
|
+
subject.folders
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context '#each_uid' do
|
43
|
+
it 'should examine the folder' do
|
44
|
+
@imap.stub!(:uid_search => [])
|
45
|
+
@imap.should_receive(:examine).with('my_folder')
|
46
|
+
|
47
|
+
subject.each_uid('my_folder') {}
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should call the block with each message uid' do
|
51
|
+
@imap.stub!(:examine).with('my_folder')
|
52
|
+
@imap.should_receive(:uid_search).with(['ALL']).and_return(['123', '456'])
|
53
|
+
|
54
|
+
uids = []
|
55
|
+
subject.each_uid('my_folder') do |uid|
|
56
|
+
uids << uid
|
57
|
+
end
|
58
|
+
|
59
|
+
uids.should == ['123', '456']
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context '#fetch' do
|
64
|
+
before :each do
|
65
|
+
@message_body = 'the body'
|
66
|
+
@message = {
|
67
|
+
'RFC822' => @message_body,
|
68
|
+
'other' => 'xxx'
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should request the message, the flags and the date' do
|
73
|
+
@imap.should_receive(:uid_fetch).
|
74
|
+
with(['123'], ['RFC822', 'FLAGS', 'INTERNALDATE']).
|
75
|
+
and_return([[nil, @message]])
|
76
|
+
|
77
|
+
subject.fetch('123')
|
78
|
+
end
|
79
|
+
|
80
|
+
if RUBY_VERSION > '1.9'
|
81
|
+
it 'should set the encoding on the message' do
|
82
|
+
@imap.stub!(:uid_fetch => [[nil, @message]])
|
83
|
+
|
84
|
+
@message_body.should_receive(:force_encoding).with('utf-8')
|
85
|
+
|
86
|
+
subject.fetch('123')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
load File.expand_path( '../spec_helper.rb', File.dirname(__FILE__) )
|
3
|
+
|
4
|
+
describe Imap::Backup::Downloader do
|
5
|
+
|
6
|
+
context '#initialize' do
|
7
|
+
|
8
|
+
it 'should fail if download path file permissions are to lax' do
|
9
|
+
account = stub('Imap::Backup::Account', :local_path => 'foobar')
|
10
|
+
stat = stub('File::Stat', :mode => 0345)
|
11
|
+
File.should_receive(:stat).with('foobar').and_return(stat)
|
12
|
+
|
13
|
+
expect do
|
14
|
+
Imap::Backup::Downloader.new(account, 'foo')
|
15
|
+
end.to raise_error(RuntimeError, "Permissions on 'foobar' should be 0700, not 0345")
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
context '#run' do
|
21
|
+
|
22
|
+
before :each do
|
23
|
+
stat = stub('File::Stat', :mode => 0700)
|
24
|
+
File.stub!(:stat => stat)
|
25
|
+
|
26
|
+
@account = stub('Imap::Backup::Account', :local_path => '/base/path')
|
27
|
+
@d = Imap::Backup::Downloader.new(@account, 'my_folder')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should create the folder and update permissions' do
|
31
|
+
@account.stub!(:each_uid)
|
32
|
+
FileUtils.should_receive(:mkdir_p).with('/base/path/my_folder')
|
33
|
+
FileUtils.should_receive(:chmod_R).with('g-wrx,o-wrx', '/base/path/my_folder')
|
34
|
+
|
35
|
+
@d.run
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'with folder' do
|
39
|
+
before :each do
|
40
|
+
FileUtils.stub!(:mkdir_p)
|
41
|
+
FileUtils.stub!(:chmod_R)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should list messages' do
|
45
|
+
@account.should_receive(:each_uid)
|
46
|
+
|
47
|
+
@d.run
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'with messages' do
|
51
|
+
before :each do
|
52
|
+
@account.should_receive(:each_uid) do |&block|
|
53
|
+
block.call '123'
|
54
|
+
block.call '999'
|
55
|
+
block.call '1234'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should check if messages exist' do
|
60
|
+
File.should_receive(:exist?).with(%r{/base/path/my_folder/\d+.json}).exactly(3).times.and_return(true)
|
61
|
+
|
62
|
+
@d.run
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should skip messages that are downloaded' do
|
66
|
+
File.stub!(:exist?).and_return(true)
|
67
|
+
|
68
|
+
@account.should_not_receive(:fetch)
|
69
|
+
|
70
|
+
@d.run
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'to download' do
|
74
|
+
before :each do
|
75
|
+
# N.B. messages 999 and 1234 wil be 'fetched'
|
76
|
+
File.stub!(:exist?) do |path|
|
77
|
+
if path =~ %r{123.json$}
|
78
|
+
true
|
79
|
+
else
|
80
|
+
false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
@message = {
|
85
|
+
'RFC822' => 'the body',
|
86
|
+
'other' => 'xxx'
|
87
|
+
}
|
88
|
+
@account.stub!(:fetch => @message)
|
89
|
+
File.stub!(:open)
|
90
|
+
FileUtils.stub!(:chmod)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should request messages' do
|
94
|
+
@account.should_receive(:fetch).with('999')
|
95
|
+
@account.should_receive(:fetch).with('1234')
|
96
|
+
|
97
|
+
@d.run
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'should save messages' do
|
101
|
+
file = stub('File')
|
102
|
+
File.should_receive(:open) do |&block|
|
103
|
+
block.call file
|
104
|
+
end
|
105
|
+
file.should_receive(:write).with(/the body/)
|
106
|
+
|
107
|
+
@d.run
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should JSON encode messages' do
|
111
|
+
file = stub('File', :write => nil)
|
112
|
+
File.stub!(:open) do |&block|
|
113
|
+
block.call file
|
114
|
+
end
|
115
|
+
|
116
|
+
@message.should_receive(:to_json).twice
|
117
|
+
|
118
|
+
@d.run
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should set file permissions' do
|
122
|
+
FileUtils.should_receive(:chmod).with(0600, /999.json$/)
|
123
|
+
FileUtils.should_receive(:chmod).with(0600, /1234.json$/)
|
124
|
+
|
125
|
+
@d.run
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
load File.expand_path( '../spec_helper.rb', File.dirname(__FILE__) )
|
3
|
+
|
4
|
+
describe Imap::Backup::Settings do
|
5
|
+
|
6
|
+
context '#initialize' do
|
7
|
+
|
8
|
+
it 'should fail if the config file is missing' do
|
9
|
+
File.should_receive(:exist?).and_return(false)
|
10
|
+
|
11
|
+
expect do
|
12
|
+
Imap::Backup::Settings.new
|
13
|
+
end.to raise_error(RuntimeError, /not found/)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should fail if the config file permissions are too lax' do
|
17
|
+
File.stub!(:exist?).and_return(true)
|
18
|
+
|
19
|
+
stat = stub('File::Stat', :mode => 0644)
|
20
|
+
File.should_receive(:stat).and_return(stat)
|
21
|
+
|
22
|
+
expect do
|
23
|
+
Imap::Backup::Settings.new
|
24
|
+
end.to raise_error(RuntimeError, /Permissions.*?should be 0600/)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should load the config file' do
|
28
|
+
File.stub!(:exist?).and_return(true)
|
29
|
+
|
30
|
+
stat = stub('File::Stat', :mode => 0600)
|
31
|
+
File.stub!(:stat).and_return(stat)
|
32
|
+
|
33
|
+
file = stub('file')
|
34
|
+
File.should_receive(:open).with(%r{/.imap-backup/config.json}).and_return(file)
|
35
|
+
JSON.should_receive(:load).with(file)
|
36
|
+
|
37
|
+
Imap::Backup::Settings.new
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
context '#each_account' do
|
43
|
+
before :each do
|
44
|
+
@account1_settings = stub('account1 settings')
|
45
|
+
settings = {
|
46
|
+
'accounts' => [
|
47
|
+
@account1_settings
|
48
|
+
]
|
49
|
+
}
|
50
|
+
File.stub!(:open)
|
51
|
+
JSON.stub!(:load).and_return(settings)
|
52
|
+
@account = stub('Imap::Backup::Settings', :disconnect => nil)
|
53
|
+
end
|
54
|
+
|
55
|
+
subject { Imap::Backup::Settings.new }
|
56
|
+
|
57
|
+
it 'should create accounts' do
|
58
|
+
Imap::Backup::Account.should_receive(:new).with(@account1_settings).and_return(@account)
|
59
|
+
subject.each_account {}
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should call the block' do
|
63
|
+
Imap::Backup::Account.stub!(:new).and_return(@account)
|
64
|
+
calls = 0
|
65
|
+
|
66
|
+
subject.each_account do |a|
|
67
|
+
calls += 1
|
68
|
+
a.should == @account
|
69
|
+
end
|
70
|
+
calls.should == 1
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should disconnect the account' do
|
74
|
+
Imap::Backup::Account.stub!(:new).and_return(@account)
|
75
|
+
|
76
|
+
@account.should_receive(:disconnect)
|
77
|
+
|
78
|
+
subject.each_account {}
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
load File.expand_path( '../spec_helper.rb', File.dirname(__FILE__) )
|
3
|
+
|
4
|
+
describe Imap::Backup::Utils do
|
5
|
+
|
6
|
+
include Imap::Backup::Utils
|
7
|
+
|
8
|
+
context '#check_permissions' do
|
9
|
+
|
10
|
+
it 'should stat the file' do
|
11
|
+
stat = stub('File::Stat', :mode => 0100)
|
12
|
+
File.should_receive(:stat).with('foobar').and_return(stat)
|
13
|
+
|
14
|
+
check_permissions('foobar', 0345)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should succeed if file permissions are less than limit' do
|
18
|
+
stat = stub('File::Stat', :mode => 0100)
|
19
|
+
File.stub!(:stat).and_return(stat)
|
20
|
+
|
21
|
+
expect do
|
22
|
+
check_permissions('foobar', 0345)
|
23
|
+
end.to_not raise_error
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should succeed if file permissions are equal to limit' do
|
27
|
+
stat = stub('File::Stat', :mode => 0345)
|
28
|
+
File.stub!(:stat).and_return(stat)
|
29
|
+
|
30
|
+
expect do
|
31
|
+
check_permissions('foobar', 0345)
|
32
|
+
end.to_not raise_error
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should fail if file permissions are over the limit' do
|
36
|
+
stat = stub('File::Stat', :mode => 0777)
|
37
|
+
File.stub!(:stat).and_return(stat)
|
38
|
+
|
39
|
+
expect do
|
40
|
+
check_permissions('foobar', 0345)
|
41
|
+
end.to raise_error(RuntimeError, "Permissions on 'foobar' should be 0345, not 0777")
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
context '#make_folder' do
|
47
|
+
|
48
|
+
it 'should do nothing if an empty path is supplied' do
|
49
|
+
FileUtils.should_not_receive(:mkdir_p)
|
50
|
+
|
51
|
+
make_folder('aaa', '', 0222)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should create the path' do
|
55
|
+
FileUtils.stub!(:chmod_R)
|
56
|
+
|
57
|
+
FileUtils.should_receive(:mkdir_p).with('/base/path/new/folder')
|
58
|
+
|
59
|
+
make_folder('/base/path', 'new/folder', 0222)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should set permissions on the path' do
|
63
|
+
FileUtils.stub!(:mkdir_p)
|
64
|
+
|
65
|
+
FileUtils.should_receive(:chmod_R).with(0222, '/base/path/new')
|
66
|
+
|
67
|
+
make_folder('/base/path', 'new/folder', 0222)
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
metadata
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: imap-backup
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Joe Yates
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-06-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: pry
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: pry-doc
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.3.0
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.3.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: simplecov
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Backup GMail, or any other IMAP email service, to disk.
|
79
|
+
email:
|
80
|
+
- joe.g.yates@gmail.com
|
81
|
+
executables:
|
82
|
+
- imap-backup
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- .travis.yml
|
88
|
+
- Gemfile
|
89
|
+
- LICENSE
|
90
|
+
- README.md
|
91
|
+
- Rakefile
|
92
|
+
- bin/imap-backup
|
93
|
+
- imap-backup.gemspec
|
94
|
+
- lib/imap/backup.rb
|
95
|
+
- lib/imap/backup/account.rb
|
96
|
+
- lib/imap/backup/downloader.rb
|
97
|
+
- lib/imap/backup/settings.rb
|
98
|
+
- lib/imap/backup/utils.rb
|
99
|
+
- lib/imap/backup/version.rb
|
100
|
+
- spec/gather_rspec_coverage.rb
|
101
|
+
- spec/spec_helper.rb
|
102
|
+
- spec/unit/account_spec.rb
|
103
|
+
- spec/unit/downloader_spec.rb
|
104
|
+
- spec/unit/settings_spec.rb
|
105
|
+
- spec/unit/utils_spec.rb
|
106
|
+
homepage: https://github.com/joeyates/imap-backup
|
107
|
+
licenses: []
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
segments:
|
119
|
+
- 0
|
120
|
+
hash: 3027570741254356503
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ! '>='
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
hash: 3027570741254356503
|
130
|
+
requirements: []
|
131
|
+
rubyforge_project:
|
132
|
+
rubygems_version: 1.8.23
|
133
|
+
signing_key:
|
134
|
+
specification_version: 3
|
135
|
+
summary: Backup GMail (or other IMAP) accounts to disk
|
136
|
+
test_files:
|
137
|
+
- spec/gather_rspec_coverage.rb
|
138
|
+
- spec/spec_helper.rb
|
139
|
+
- spec/unit/account_spec.rb
|
140
|
+
- spec/unit/downloader_spec.rb
|
141
|
+
- spec/unit/settings_spec.rb
|
142
|
+
- spec/unit/utils_spec.rb
|
143
|
+
has_rdoc:
|