imap_archiver 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+ require 'imap_archiver'
4
+ exit ImapArchiver::CLI.execute(STDOUT,STDIN, ARGV)
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,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "ImapArchiver" do
4
+ it "should read the correct version" do
5
+ ImapArchiver::Version.should == "0.0.1"
6
+ end
7
+ 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
@@ -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