]> git.cworth.org Git - sup/blob - bin/sup-sync-back
add --search option to sup (thanks to Marcus Williams)
[sup] / bin / sup-sync-back
1 #!/usr/bin/env ruby
2
3 require 'rubygems'
4 require 'uri'
5 require 'tempfile'
6 require 'trollop'
7 require "sup"
8
9 ## save a message 'm' to an open file pointer 'fp'
10 def save m, fp
11   m.source.each_raw_message_line(m.source_info) { |l| fp.print l }
12 end
13 def die msg
14   $stderr.puts "Error: #{msg}"
15   exit(-1)
16 end
17
18 opts = Trollop::options do
19   version "sup-sync-back (sup #{Redwood::VERSION})"
20   banner <<EOS
21 Drop or move messages from Sup sources that are marked as deleted or
22 spam in the Sup index.
23
24 Currently only works with mbox sources.
25
26 Usage:
27   sup-sync-back [options] <source>*
28
29 where <source>* is zero or more source URIs. If no sources are given,
30 sync back all usual sources.
31
32 You almost certainly want to run sup-sync --changed after this command.
33 Running this does not change the index.
34
35 Options include:
36 EOS
37   opt :drop_deleted, "Drop deleted messages.", :default => false, :short => "d"
38   opt :move_deleted, "Move deleted messages to a local mbox file.", :type => String, :short => :none
39   opt :drop_spam, "Drop spam messages.", :default => false, :short => "s"
40   opt :move_spam, "Move spam messages to a local mbox file.", :type => String, :short => :none
41
42   opt :with_dotlockfile, "Specific dotlockfile location (mbox files only).", :default => "/usr/bin/dotlockfile", :short => :none
43   opt :dont_use_dotlockfile, "Don't use dotlockfile to lock mbox files. Dangerous if other processes modify them concurrently.", :default => false, :short => :none
44
45   opt :verbose, "Print message ids as they're processed."
46   opt :dry_run, "Don't actually modify the index. Probably only useful with --verbose.", :short => "-n"
47   opt :version, "Show version information", :short => :none
48
49   conflicts :drop_deleted, :move_deleted
50   conflicts :drop_spam, :move_spam
51 end
52
53 unless opts[:drop_deleted] || opts[:move_deleted] || opts[:drop_spam] || opts[:move_spam]
54   puts "Nothing to do."
55   exit
56 end
57
58 Redwood::start
59 index = Redwood::Index.new
60 index.lock_or_die
61
62 deleted_fp, spam_fp = nil
63 unless opts[:dry_run]
64   deleted_fp = File.open(opts[:move_deleted], "a") if opts[:move_deleted] 
65   spam_fp = File.open(opts[:move_spam], "a") if opts[:move_spam]
66 end
67
68 dotlockfile = opts[:with_dotlockfile] || "/usr/bin/dotlockfile"
69
70 begin
71   index.load
72
73   sources = ARGV.map do |uri|
74     s = index.source_for(uri) or die "unknown source: #{uri}. Did you add it with sup-add first?"
75     s.is_a?(Redwood::MBox::Loader) or die "#{uri} is not an mbox source."
76     s
77   end
78
79   if sources.empty?
80     sources = index.usual_sources.select { |s| s.is_a? Redwood::MBox::Loader } 
81   end
82
83   unless sources.all? { |s| s.file_path.nil? } || File.executable?(dotlockfile) || opts[:dont_use_dotlockfile]
84     die <<EOS
85 can't execute dotlockfile binary: #{dotlockfile}. Specify --with-dotlockfile
86 if it's in a nonstandard location, or, if you want to live dangerously, try
87 --dont-use-dotlockfile
88 EOS
89   end
90
91   modified_sources = []
92   sources.each do |source|
93     $stderr.puts "Scanning #{source}..."
94
95     unless ((opts[:drop_deleted] || opts[:move_deleted]) && index.has_any_from_source_with_label?(source, :deleted)) || ((opts[:drop_spam] || opts[:move_spam]) && index.has_any_from_source_with_label?(source, :spam))
96       $stderr.puts "Nothing to do from this source; skipping"
97       next
98     end
99
100     source.reset!
101     num_dropped = num_moved = num_scanned = 0
102     
103     out_fp = Tempfile.new "sup-sync-back-#{source.id}"
104     Redwood::PollManager.add_messages_from source do |m, offset, entry|
105       num_scanned += 1
106
107       if entry
108         labels = entry[:label].split.map { |x| x.intern }.to_boolean_h
109
110         if labels.member? :deleted
111           if opts[:drop_deleted]
112             puts "Dropping deleted message #{source}##{offset}" if opts[:verbose]
113             num_dropped += 1
114           elsif opts[:move_deleted] && labels.member?(:deleted)
115             puts "Moving deleted message #{source}##{offset}" if opts[:verbose]
116             save m, deleted_fp unless opts[:dry_run]
117             num_moved += 1
118           end
119
120         elsif labels.member? :spam
121           if opts[:drop_spam]
122             puts "Dropping spam message #{source}##{offset}" if opts[:verbose]
123             num_dropped += 1
124           elsif opts[:move_spam] && labels.member?(:spam)
125             puts "Moving spam message #{source}##{offset}" if opts[:verbose]
126             save m, spam_fp unless opts[:dry_run]
127             num_moved += 1
128           end
129         else
130           save m, out_fp unless opts[:dry_run]
131         end
132       else
133         save m, out_fp unless opts[:dry_run]
134       end
135
136       nil # don't actually add anything!
137     end
138     $stderr.puts "Scanned #{num_scanned}, dropped #{num_dropped}, moved #{num_moved} messages from #{source}."
139     modified_sources << source if num_dropped > 0 || num_moved > 0
140     out_fp.close unless opts[:dry_run]
141
142     unless opts[:dry_run] || (num_dropped == 0 && num_moved == 0)
143       deleted_fp.flush if deleted_fp
144       spam_fp.flush if spam_fp
145       unless opts[:dont_use_dotlockfile]
146         puts "Locking #{source.file_path}..."
147         system "#{opts[:dotlockfile]} -l #{source.file_path}"
148         puts "Writing #{source.file_path}..."
149         FileUtils.cp out_fp.path, source.file_path
150         puts "Unlocking #{source.file_path}..."
151         system "#{opts[:dotlockfile]} -u #{source.file_path}"
152       end
153     end
154   end
155
156   unless opts[:dry_run]
157     deleted_fp.close if deleted_fp
158     spam_fp.close if spam_fp
159   end
160
161   $stderr.puts "Done."
162   unless modified_sources.empty?
163     $stderr.puts "You should now run: sup-sync --changed #{modified_sources.join(' ')}"
164   end
165 rescue Exception => e
166   File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }
167   raise
168 ensure
169   Redwood::finish
170   index.unlock
171 end