Fix a fd leak in knotes backend (stupid me) and some cleanup in kaddrbook backend.
[beagle.git] / beagled / GaimLogQueryable / GaimLogQueryable.cs
blob82baa5d72ecce565047a0cf19192461f84f0b3fa
1 //
2 // GaimLogQueryable.cs
3 //
4 // Copyright (C) 2004 Novell, Inc.
5 //
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a
9 // copy of this software and associated documentation files (the "Software"),
10 // to deal in the Software without restriction, including without limitation
11 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 // and/or sell copies of the Software, and to permit persons to whom the
13 // Software is furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 // DEALINGS IN THE SOFTWARE.
27 using System;
28 using System.Collections;
29 using System.IO;
30 using System.Text;
31 using System.Threading;
33 using Beagle.Daemon;
34 using Beagle.Util;
36 namespace Beagle.Daemon.GaimLogQueryable {
38 // FIXME: This should be rather renamed to Gaim to be compliant with other backend names
39 [QueryableFlavor (Name="GaimLog", Domain=QueryDomain.Local, RequireInotify=false)]
40 public class GaimLogQueryable : LuceneFileQueryable, IIndexableGenerator {
42 private static Logger log = Logger.Get ("GaimLogQueryable");
44 private string config_dir, log_dir, icons_dir;
46 private int polling_interval_in_seconds = 60;
48 private GaimBuddyListReader list = new GaimBuddyListReader ();
50 public GaimLogQueryable () : base ("GaimLogIndex")
52 config_dir = Path.Combine (PathFinder.HomeDir, ".gaim");
53 log_dir = Path.Combine (config_dir, "logs");
54 icons_dir = Path.Combine (config_dir, "icons");
57 /////////////////////////////////////////////////
59 private void StartWorker()
61 if (! Directory.Exists (log_dir)) {
62 GLib.Timeout.Add (60000, new GLib.TimeoutHandler (CheckForExistence));
63 return;
66 log.Info ("Starting Gaim log backend");
68 Stopwatch stopwatch = new Stopwatch ();
69 stopwatch.Start ();
71 if (Inotify.Enabled) {
72 Log.Info ("Setting up inotify watches on gaim log directories");
73 Crawl (false);
76 Scheduler.Task task;
77 task = NewAddTask (this);
78 task.Tag = "Crawling Gaim logs";
79 task.Source = this;
81 if (!Inotify.Enabled) {
82 Scheduler.TaskGroup group = Scheduler.NewTaskGroup ("Repeating gaim log crawler", null, AddCrawlTask);
83 task.AddTaskGroup (group);
86 ThisScheduler.Add (task);
88 stopwatch.Stop ();
90 log.Info ("Gaim log backend worker thread done in {0}", stopwatch);
93 public override void Start ()
95 base.Start ();
97 ExceptionHandlingThread.Start (new ThreadStart (StartWorker));
100 /////////////////////////////////////////////////
102 private void AddCrawlTask ()
104 Scheduler.Task task = Scheduler.TaskFromHook (new Scheduler.TaskHook (CrawlHook));
105 task.Tag = "Crawling ~/.gaim/logs to find new logfiles";
106 task.Source = this;
107 ThisScheduler.Add (task);
110 private void CrawlHook (Scheduler.Task task)
112 Crawl (true);
113 task.Reschedule = true;
114 task.TriggerTime = DateTime.Now.AddSeconds (polling_interval_in_seconds);
117 private void Crawl (bool index)
119 if (Inotify.Enabled)
120 Inotify.Subscribe (log_dir, OnInotifyNewProtocol, Inotify.EventType.Create);
122 // Walk through protocol subdirs
123 foreach (string proto_dir in DirectoryWalker.GetDirectories (log_dir))
124 CrawlProtocolDirectory (proto_dir, index);
127 private void CrawlProtocolDirectory (string proto_dir, bool index)
129 if (Inotify.Enabled)
130 Inotify.Subscribe (proto_dir, OnInotifyNewAccount, Inotify.EventType.Create);
132 // Walk through accounts
133 foreach (string account_dir in DirectoryWalker.GetDirectories (proto_dir))
134 CrawlAccountDirectory (account_dir, index);
137 private void CrawlAccountDirectory (string account_dir, bool index)
139 if (Inotify.Enabled)
140 Inotify.Subscribe (account_dir, OnInotifyNewRemote, Inotify.EventType.Create);
142 // Walk through remote user conversations
143 foreach (string remote_dir in DirectoryWalker.GetDirectories (account_dir)) {
144 if (remote_dir.IndexOf (".system") < 0)
145 CrawlRemoteDirectory (remote_dir, index);
149 private void CrawlRemoteDirectory (string remote_dir, bool index)
151 if (Inotify.Enabled)
152 Inotify.Subscribe (remote_dir, OnInotifyNewConversation, Inotify.EventType.CloseWrite | Inotify.EventType.Modify);
154 if (index) {
155 foreach (FileInfo file in DirectoryWalker.GetFileInfos (remote_dir))
156 if (FileIsInteresting (file.Name))
157 IndexLog (file.FullName, Scheduler.Priority.Delayed);
161 /////////////////////////////////////////////////
163 public string StatusName {
164 get { return "GaimLogQueryable"; }
167 private IEnumerator log_files = null;
169 public void PostFlushHook () { }
171 public bool HasNextIndexable ()
173 if (log_files == null)
174 log_files = DirectoryWalker.GetFileInfosRecursive (log_dir).GetEnumerator ();
176 return log_files.MoveNext ();
179 public Indexable GetNextIndexable ()
181 FileInfo file = (FileInfo) log_files.Current;
183 if (! file.Exists)
184 return null;
186 if (IsUpToDate (file.FullName))
187 return null;
189 Indexable indexable = ImLogToIndexable (file.FullName);
191 return indexable;
194 /////////////////////////////////////////////////
196 private bool CheckForExistence ()
198 if (!Directory.Exists (log_dir))
199 return true;
201 this.Start ();
203 return false;
206 private bool FileIsInteresting (string filename)
208 if (filename.Length < 21)
209 return false;
211 string ext = Path.GetExtension (filename);
212 if (ext != ".txt" && ext != ".html")
213 return false;
215 // Pre-gaim 2.0.0 logs are in the format "2005-07-22.161521.txt". Afterward a
216 // timezone field as added, ie. "2005-07-22.161521-0500EST.txt".
218 // This is a lot uglier than a regexp, but they are so damn expensive.
220 return Char.IsDigit (filename [0]) && Char.IsDigit (filename [1])
221 && Char.IsDigit (filename [2]) && Char.IsDigit (filename [3])
222 && filename [4] == '-'
223 && Char.IsDigit (filename [5]) && Char.IsDigit (filename [6])
224 && filename [7] == '-'
225 && Char.IsDigit (filename [8]) && Char.IsDigit (filename [9])
226 && filename [10] == '.'
227 && Char.IsDigit (filename [11]) && Char.IsDigit (filename [12])
228 && Char.IsDigit (filename [13]) && Char.IsDigit (filename [14])
229 && Char.IsDigit (filename [15]) && Char.IsDigit (filename [16])
230 && (filename [17] == '+' || filename [17] == '-' || filename [17] == '.');
233 /////////////////////////////////////////////////
235 private void OnInotifyNewProtocol (Inotify.Watch watch,
236 string path, string subitem, string srcpath,
237 Inotify.EventType type)
239 if (subitem.Length == 0 || (type & Inotify.EventType.IsDirectory) == 0)
240 return;
242 CrawlProtocolDirectory (Path.Combine (path, subitem), true);
245 private void OnInotifyNewAccount (Inotify.Watch watch,
246 string path, string subitem, string srcpath,
247 Inotify.EventType type)
249 if (subitem.Length == 0 || (type & Inotify.EventType.IsDirectory) == 0)
250 return;
252 CrawlAccountDirectory (Path.Combine (path, subitem), true);
255 private void OnInotifyNewRemote (Inotify.Watch watch,
256 string path, string subitem, string srcpath,
257 Inotify.EventType type)
259 if (subitem.Length == 0 || (type & Inotify.EventType.IsDirectory) == 0)
260 return;
262 CrawlRemoteDirectory (Path.Combine (path, subitem), true);
265 private void OnInotifyNewConversation (Inotify.Watch watch,
266 string path, string subitem, string srcpath,
267 Inotify.EventType type)
269 if (subitem.Length == 0 || (type & Inotify.EventType.IsDirectory) != 0)
270 return;
272 if (FileIsInteresting (subitem))
273 IndexLog (Path.Combine (path, subitem), Scheduler.Priority.Immediate);
276 /////////////////////////////////////////////////
278 private static Indexable ImLogToIndexable (string filename)
280 Uri uri = UriFu.PathToFileUri (filename);
281 Indexable indexable = new Indexable (uri);
282 indexable.ContentUri = uri;
283 indexable.Timestamp = File.GetLastWriteTimeUtc (filename);
284 indexable.MimeType = GaimLog.MimeType;
285 indexable.HitType = "IMLog";
286 indexable.CacheContent = false;
288 return indexable;
291 private void IndexLog (string filename, Scheduler.Priority priority)
293 if (! File.Exists (filename))
294 return;
296 if (IsUpToDate (filename))
297 return;
299 Indexable indexable = ImLogToIndexable (filename);
300 Scheduler.Task task = NewAddTask (indexable);
301 task.Priority = priority;
302 task.SubPriority = 0;
303 ThisScheduler.Add (task);
306 override protected double RelevancyMultiplier (Hit hit)
308 return HalfLifeMultiplierFromProperty (hit, 0.25, "fixme:endtime", "fixme:starttime");
311 override protected bool HitFilter (Hit hit)
313 // If the protocol isn't set (because maybe we got an
314 // exception while we were indexing), this isn't a
315 // valid hit.
316 if (hit ["fixme:protocol"] == null) {
317 Log.Warn ("Discarding IM log hit with missing protocol info: {0}", hit.Uri);
318 return false;
321 string speakingto = hit ["fixme:speakingto"];
323 // We have no idea who we're speaking to. Bad, but we
324 // still want to present it.
325 if (speakingto == null || speakingto == String.Empty)
326 return true;
328 ImBuddy buddy = list.Search (speakingto);
330 // We might still want to see a chat even if someone's
331 // not on our buddy list.
332 if (buddy == null)
333 return true;
335 if (buddy.Alias != "")
336 hit.AddProperty (Beagle.Property.NewKeyword ("fixme:speakingto_alias", buddy.Alias));
338 if (buddy.BuddyIconLocation != "")
339 hit.AddProperty (Beagle.Property.NewUnsearched ("fixme:speakingto_icon", Path.Combine (icons_dir, buddy.BuddyIconLocation)));
341 return true;
344 override public string GetSnippet (string [] query_terms, Hit hit)
346 TextReader reader;
347 reader = TextCache.UserCache.GetReader (hit.Uri);
348 if (reader == null)
349 return null;
350 HtmlRemovingReader html_removing_reader = new HtmlRemovingReader (reader);
351 string snippet = SnippetFu.GetSnippet (query_terms, html_removing_reader);
352 html_removing_reader.Close ();
354 return snippet;