4 // Copyright (C) 2004 Novell, Inc.
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.
28 using System
.Collections
;
31 using System
.Threading
;
36 namespace Beagle
.Daemon
.GaimLogQueryable
{
38 [QueryableFlavor (Name
="IMLog", Domain
=QueryDomain
.Local
, RequireInotify
=false)]
39 public class GaimLogQueryable
: LuceneFileQueryable
{
41 private static Logger log
= Logger
.Get ("GaimLogQueryable");
43 private string config_dir
, log_dir
, icons_dir
;
45 private int polling_interval_in_seconds
= 60;
47 private GaimLogCrawler crawler
;
49 private GaimBuddyListReader list
= new GaimBuddyListReader ();
51 public GaimLogQueryable () : base ("GaimLogIndex")
53 config_dir
= Path
.Combine (PathFinder
.HomeDir
, ".gaim");
54 log_dir
= Path
.Combine (config_dir
, "logs");
55 icons_dir
= Path
.Combine (config_dir
, "icons");
58 /////////////////////////////////////////////////
60 private void StartWorker()
62 if (! Directory
.Exists (log_dir
)) {
63 GLib
.Timeout
.Add (60000, new GLib
.TimeoutHandler (CheckForExistence
));
67 log
.Info ("Starting Gaim log backend");
69 Stopwatch stopwatch
= new Stopwatch ();
75 crawler
= new GaimLogCrawler (log_dir
);
78 if (!Inotify
.Enabled
) {
79 Scheduler
.Task task
= Scheduler
.TaskFromHook (new Scheduler
.TaskHook (CrawlHook
));
80 task
.Tag
= "Crawling ~/.gaim/logs to find new logfiles";
81 ThisScheduler
.Add (task
);
86 log
.Info ("Gaim log backend worker thread done in {0}", stopwatch
);
89 public override void Start ()
93 ExceptionHandlingThread
.Start (new ThreadStart (StartWorker
));
96 /////////////////////////////////////////////////
101 foreach (FileInfo file
in crawler
.Logs
) {
102 IndexLog (file
.FullName
, Scheduler
.Priority
.Delayed
);
106 private void CrawlHook (Scheduler
.Task task
)
109 task
.Reschedule
= true;
110 task
.TriggerTime
= DateTime
.Now
.AddSeconds (polling_interval_in_seconds
);
113 /////////////////////////////////////////////////
115 // Sets up an Inotify watch on all subdirectories withing ~/.gaim/logs
116 private void Watch (string path
)
118 DirectoryInfo root
= new DirectoryInfo (path
);
121 log
.Warn ("IM: {0} cannot watch path. It doesn't exist.", path
);
125 Queue queue
= new Queue ();
126 queue
.Enqueue (root
);
128 while (queue
.Count
> 0) {
129 DirectoryInfo dir
= queue
.Dequeue () as DirectoryInfo
;
131 // Setup watches on the present directory.
132 Inotify
.Subscribe (dir
.FullName
, OnInotifyEvent
,
133 Inotify
.EventType
.Create
| Inotify
.EventType
.Modify
);
135 // Add all subdirectories to the queue so their files can be indexed.
136 foreach (DirectoryInfo subdir
in dir
.GetDirectories ())
137 queue
.Enqueue (subdir
);
141 /////////////////////////////////////////////////
143 private bool CheckForExistence ()
145 if (!Directory
.Exists (log_dir
))
153 /////////////////////////////////////////////////
155 private void OnInotifyEvent (Inotify
.Watch watch
,
159 Inotify
.EventType type
)
164 string full_path
= Path
.Combine (path
, subitem
);
166 if ((type
& Inotify
.EventType
.Create
) != 0 && (type
& Inotify
.EventType
.IsDirectory
) != 0) {
171 if ((type
& Inotify
.EventType
.Modify
) != 0) {
172 IndexLog (full_path
, Scheduler
.Priority
.Immediate
);
177 /////////////////////////////////////////////////
179 private static Indexable
ImLogToIndexable (ImLog log
)
181 Indexable indexable
= new Indexable (log
.Uri
);
182 indexable
.Timestamp
= log
.Timestamp
;
183 indexable
.MimeType
= "text/plain";
184 indexable
.Type
= "IMLog";
186 indexable
.Filtering
= IndexableFiltering
.AlreadyFiltered
;
188 StringBuilder text
= new StringBuilder ();
189 foreach (ImLog
.Utterance utt
in log
.Utterances
) {
190 //Console.WriteLine ("[{0}][{1}]", utt.Who, utt.Text);
191 text
.Append (utt
.Text
);
195 // FIXME: It would be cleaner to have a TextReader than streamed out
198 indexable
.AddProperty (Property
.NewDate ("fixme:starttime", log
.StartTime
));
199 indexable
.AddProperty (Property
.NewDate ("fixme:endtime", log
.EndTime
));
200 indexable
.AddProperty (Property
.NewUnsearched ("fixme:file", log
.LogFile
));
201 indexable
.AddProperty (Property
.NewUnsearched ("fixme:offset", log
.LogOffset
));
202 indexable
.AddProperty (Property
.NewUnsearched ("fixme:client", log
.Client
));
203 indexable
.AddProperty (Property
.NewUnsearched ("fixme:protocol", log
.Protocol
));
205 // FIXME: Should these use Property.NewKeyword and be searched?
206 indexable
.AddProperty (Property
.NewUnsearched ("fixme:speakingto", log
.SpeakingTo
));
207 indexable
.AddProperty (Property
.NewUnsearched ("fixme:identity", log
.Identity
));
209 StringReader reader
= new StringReader (text
.ToString ());
210 indexable
.SetTextReader (reader
);
215 private void IndexLog (string filename
, Scheduler
.Priority priority
)
217 FileInfo info
= new FileInfo (filename
);
221 if (IsUpToDate (filename
))
224 ICollection logs
= GaimLog
.ScanLog (info
);
225 foreach (ImLog log
in logs
) {
226 Indexable indexable
= ImLogToIndexable (log
);
227 Scheduler
.Task task
= NewAddTask (indexable
);
228 task
.Priority
= priority
;
229 task
.SubPriority
= 0;
230 ThisScheduler
.Add (task
);
234 override protected double RelevancyMultiplier (Hit hit
)
236 return HalfLifeMultiplierFromProperty (hit
, 0.25,
237 "fixme:endtime", "fixme:starttime");
240 override public string GetSnippet (string[] query_terms
, Hit hit
)
242 // FIXME: This does the wrong thing for old-style logs.
243 string file
= hit
["fixme:file"];
244 ICollection logs
= GaimLog
.ScanLog (new FileInfo (file
));
245 IEnumerator iter
= logs
.GetEnumerator ();
247 if (iter
.MoveNext ())
248 log
= iter
.Current
as ImLog
;
254 // FIXME: This is very lame, and doesn't do the
255 // right thing w/ stemming, word boundaries, etc.
256 foreach (ImLog
.Utterance utt
in log
.Utterances
) {
257 string text
= utt
.Text
;
258 string who
= utt
.Who
;
260 string snippet
= SnippetFu
.GetSnippet (query_terms
, new StringReader (text
));
262 if (snippet
== null || snippet
== "")
265 result
+= String
.Format ("{0}: {1} ", who
, snippet
);
267 if (result
.Length
> 300)
277 override protected bool HitFilter (Hit hit
)
279 ImBuddy buddy
= list
.Search (hit
["fixme:speakingto"]);
282 if (buddy
.Alias
!= "")
283 hit
["fixme:speakingto_alias"] = buddy
.Alias
;
285 if (buddy
.BuddyIconLocation
!= "")
286 hit
["fixme:speakingto_icon"] = Path
.Combine (icons_dir
, buddy
.BuddyIconLocation
);
292 override protected Hit
PostProcessHit (Hit hit
)
294 ImBuddy buddy
= list
.Search (hit
["fixme:speakingto"]);
297 if (buddy
.Alias
!= "")
298 hit
["fixme:speakingto_alias"] = buddy
.Alias
;
300 if (buddy
.BuddyIconLocation
!= "")
301 hit
["fixme:speakingto_icon"] = Path
.Combine (icons_dir
, buddy
.BuddyIconLocation
);