imap_archiver 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +78 -0
- data/VERSION +1 -0
- data/autotest/discover.rb +1 -0
- data/bin/imap_archiver +4 -0
- data/cucumber.yml +10 -0
- data/features/configuration.feature +44 -0
- data/features/step_definitions/configuration_steps.rb +14 -0
- data/features/support/env.rb +17 -0
- data/lib/imap_archiver.rb +15 -0
- data/lib/imap_archiver/archiver.rb +105 -0
- data/lib/imap_archiver/cli.rb +38 -0
- data/lib/imap_archiver/config.rb +47 -0
- data/spec/archiver_spec.rb +111 -0
- data/spec/cli_spec.rb +30 -0
- data/spec/imap_archiver_spec.rb +7 -0
- data/spec/integration_spec.rb +140 -0
- data/spec/spec_helper.rb +10 -0
- metadata +122 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Jelle Helsen
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= imap_archiver
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
13
|
+
* Send me a pull request. Bonus points for topic branches.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2010 Jelle Helsen. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "imap_archiver"
|
8
|
+
gem.summary = %Q{Tool to archive lots of messages in an imap server}
|
9
|
+
gem.description = %Q{imap_archiver is a command line tool to archive messages on an imap server.
|
10
|
+
You tell it what folders to archive and where to archive it.
|
11
|
+
For every folder that is archived a series of folders (one for each month) is created inside the archive folder.}
|
12
|
+
gem.email = "jelle.helsen@hcode.be"
|
13
|
+
gem.homepage = "http://github.com/jellehelsen/imap_archiver"
|
14
|
+
gem.authors = ["Jelle Helsen"]
|
15
|
+
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
16
|
+
gem.add_development_dependency "aruba", ">= 0"
|
17
|
+
|
18
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
19
|
+
end
|
20
|
+
Jeweler::GemcutterTasks.new
|
21
|
+
rescue LoadError
|
22
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'rake/testtask'
|
26
|
+
Rake::TestTask.new(:test) do |test|
|
27
|
+
test.libs << 'lib' << 'test'
|
28
|
+
test.pattern = 'test/**/test_*.rb'
|
29
|
+
test.verbose = true
|
30
|
+
end
|
31
|
+
|
32
|
+
begin
|
33
|
+
require 'rcov/rcovtask'
|
34
|
+
Rcov::RcovTask.new do |test|
|
35
|
+
test.libs << 'test'
|
36
|
+
test.pattern = 'test/**/test_*.rb'
|
37
|
+
test.verbose = true
|
38
|
+
end
|
39
|
+
rescue LoadError
|
40
|
+
task :rcov do
|
41
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
task :test => :check_dependencies
|
46
|
+
require 'rspec/core/rake_task'
|
47
|
+
RSpec::Core::RakeTask.new(:spec)
|
48
|
+
|
49
|
+
# Spec::Rake::SpecTask.new(:rcov) do |spec|
|
50
|
+
# spec.libs << 'lib' << 'spec'
|
51
|
+
# spec.pattern = 'spec/**/*_spec.rb'
|
52
|
+
# spec.rcov = true
|
53
|
+
# end
|
54
|
+
|
55
|
+
# task :spec => :check_dependencies
|
56
|
+
|
57
|
+
begin
|
58
|
+
require 'cucumber/rake/task'
|
59
|
+
Cucumber::Rake::Task.new(:features)
|
60
|
+
|
61
|
+
task :features => :check_dependencies
|
62
|
+
rescue LoadError
|
63
|
+
task :features do
|
64
|
+
abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
task :default => :spec
|
69
|
+
|
70
|
+
require 'rake/rdoctask'
|
71
|
+
Rake::RDocTask.new do |rdoc|
|
72
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
73
|
+
|
74
|
+
rdoc.rdoc_dir = 'rdoc'
|
75
|
+
rdoc.title = "imap_archiver #{version}"
|
76
|
+
rdoc.rdoc_files.include('README*')
|
77
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
78
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1 @@
|
|
1
|
+
Autotest.add_discovery { "rspec2" }
|
data/bin/imap_archiver
ADDED
data/cucumber.yml
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
<%
|
2
|
+
rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : ""
|
3
|
+
rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}"
|
4
|
+
std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} --strict --color"
|
5
|
+
%>
|
6
|
+
default: <%= std_opts %> features
|
7
|
+
wip: --tags @wip:3 --wip features
|
8
|
+
rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip
|
9
|
+
autotest: --format pretty --tags @wip --wip features --color
|
10
|
+
autotest-all: <%= std_opts %> features
|
@@ -0,0 +1,44 @@
|
|
1
|
+
Feature: Configuring imaparchiver
|
2
|
+
In order to configure imaparchiver
|
3
|
+
A configuration file should be read
|
4
|
+
|
5
|
+
Scenario: Parsing the configuration file
|
6
|
+
Given a file named "/tmp/imap_archiver_config.rb" with:
|
7
|
+
"""
|
8
|
+
ImapArchiver::Config.run do |config|
|
9
|
+
config.imap_server = 'imap.example.net'
|
10
|
+
config.username = 'jhelsen'
|
11
|
+
config.password = 'secret'
|
12
|
+
config.folders_to_archive = /test/
|
13
|
+
config.archive_folder = "/Archive/test"
|
14
|
+
end
|
15
|
+
"""
|
16
|
+
When I load the configuration file "/tmp/imap_archiver_config.rb"
|
17
|
+
Then the configuration imap_server should be "imap.example.net"
|
18
|
+
And the configuration username should be "jhelsen"
|
19
|
+
And the configuration password should be "secret"
|
20
|
+
And the configuration folders_to_archive should be "/test/"
|
21
|
+
And the configuration archive_folder should be "/Archive/test"
|
22
|
+
|
23
|
+
Scenario: starting without a configuration file
|
24
|
+
Given I have no file "/tmp/imap_archiver_config.rb"
|
25
|
+
When I run "../../bin/imap_archiver -F /tmp/imap_archiver_config.rb"
|
26
|
+
Then it should fail with:
|
27
|
+
"""
|
28
|
+
Config_file not found!
|
29
|
+
"""
|
30
|
+
|
31
|
+
|
32
|
+
Scenario: starting with an invalid configuration file
|
33
|
+
Given a file named "/tmp/imap_archiver_config.rb" with:
|
34
|
+
"""
|
35
|
+
ImapArchiver::Config.run do |config|
|
36
|
+
end
|
37
|
+
"""
|
38
|
+
When I run "../../bin/imap_archiver -F /tmp/imap_archiver_config.rb"
|
39
|
+
Then it should fail with:
|
40
|
+
"""
|
41
|
+
No imap server in configuration file!
|
42
|
+
"""
|
43
|
+
|
44
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Given /^I have no file "([^"]*)"$/ do |filename|
|
2
|
+
`rm -f #{filename}`
|
3
|
+
end
|
4
|
+
|
5
|
+
When /^I load the configuration file "([^"]*)"$/ do |filename|
|
6
|
+
@archiver = ImapArchiver::Archiver.new(filename)
|
7
|
+
end
|
8
|
+
|
9
|
+
Then /^the configuration ([^"]*) should be "([^"]*)"$/ do |key,value|
|
10
|
+
if value =~ /^\/.*\/$/
|
11
|
+
value = Regexp.compile(value.gsub("/",""))
|
12
|
+
end
|
13
|
+
@archiver.send(key).should == value
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
|
2
|
+
require 'imap_archiver'
|
3
|
+
|
4
|
+
require "aruba"
|
5
|
+
# require 'rspec/expectations'
|
6
|
+
# require 'cucumber/rspec/doubles'
|
7
|
+
require 'rspec/core'
|
8
|
+
require "rspec/mocks"
|
9
|
+
RSpec.configure do |c|
|
10
|
+
# c.mock_framework = :rspec
|
11
|
+
c.mock_framework = :mocha
|
12
|
+
# c.mock_framework = :rr
|
13
|
+
# c.mock_framework = :flexmock
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'cucumber/rspec/doubles'
|
17
|
+
# require 'spec/stubs/cucumber'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
$:.unshift(File.dirname(__FILE__)+'/imap_archiver')
|
4
|
+
require "rubygems"
|
5
|
+
require "net/imap"
|
6
|
+
require "date"
|
7
|
+
require "active_support/all"
|
8
|
+
require "pathname"
|
9
|
+
require "archiver"
|
10
|
+
require "cli"
|
11
|
+
require "config"
|
12
|
+
require "ostruct"
|
13
|
+
module ImapArchiver
|
14
|
+
Version = File.exist?('VERSION') ? File.read('VERSION').strip : ""
|
15
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require "config"
|
2
|
+
module ImapArchiver
|
3
|
+
class Archiver
|
4
|
+
include ::ImapArchiver::Config
|
5
|
+
attr_accessor :connection
|
6
|
+
def initialize(config_file)
|
7
|
+
self.load_config(config_file)
|
8
|
+
config_valid?
|
9
|
+
end
|
10
|
+
|
11
|
+
def connect
|
12
|
+
self.reconnect
|
13
|
+
@msg_count = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def reconnect
|
17
|
+
self.connection = Net::IMAP.new(imap_server) rescue nil
|
18
|
+
capability = self.connection.capability rescue nil
|
19
|
+
if capability.detect {|c| c =~ /AUTH=(CRAM|DIGEST)-MD5/}
|
20
|
+
puts "loging in with #{auth_mech}"
|
21
|
+
self.connection.authenticate(auth_mech,username,password)
|
22
|
+
else
|
23
|
+
puts "plain login"
|
24
|
+
self.connection.login(username,password) rescue nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def folder_list
|
29
|
+
connect if connection.nil?
|
30
|
+
if folders_to_archive.is_a? Array
|
31
|
+
folders = folders_to_archive.map {|f| connection.list('',f)}
|
32
|
+
return folders.delete_if(&:nil?).map(&:first)
|
33
|
+
end
|
34
|
+
if folders_to_archive.is_a? Regexp
|
35
|
+
folders = connection.list("","*")
|
36
|
+
return folders.delete_if {|f| (f.name =~ folders_to_archive).nil? }
|
37
|
+
end
|
38
|
+
connection.list(base_folder,"*")
|
39
|
+
end
|
40
|
+
|
41
|
+
def start
|
42
|
+
folder_list.each do |folder|
|
43
|
+
since_date = Date.today.months_ago(3).beginning_of_month
|
44
|
+
before_date= Date.today.months_ago(3).end_of_month
|
45
|
+
archive_folder_between_dates(folder.name,since_date,before_date) #if folder.name =~ /^Public Folders\/Team\//
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def archive_folder_between_dates(folder, since_date, before_date)
|
50
|
+
tmp_folder = Pathname.new(folder).relative_path_from(Pathname.new(base_folder)).to_s
|
51
|
+
current_archive_folder = "#{archive_folder}/#{tmp_folder}/#{since_date.strftime("%b %Y")}"
|
52
|
+
# puts archive_folder
|
53
|
+
conditions = ["SINCE", since_date.strftime("%d-%b-%Y"), "BEFORE", before_date.strftime("%d-%b-%Y"), "SEEN", "NOT", "FLAGGED"]
|
54
|
+
retry_count = 0
|
55
|
+
begin
|
56
|
+
connection.select(folder)
|
57
|
+
# puts "will search 1"
|
58
|
+
msgs_to_archive = connection.search(conditions)
|
59
|
+
if msgs_to_archive.size > 0
|
60
|
+
# puts "will archive #{msgs_to_archive.size} messages"
|
61
|
+
if connection.list("",current_archive_folder).nil?
|
62
|
+
connection.create(current_archive_folder)
|
63
|
+
if connection.capability.include?("ACL")
|
64
|
+
self.archive_folder_acl.each do |key,value|
|
65
|
+
connection.setacl(current_archive_folder,key,value)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
connection.copy(msgs_to_archive, current_archive_folder)
|
70
|
+
connection.store(msgs_to_archive, "+FLAGS",[:Deleted])
|
71
|
+
@msg_count += msgs_to_archive.size
|
72
|
+
connection.expunge
|
73
|
+
end
|
74
|
+
if connection.search(["BEFORE", since_date.strftime("%d-%b-%Y"), "SEEN", "NOT", "FLAGGED"]).size > 0
|
75
|
+
archive_folder_between_dates(folder,since_date.months_ago(1), since_date-1)
|
76
|
+
end
|
77
|
+
rescue IOError => e
|
78
|
+
retry_count += 1
|
79
|
+
# puts "retrying"
|
80
|
+
if retry_count < 3
|
81
|
+
reconnect
|
82
|
+
retry
|
83
|
+
else
|
84
|
+
puts "Error archiving #{folder} to #{archive_folder}: #{e}"
|
85
|
+
puts e.backtrace
|
86
|
+
end
|
87
|
+
rescue Net::IMAP::NoResponseError => e
|
88
|
+
puts "#{e}: #{folder}: #{e.backtrace.join("\n")}"
|
89
|
+
# rescue Exception => e
|
90
|
+
# retry_count += 1
|
91
|
+
# if retry_count < 3
|
92
|
+
# puts "retrying #{e}"
|
93
|
+
# retry
|
94
|
+
# else
|
95
|
+
# puts "Error archiving #{folder} to #{archive_folder}: #{e}"
|
96
|
+
# puts e.backtrace
|
97
|
+
# end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.run(config_file)
|
102
|
+
self.new(config_file).start
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module ImapArchiver
|
4
|
+
class CLI
|
5
|
+
def self.execute(stdout,stdin, arguments=[])
|
6
|
+
|
7
|
+
# NOTE: the option -p/--path= is given as an example, and should be replaced in your application.
|
8
|
+
options = {
|
9
|
+
:config_file => "~/.imap_archiver.rb"
|
10
|
+
}
|
11
|
+
mandatory_options = %w( )
|
12
|
+
|
13
|
+
parser = OptionParser.new do |opts|
|
14
|
+
opts.banner = <<-BANNER.gsub(/^ /,'')
|
15
|
+
This application is wonderful because...
|
16
|
+
|
17
|
+
Usage: #{File.basename($0)} [options]
|
18
|
+
|
19
|
+
Options are:
|
20
|
+
BANNER
|
21
|
+
opts.separator ""
|
22
|
+
opts.on("-h", "--help",
|
23
|
+
"Show this help message.") { stdout.puts opts; exit }
|
24
|
+
opts.on("-F PATH", "", String, "Configuration file", "Default: ~/.imap_archiver.yml"){|arg| options[:config_file] = arg}
|
25
|
+
opts.parse!(arguments)
|
26
|
+
|
27
|
+
if mandatory_options && mandatory_options.find { |option| options[option.to_sym].nil? }
|
28
|
+
stdout.puts opts; exit
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Config.load_config(options[:config_file])
|
33
|
+
Archiver.run(options[:config_file])
|
34
|
+
|
35
|
+
return 0
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
module ImapArchiver
|
3
|
+
module Config
|
4
|
+
attr_accessor :imap_server, :username, :password, :auth_mech, :base_folder, :archive_folder, :folders_to_archive, :archive_folder_acl
|
5
|
+
|
6
|
+
# @@config_struct = ::OpenStruct.new({:imap_server => '',
|
7
|
+
# :username => '',
|
8
|
+
# :password => ''})
|
9
|
+
|
10
|
+
def self.load_config(config_file)
|
11
|
+
begin
|
12
|
+
load config_file
|
13
|
+
rescue LoadError => e
|
14
|
+
raise "Config_file not found! #{config_file} #{e}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def load_config(config_file)
|
19
|
+
@@base = self
|
20
|
+
ImapArchiver::Config.load_config(config_file)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.run
|
24
|
+
yield self
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.included(base)
|
28
|
+
base.extend(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
# def method_missing(method, *args)
|
32
|
+
# @@config_struct.send(method,*args)
|
33
|
+
# end
|
34
|
+
|
35
|
+
def config_valid?
|
36
|
+
self.auth_mech ||= "CRAM-MD5"
|
37
|
+
raise "No imap server in configuration file!" if self.imap_server.nil?
|
38
|
+
raise "No username in configuration file!" if username.nil?
|
39
|
+
raise "No password in configuration file!" if password.nil?
|
40
|
+
end
|
41
|
+
# private
|
42
|
+
def self.method_missing(method, *args)
|
43
|
+
@@base.send(method,*args)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
describe ImapArchiver::Archiver do
|
3
|
+
it "should load and validate the configuration" do
|
4
|
+
ImapArchiver::Config.expects(:load_config)
|
5
|
+
ImapArchiver::Archiver.any_instance.expects(:config_valid?)
|
6
|
+
ImapArchiver::Archiver.new("configfile")
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "IMAP connection" do
|
10
|
+
before(:each) do
|
11
|
+
ImapArchiver::Config.expects(:load_config)
|
12
|
+
ImapArchiver::Archiver.any_instance.expects(:config_valid?)
|
13
|
+
@archiver = ImapArchiver::Archiver.new("configfile")
|
14
|
+
@archiver.imap_server = "mailserver"
|
15
|
+
@archiver.username = "user"
|
16
|
+
@archiver.password = "password"
|
17
|
+
@archiver.auth_mech = 'CRAM-MD5'
|
18
|
+
@archiver.base_folder = 'Public Folders'
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should create and authenticate an imap connection when calling reconnect" do
|
22
|
+
connection = mock(:capability => ["AUTH=CRAM-MD5"])
|
23
|
+
Net::IMAP.expects(:new).with("mailserver").returns(connection)
|
24
|
+
connection.expects(:authenticate).with("CRAM-MD5","user","password")
|
25
|
+
@archiver.reconnect
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should fallback to plain login if no other authentication mechanisms are available" do
|
29
|
+
connection = mock()
|
30
|
+
Net::IMAP.expects(:new).with("mailserver").returns(connection)
|
31
|
+
connection.expects(:capability).returns(%w(AUTH=PLAIN AUTH=LOGIN))
|
32
|
+
connection.expects(:login).with("user","password")
|
33
|
+
@archiver.connect
|
34
|
+
|
35
|
+
end
|
36
|
+
it "should reconnect when calling connect" do
|
37
|
+
@archiver.expects(:reconnect)
|
38
|
+
@archiver.connect
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should reconnect when an IOError is raised during archiving" do
|
42
|
+
@archiver.instance_variable_set(:@msg_count, 0)
|
43
|
+
@archiver.connection.expects(:select).times(2)
|
44
|
+
@archiver.connection.expects(:search).times(3).returns([1],[1],[])
|
45
|
+
@archiver.connection.expects(:list).times(2).returns(true)
|
46
|
+
@archiver.connection.stubs(:copy).raises(IOError).then.returns(nil)
|
47
|
+
@archiver.expects(:reconnect).times(1)
|
48
|
+
@archiver.connection.expects(:store)
|
49
|
+
@archiver.connection.expects(:expunge)
|
50
|
+
@archiver.archive_folder_between_dates('Public Folders/test',Date.today,Date.today)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should list all folders in base_folder when starting archiving" do
|
54
|
+
@archiver.expects(:folder_list).returns([])
|
55
|
+
@archiver.start
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should copy and delete all found messages" do
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "listing folders" do
|
64
|
+
before(:each) do
|
65
|
+
ImapArchiver::Config.expects(:load_config)
|
66
|
+
ImapArchiver::Archiver.any_instance.expects(:config_valid?)
|
67
|
+
@archiver = ImapArchiver::Archiver.new("configfile")
|
68
|
+
@archiver.imap_server = "mailserver"
|
69
|
+
@archiver.username = "user"
|
70
|
+
@archiver.password = "password"
|
71
|
+
@archiver.auth_mech = 'CRAM-MD5'
|
72
|
+
@archiver.archive_folder = 'archive'
|
73
|
+
@archiver.base_folder = 'Public Folders'
|
74
|
+
# @archiver.expects(:connect)
|
75
|
+
@archiver.connection = stub_everything
|
76
|
+
end
|
77
|
+
it "should list all folders in folders_to_archive array" do
|
78
|
+
@archiver.folders_to_archive = ["Public Folders/test1", "test2"]
|
79
|
+
@archiver.connection.expects(:list).with('',"Public Folders/test1").returns([stub(:name => 'Public Folders/test1')])
|
80
|
+
@archiver.connection.expects(:list).with('',"test2").returns([stub(:name => 'test2')])
|
81
|
+
@archiver.connection.expects(:search).times(4).returns([])
|
82
|
+
@archiver.start
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should list all folders in folders_to_archive regexp" do
|
86
|
+
@archiver.folders_to_archive = /testregexp/
|
87
|
+
mock1 = mock(:name => 'hello')
|
88
|
+
mock2 = mock()
|
89
|
+
mock2.expects(:name).twice.returns('testregexp')
|
90
|
+
@archiver.connection.expects(:list).with('',"*").returns([mock1,mock2])
|
91
|
+
@archiver.expects(:archive_folder_between_dates)
|
92
|
+
@archiver.start
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should filter the folder_list when using a regexp" do
|
96
|
+
@archiver.folders_to_archive = /testregexp/
|
97
|
+
mock1 = mock(:name => 'hello')
|
98
|
+
mock2 = mock(:name => 'een testregexp test')
|
99
|
+
@archiver.connection.expects(:list).with('',"*").returns([mock1,mock2])
|
100
|
+
folder_list = @archiver.folder_list
|
101
|
+
folder_list.should == [mock2]
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should strip the folder_list of non-existing mailboxes" do
|
105
|
+
@archiver.folders_to_archive = ["Public Folders/test1", "test2"]
|
106
|
+
@archiver.connection.expects(:list).with('',"Public Folders/test1").returns([mock1 = mock()])
|
107
|
+
@archiver.connection.expects(:list).with('',"test2").returns(nil)
|
108
|
+
@archiver.folder_list.should == [mock1]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/spec/cli_spec.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
describe ImapArchiver::CLI do
|
3
|
+
it "should start the archiver with the default config file if no options are given" do
|
4
|
+
ImapArchiver::Archiver.expects(:run).with('~/.imap_archiver.rb')
|
5
|
+
ImapArchiver::CLI.execute(STDOUT,STDIN,[])
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should display the help message" do
|
9
|
+
help_message = """This application is wonderful because...
|
10
|
+
|
11
|
+
Usage: rspec [options]
|
12
|
+
|
13
|
+
Options are:
|
14
|
+
|
15
|
+
-h, --help Show this help message.
|
16
|
+
-F PATH
|
17
|
+
Configuration file
|
18
|
+
Default: ~/.imap_archiver.yml
|
19
|
+
"""
|
20
|
+
io = StringIO.new
|
21
|
+
|
22
|
+
lambda { ImapArchiver::CLI.execute(io,STDIN,['-h']) }.should raise_error(SystemExit)
|
23
|
+
io.string.should == help_message
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should load the given config file" do
|
27
|
+
ImapArchiver::Archiver.expects(:run).with('/tmp/imap_archiver.rb')
|
28
|
+
ImapArchiver::CLI.execute(STDOUT,STDIN,%w(-F /tmp/imap_archiver.rb))
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require "tempfile"
|
3
|
+
describe "imap_archiver" do
|
4
|
+
describe "with configuration file with regexp folders_to_archive" do
|
5
|
+
before(:all) do
|
6
|
+
@config_file = File.new(File.join(Dir.tmpdir,"imap_archiver_config.rb"),'w+')
|
7
|
+
@config_file.write """
|
8
|
+
ImapArchiver::Config.run do |config|
|
9
|
+
config.imap_server = 'imap.example.net'
|
10
|
+
config.username = 'jhelsen'
|
11
|
+
config.password = 'secret'
|
12
|
+
config.folders_to_archive = /^test/
|
13
|
+
config.archive_folder = '/Archive/test'
|
14
|
+
config.base_folder = ''
|
15
|
+
config.archive_folder_acl = {'jhelsen' => 'lrswpcda'}
|
16
|
+
end
|
17
|
+
"""
|
18
|
+
@config_file.close
|
19
|
+
end
|
20
|
+
|
21
|
+
after(:all) do
|
22
|
+
# File.delete(@config_file.path)
|
23
|
+
end
|
24
|
+
|
25
|
+
before(:each) do
|
26
|
+
@connection = stub_everything(:capability => %w(AUTH=CRAM-MD5 ACL))
|
27
|
+
@expectation = Net::IMAP.expects(:new).with("imap.example.net").returns(@connection)
|
28
|
+
@archive_date = Date.today.months_ago(3).beginning_of_month
|
29
|
+
end
|
30
|
+
it "should create and authenticate an imap connection and list all folders" do
|
31
|
+
@connection.expects(:authenticate).with("CRAM-MD5","jhelsen","secret").returns(true)
|
32
|
+
@connection.expects(:list).with("","*").returns([])
|
33
|
+
flunk "Config file gone!!!" unless File.file?(@config_file.path)
|
34
|
+
ImapArchiver::CLI.execute(STDOUT,STDIN,["-F",@config_file.path])
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should select and search the matching mailboxes" do
|
38
|
+
@connection.expects(:list).with("","*").returns([mock1=stub(:name=>'testfolder'),mock2=stub(:name=>'foldertest')])
|
39
|
+
@connection.expects(:select).with('testfolder')
|
40
|
+
@connection.expects(:search).twice.returns([]) #search current range and before
|
41
|
+
ImapArchiver::CLI.execute(STDOUT,STDIN,["-F",@config_file.path])
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should create a mailbox for archiving if it does not exist" do
|
45
|
+
@connection.expects(:list).with("","*").returns([mock1=stub(:name=>'testfolder'),mock2=stub(:name=>'foldertest')])
|
46
|
+
@connection.expects(:select).with('testfolder')
|
47
|
+
@connection.expects(:search).twice.returns([1,2],[])
|
48
|
+
@connection.expects(:list).with('',"/Archive/test/testfolder/#{@archive_date.strftime("%b %Y")}")
|
49
|
+
@connection.expects(:create).with("/Archive/test/testfolder/#{@archive_date.strftime("%b %Y")}")
|
50
|
+
|
51
|
+
ImapArchiver::CLI.execute(STDOUT,STDIN,["-F",@config_file.path])
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should not create a mailbox for archiving if it does exist" do
|
55
|
+
@connection.expects(:list).with("","*").returns([mock1=stub(:name=>'testfolder'),mock2=stub(:name=>'foldertest')])
|
56
|
+
@connection.expects(:select).with('testfolder')
|
57
|
+
@connection.expects(:search).twice.returns([1,2],[])
|
58
|
+
@connection.expects(:list).with('',"/Archive/test/testfolder/#{@archive_date.strftime("%b %Y")}").returns(stub_everything('archive folder'))
|
59
|
+
@connection.expects(:create).never
|
60
|
+
|
61
|
+
ImapArchiver::CLI.execute(STDOUT,STDIN,["-F",@config_file.path])
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should copy messages found to the archive folder" do
|
65
|
+
@connection.expects(:list).with("","*").returns([mock1=stub(:name=>'testfolder'),mock2=stub(:name=>'foldertest')])
|
66
|
+
@connection.expects(:select).with('testfolder')
|
67
|
+
# @connection.expects(:search).twice.returns([1,2],[])
|
68
|
+
@connection.expects(:search).with(["SINCE", @archive_date.strftime("%d-%b-%Y"), "BEFORE", @archive_date.end_of_month.strftime("%d-%b-%Y"), "SEEN", "NOT", "FLAGGED"]).returns([1,2])
|
69
|
+
@connection.expects(:search).with(["BEFORE", @archive_date.strftime("%d-%b-%Y"), "SEEN", "NOT", "FLAGGED"]).returns([])
|
70
|
+
|
71
|
+
@connection.expects(:copy).with([1,2],"/Archive/test/testfolder/#{@archive_date.strftime("%b %Y")}")
|
72
|
+
|
73
|
+
ImapArchiver::CLI.execute(STDOUT,STDIN,["-F",@config_file.path])
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should delete the copied messages" do
|
78
|
+
@connection.expects(:list).with("","*").returns([mock1=stub(:name=>'testfolder'),mock2=stub(:name=>'foldertest')])
|
79
|
+
@connection.expects(:select).with('testfolder')
|
80
|
+
@connection.expects(:search).twice.returns([1,2],[])
|
81
|
+
@connection.expects(:store).with([1,2],"+FLAGS",[:Deleted])
|
82
|
+
@connection.expects(:expunge)
|
83
|
+
ImapArchiver::CLI.execute(STDOUT,STDIN,["-F",@config_file.path])
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should search for messages older then the archiving period" do
|
88
|
+
@connection.expects(:list).with("","*").returns([mock1=stub(:name=>'testfolder'),mock2=stub(:name=>'foldertest')])
|
89
|
+
@connection.expects(:select).with('testfolder').twice
|
90
|
+
@connection.expects(:search).with(["SINCE", @archive_date.strftime("%d-%b-%Y"), "BEFORE", @archive_date.end_of_month.strftime("%d-%b-%Y"), "SEEN", "NOT", "FLAGGED"]).returns([1,2])
|
91
|
+
@connection.expects(:search).with(["BEFORE", @archive_date.strftime("%d-%b-%Y"), "SEEN", "NOT", "FLAGGED"]).returns([3,4])
|
92
|
+
@archive_date = @archive_date.prev_month
|
93
|
+
@connection.expects(:search).with(["SINCE", @archive_date.strftime("%d-%b-%Y"), "BEFORE", @archive_date.end_of_month.strftime("%d-%b-%Y"), "SEEN", "NOT", "FLAGGED"]).returns([1,2])
|
94
|
+
@connection.expects(:search).with(["BEFORE", @archive_date.strftime("%d-%b-%Y"), "SEEN", "NOT", "FLAGGED"]).returns([])
|
95
|
+
|
96
|
+
ImapArchiver::CLI.execute(STDOUT,STDIN,["-F",@config_file.path])
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should set the correct acl on newly created archive folders" do
|
100
|
+
@connection.expects(:list).with("","*").returns([mock1=stub(:name=>'testfolder'),mock2=stub(:name=>'foldertest')])
|
101
|
+
@connection.expects(:select).with('testfolder')
|
102
|
+
@connection.expects(:search).twice.returns([1,2],[])
|
103
|
+
@connection.expects(:list).with('',"/Archive/test/testfolder/#{@archive_date.strftime("%b %Y")}")
|
104
|
+
@connection.expects(:create).with("/Archive/test/testfolder/#{@archive_date.strftime("%b %Y")}")
|
105
|
+
@connection.expects(:setacl).with("/Archive/test/testfolder/#{@archive_date.strftime("%b %Y")}",'jhelsen','lrswpcda')
|
106
|
+
ImapArchiver::CLI.execute(STDOUT,STDIN,["-F",@config_file.path])
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "with configuration file with array folders_to_archive" do
|
112
|
+
before(:all) do
|
113
|
+
@config_file = File.new(File.join(Dir.tmpdir,"imap_archiver_config.rb"),'w+')
|
114
|
+
@config_file.write """
|
115
|
+
ImapArchiver::Config.run do |config|
|
116
|
+
config.imap_server = 'imap.example.net'
|
117
|
+
config.username = 'jhelsen'
|
118
|
+
config.password = 'secret'
|
119
|
+
config.folders_to_archive = %w(test1 test2)
|
120
|
+
config.archive_folder = '/Archive/test'
|
121
|
+
config.base_folder = ''
|
122
|
+
end
|
123
|
+
"""
|
124
|
+
@config_file.close
|
125
|
+
end
|
126
|
+
|
127
|
+
before(:each) do
|
128
|
+
@connection = stub_everything(:capability => %w(AUTH=CRAM-MD5))
|
129
|
+
@expectation = Net::IMAP.expects(:new).with("imap.example.net").returns(@connection)
|
130
|
+
@archive_date = Date.today.months_ago(3).beginning_of_month
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should select and search the matching mailboxes" do
|
134
|
+
@connection.expects(:list).with("","test1").returns([mock1=stub(:name=>'test1')])
|
135
|
+
@connection.expects(:select).with('test1')
|
136
|
+
@connection.expects(:search).twice.returns([]) #search current range and before
|
137
|
+
ImapArchiver::CLI.execute(STDOUT,STDIN,["-F",@config_file.path])
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'imap_archiver'
|
4
|
+
require 'rspec'
|
5
|
+
require 'rspec/autorun'
|
6
|
+
require "mocha"
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.run_all_when_everything_filtered = true
|
9
|
+
config.mock_framework = :mocha
|
10
|
+
end
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: imap_archiver
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Jelle Helsen
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-09-01 00:00:00 +02:00
|
19
|
+
default_executable: imap_archiver
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: thoughtbot-shoulda
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: aruba
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
49
|
+
description: |-
|
50
|
+
imap_archiver is a command line tool to archive messages on an imap server.
|
51
|
+
You tell it what folders to archive and where to archive it.
|
52
|
+
For every folder that is archived a series of folders (one for each month) is created inside the archive folder.
|
53
|
+
email: jelle.helsen@hcode.be
|
54
|
+
executables:
|
55
|
+
- imap_archiver
|
56
|
+
extensions: []
|
57
|
+
|
58
|
+
extra_rdoc_files:
|
59
|
+
- LICENSE
|
60
|
+
- README.rdoc
|
61
|
+
files:
|
62
|
+
- .document
|
63
|
+
- .gitignore
|
64
|
+
- LICENSE
|
65
|
+
- README.rdoc
|
66
|
+
- Rakefile
|
67
|
+
- VERSION
|
68
|
+
- autotest/discover.rb
|
69
|
+
- bin/imap_archiver
|
70
|
+
- cucumber.yml
|
71
|
+
- features/configuration.feature
|
72
|
+
- features/step_definitions/configuration_steps.rb
|
73
|
+
- features/support/env.rb
|
74
|
+
- lib/imap_archiver.rb
|
75
|
+
- lib/imap_archiver/archiver.rb
|
76
|
+
- lib/imap_archiver/cli.rb
|
77
|
+
- lib/imap_archiver/config.rb
|
78
|
+
- spec/archiver_spec.rb
|
79
|
+
- spec/cli_spec.rb
|
80
|
+
- spec/imap_archiver_spec.rb
|
81
|
+
- spec/integration_spec.rb
|
82
|
+
- spec/spec_helper.rb
|
83
|
+
has_rdoc: true
|
84
|
+
homepage: http://github.com/jellehelsen/imap_archiver
|
85
|
+
licenses: []
|
86
|
+
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options:
|
89
|
+
- --charset=UTF-8
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
hash: 3
|
98
|
+
segments:
|
99
|
+
- 0
|
100
|
+
version: "0"
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
hash: 3
|
107
|
+
segments:
|
108
|
+
- 0
|
109
|
+
version: "0"
|
110
|
+
requirements: []
|
111
|
+
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 1.3.7
|
114
|
+
signing_key:
|
115
|
+
specification_version: 3
|
116
|
+
summary: Tool to archive lots of messages in an imap server
|
117
|
+
test_files:
|
118
|
+
- spec/archiver_spec.rb
|
119
|
+
- spec/cli_spec.rb
|
120
|
+
- spec/imap_archiver_spec.rb
|
121
|
+
- spec/integration_spec.rb
|
122
|
+
- spec/spec_helper.rb
|