imap_archiver 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/.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
|