]> git.cworth.org Git - sup/blob - bin/sup-sync-back
4f1387e7f1e62d9ec13c03833d0670c9e87e4531
[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 <<EOS
55 Nothing to do. Please specify at least one of --drop-deleted, --move-deleted,
56 --drop-spam, or --move-spam.
57 EOS
58
59   exit
60 end
61
62 Redwood::start
63 index = Redwood::Index.new
64 index.lock_or_die
65
66 deleted_fp, spam_fp = nil
67 unless opts[:dry_run]
68   deleted_fp = File.open(opts[:move_deleted], "a") if opts[:move_deleted] 
69   spam_fp = File.open(opts[:move_spam], "a") if opts[:move_spam]
70 end
71
72 dotlockfile = opts[:with_dotlockfile] || "/usr/bin/dotlockfile"
73
74 begin
75   index.load
76
77   sources = ARGV.map do |uri|
78     s = index.source_for(uri) or die "unknown source: #{uri}. Did you add it with sup-add first?"
79     s.is_a?(Redwood::MBox::Loader) or die "#{uri} is not an mbox source."
80     s
81   end
82
83   if sources.empty?
84     sources = index.usual_sources.select { |s| s.is_a? Redwood::MBox::Loader } 
85   end
86
87   unless sources.all? { |s| s.file_path.nil? } || File.executable?(dotlockfile) || opts[:dont_use_dotlockfile]
88     die <<EOS
89 can't execute dotlockfile binary: #{dotlockfile}. Specify --with-dotlockfile
90 if it's in a nonstandard location, or, if you want to live dangerously, try
91 --dont-use-dotlockfile
92 EOS
93   end
94
95   modified_sources = []
96   sources.each do |source|
97     $stderr.puts "Scanning #{source}..."
98
99     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))
100       $stderr.puts "Nothing to do from this source; skipping"
101       next
102     end
103
104     source.reset!
105     num_dropped = num_moved = num_scanned = 0
106     
107     out_fp = Tempfile.new "sup-sync-back-#{source.id}"
108     Redwood::PollManager.add_messages_from source do |m, offset, entry|
109       num_scanned += 1
110
111       if entry
112         labels = entry[:label].symbolistize.to_boolean_h
113
114         if labels.member? :deleted
115           if opts[:drop_deleted]
116             puts "Dropping deleted message #{source}##{offset}" if opts[:verbose]
117             num_dropped += 1
118           elsif opts[:move_deleted] && labels.member?(:deleted)
119             puts "Moving deleted message #{source}##{offset}" if opts[:verbose]
120             save m, deleted_fp unless opts[:dry_run]
121             num_moved += 1
122           end
123
124         elsif labels.member? :spam
125           if opts[:drop_spam]
126             puts "Dropping spam message #{source}##{offset}" if opts[:verbose]
127             num_dropped += 1
128           elsif opts[:move_spam] && labels.member?(:spam)
129             puts "Moving spam message #{source}##{offset}" if opts[:verbose]
130             save m, spam_fp unless opts[:dry_run]
131             num_moved += 1
132           end
133         else
134           save m, out_fp unless opts[:dry_run]
135         end
136       else
137         save m, out_fp unless opts[:dry_run]
138       end
139
140       nil # don't actually add anything!
141     end
142     $stderr.puts "Scanned #{num_scanned}, dropped #{num_dropped}, moved #{num_moved} messages from #{source}."
143     modified_sources << source if num_dropped > 0 || num_moved > 0
144     out_fp.close unless opts[:dry_run]
145
146     unless opts[:dry_run] || (num_dropped == 0 && num_moved == 0)
147       deleted_fp.flush if deleted_fp
148       spam_fp.flush if spam_fp
149       unless opts[:dont_use_dotlockfile]
150         puts "Locking #{source.file_path}..."
151         system "#{opts[:dotlockfile]} -l #{source.file_path}"
152         puts "Writing #{source.file_path}..."
153         FileUtils.cp out_fp.path, source.file_path
154         puts "Unlocking #{source.file_path}..."
155         system "#{opts[:dotlockfile]} -u #{source.file_path}"
156       end
157     end
158   end
159
160   unless opts[:dry_run]
161     deleted_fp.close if deleted_fp
162     spam_fp.close if spam_fp
163   end
164
165   $stderr.puts "Done."
166   unless modified_sources.empty?
167     $stderr.puts "You should now run: sup-sync --changed #{modified_sources.join(' ')}"
168   end
169 rescue Exception => e
170   File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }
171   raise
172 ensure
173   Redwood::finish
174   index.unlock
175 end