Various Instant messaging log parsing improvements: - Use a filter to parse the logs (move work into the helper process) - Store times in ImLog as numbers - Some cleanups - Remove support for old gaim logs ImLogViewer changes: - Uses --client argument to specify gaim or kopete (rather than guessing based on path) - Changing log always scrolls to the top - Searching for text always ensures the first match is on screen - Clearing the search does not reset you to the first log - Clearing the search leaves you in exactly the same position in the current log - Show the beagle icon - Other minor cleanups Index: Filters/Makefile.am =================================================================== RCS file: /cvs/gnome/beagle/Filters/Makefile.am,v retrieving revision 1.48 diff -u -B -p -r1.48 Makefile.am --- Filters/Makefile.am 15 Sep 2005 16:59:23 -0000 1.48 +++ Filters/Makefile.am 24 Oct 2005 18:08:46 -0000 @@ -53,7 +53,8 @@ CSFILES = \ $(srcdir)/FilterDesktop.cs \ $(srcdir)/FilterDirectory.cs \ $(srcdir)/FilterMail.cs \ - $(srcdir)/FilterMusic.cs + $(srcdir)/FilterMusic.cs \ + $(srcdir)/FilterImLog.cs if ENABLE_GSF_SHARP CSFILES += \ Index: ImLogViewer/ImLogViewer.cs =================================================================== RCS file: /cvs/gnome/beagle/ImLogViewer/ImLogViewer.cs,v retrieving revision 1.26 diff -u -B -p -r1.26 ImLogViewer.cs --- ImLogViewer/ImLogViewer.cs 24 May 2005 14:07:37 -0000 1.26 +++ ImLogViewer/ImLogViewer.cs 24 Oct 2005 18:08:47 -0000 @@ -6,9 +6,28 @@ // // Copyright (C) 2005 Novell, Inc. // +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// using System; using Mono.Posix; +using Beagle.Util; namespace ImLogViewer { @@ -17,6 +36,7 @@ namespace ImLogViewer { private static string highlight; private static string search; private static string path; + private static string client; public static void Main (string[] args) { @@ -24,13 +44,24 @@ namespace ImLogViewer { Catalog.Init ("beagle", Beagle.Util.ExternalStringsHack.LocaleDir); ParseArgs (args); - ImLogWindow window = new ImLogWindow (path, search, highlight); + + ImClient imclient; + try { + imclient = (ImClient) Enum.Parse (typeof (ImClient), client, true); + } catch (Exception) { + Console.WriteLine ("ERROR: '{0}' is not a valid client name.", client); + Environment.Exit (3); + return; + } + + new ImLogWindow (imclient, path, search, highlight); } private static void PrintUsageAndExit () { - Console.WriteLine ("USAGE: beagle-imlogviewer [OPTIONS] \n" + + Console.WriteLine ("USAGE: beagle-imlogviewer --client [OPTIONS] \n" + "Options:\n" + + " --client\t\t\tClient that the log belongs to (e.g. gaim).\n" + " --highlight-search\t\tWords to highlight in the buffer.\n" + " --search\t\t\tSearch query to filter hits on."); @@ -59,6 +90,11 @@ namespace ImLogViewer { i++; break; + case "--client": + client = args [i + 1]; + i++; + break; + default: if (args [i].StartsWith ("--")) { Console.WriteLine ("WARN: Invalid option {0}", args [i]); @@ -72,6 +108,11 @@ namespace ImLogViewer { if (path == null) { Console.WriteLine ("ERROR: Please specify a valid log path or log directory."); Environment.Exit (1); + } + + if (client == null) { + Console.WriteLine ("ERROR: Please specify a valid client name."); + Environment.Exit (2); } } } Index: ImLogViewer/ImLogWindow.cs =================================================================== RCS file: /cvs/gnome/beagle/ImLogViewer/ImLogWindow.cs,v retrieving revision 1.6 diff -u -B -p -r1.6 ImLogWindow.cs --- ImLogViewer/ImLogWindow.cs 19 Aug 2005 15:04:05 -0000 1.6 +++ ImLogViewer/ImLogWindow.cs 24 Oct 2005 18:08:49 -0000 @@ -6,6 +6,24 @@ // // Copyright (C) 2005 Novell, Inc. // +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// using System; using System.Collections; @@ -26,9 +44,8 @@ namespace ImLogViewer { [Widget] Button search_button; [Widget] Button clear_button; [Widget] TextView conversation; + [Widget] ScrolledWindow scrolledwindow; - private string selected_log; - private string speaking_to; private string log_path; private string highlight_text; @@ -38,31 +55,29 @@ namespace ImLogViewer { private ThreadNotify index_thread_notify; private Timeline timeline = new Timeline (); - private string client; + private FileInfo initial_select_file; + private ImLog initial_select; + + private ImClient client; - public ImLogWindow (string path, string search, string highlight) + public ImLogWindow (ImClient client, string path, string search, string highlight) { - // FIXME: This is a bunch of crap, ImLog should be more dynamic. - if (path.IndexOf (".gaim/logs") != -1) - client = "gaim"; - else if (path.IndexOf ("apps/kopete/logs") != -1) - client = "kopete"; + this.client = client; if (Directory.Exists (path)) { log_path = path; } else if (File.Exists (path)) { log_path = Path.GetDirectoryName (path); - selected_log = path; + initial_select_file = new FileInfo (path); } else { Console.WriteLine ("ERROR: Log path doesn't exist - {0}", path); return; } highlight_text = highlight; - search_text = search; - - ShowWindow (); + search_text = search; + ShowWindow (); } private void SetStatusTitle (DateTime dt) @@ -72,23 +87,23 @@ namespace ImLogViewer { private void SetWindowTitle (string speaker) { - if (speaker == null || speaker == "") + if (speaker == null || speaker.Length == 0) return; // Find the buddy ImBuddy buddy = null; - if (client == "gaim") + if (client == ImClient.Gaim) buddy = new GaimBuddyListReader ().Search (speaker); - else if (client == "kopete") + else if (client == ImClient.Kopete) buddy = new KopeteBuddyListReader ().Search (speaker); if (speaker.EndsWith (".chat")) { - imviewer.Title = String.Format (Catalog.GetString ("Conversations in {0}"), speaker.Replace (".chat", "")); + imviewer.Title = String.Format (Catalog.GetString ("Conversations in {0}"), speaker.Replace (".chat", String.Empty)); } else { string nick = speaker; - if (buddy != null && buddy.Alias != "") + if (buddy != null && buddy.Alias.Length > 0) nick = buddy.Alias; imviewer.Title = String.Format (Catalog.GetString ("Conversations with {0}"), nick); @@ -99,10 +114,11 @@ namespace ImLogViewer { private void ShowWindow () { - Application.Init(); + Application.Init (); Glade.XML gxml = new Glade.XML (null, "ImLogViewer.glade", "imviewer", null); gxml.Autoconnect (this); + imviewer.Icon = Beagle.Images.GetPixbuf ("best.png"); conversation.PixelsAboveLines = 3; conversation.LeftMargin = 4; @@ -150,22 +166,21 @@ namespace ImLogViewer { private void IndexLogs () { foreach (string file in Directory.GetFiles (log_path)) { - ICollection logs = null; + ImLog log = null; - if (client == "gaim") - logs = GaimLog.ScanLog (new FileInfo (file)); - else if (client == "kopete") - logs = KopeteLog.ScanLog (new FileInfo (file)); + if (client == ImClient.Gaim) + log = new GaimLog (new FileInfo (file), new StreamReader (file)); + else if (client == ImClient.Kopete) + log = new KopeteLog (new FileInfo (file), new StreamReader (file)); + + if (initial_select_file != null && log.File.FullName == initial_select_file.FullName) { + initial_select = log; + initial_select_file = null; + } - if (logs == null) - continue; - - foreach (ImLog log in logs) { - if (speaking_to == null) - SetWindowTitle (log.SpeakingTo); - + if (speaking_to == null) + SetWindowTitle (log.SpeakingTo); timeline.Add (log, log.StartTime); - } } index_thread_notify.WakeupMain (); @@ -192,33 +207,106 @@ namespace ImLogViewer { return true; } - private class ImLogPreview + private string GetPreview (ImLog log) { - public string Snippet; - public ImLog Log; + string preview = null; + + if (log.Utterances.Count == 0) + return String.Empty; - public ImLogPreview (ImLog log) - { - Snippet = log.EllipsizedSnippet; - Log = log; + foreach (ImLog.Utterance utt in log.Utterances) { + string snippet = utt.Text; + int word_count = StringFu.CountWords (snippet, 15); + if (word_count > 3) { + preview = snippet; + break; + } } + + if (preview == null) + return ((ImLog.Utterance) log.Utterances [0]).Text; + + if (preview.Length > 50) + return preview.Substring (0, 50) + "..."; + return preview; } private void AddCategory (ArrayList list, string name, string date_format) { - if (list.Count > 0) { - ArrayList previews = GetPreviews (list); - if (previews.Count > 0) { - TreeIter parent = tree_store.AppendValues (String.Format ("{0}", Catalog.GetString (name)), "", null); - AddPreviews (parent, previews, Catalog.GetString (date_format)); + if (list.Count == 0) + return; + + ImLog selected_log = GetSelectedLog (); + + TreeIter parent = TreeIter.Zero; + + foreach (ImLog log in list) { + if (search_text != null && search_text.Length > 0) + if (! LogContainsString (log, search_text)) + continue; + + if (parent.Equals(TreeIter.Zero)) + parent = tree_store.AppendValues (String.Format ("{0}", Catalog.GetString (name)), String.Empty, null); + + string date = log.StartTime.ToString (Catalog.GetString (date_format)); + tree_store.AppendValues (parent, date, GetPreview (log), log); + } + } + + private void SearchTimeline () + { + // Remove all timeline entries that don't match the search results + + ImLog selected = GetSelectedLog (); + + TreeIter iter; + if (!tree_store.GetIterFirst (out iter)) + return; + + ArrayList to_remove = new ArrayList (); + + do { + if (tree_store.IterHasChild (iter)) { + TreeIter child; + tree_store.IterNthChild (out child, iter, 0); + + do { + ImLog log = tree_store.GetValue (child, 2) as ImLog; + if (LogContainsString (log, search_text)) + continue; + + to_remove.Add (tree_store.GetPath (child)); + if (log == selected) + selected = null; + } while (tree_store.IterNext (ref child)); } + } while (tree_store.IterNext (ref iter)); + + for (int i = to_remove.Count - 1; i >= 0; i--) { + if (!tree_store.GetIter (out iter, to_remove [i] as TreePath)) + break; + tree_store.Remove (ref iter); } + + ScrollToLog (selected); + RenderConversation (selected); } private void RepopulateTimeline () { - tree_store.Clear (); + RepopulateTimeline (true, 0); + } + + private void RepopulateTimeline (bool reset, double vadj) + { + ImLog log; + + if (!reset) + log = GetSelectedLog (); + else + log = initial_select; + tree_store.Clear (); AddCategory (timeline.Today, "Today", "HH:mm"); AddCategory (timeline.Yesterday, "Yesterday", "HH:mm"); AddCategory (timeline.ThisWeek, "This Week", "dddd"); @@ -228,56 +316,37 @@ namespace ImLogViewer { AddCategory (timeline.Older, "Older", "yyy MMM d"); timelinetree.ExpandAll(); - } - - private void AddPreviews (TreeIter parent, ArrayList previews, string date_format) - { - foreach (ImLogPreview preview in previews) { - string date = preview.Log.StartTime.ToString (date_format); - tree_store.AppendValues (parent, date, preview.Snippet, preview.Log); - - if (selected_log == null || selected_log == preview.Log.LogFile) { - selected_log = preview.Log.LogFile; - RenderConversation (preview.Log); - ScrollToLog (preview.Log.LogFile); - } - } - } - - private ArrayList GetPreviews (ArrayList list) - { - ArrayList logs = new ArrayList (); - - foreach (ImLog log in list) { - if (search_text != null && search_text != "") - if (! LogContainsString (log, search_text)) - continue; + ScrollToLog (log); + RenderConversation (log); - ImLogPreview preview = new ImLogPreview (log); - logs.Add (preview); - } - - return logs; + if (!reset) + SetConversationScroll (vadj); } private void RenderConversation (ImLog im_log) { TextBuffer buffer = conversation.Buffer; - TextIter start = buffer.StartIter; - TextIter end = buffer.EndIter; + buffer.Clear (); - buffer.Delete (ref start, ref end); - if (im_log == null) { - //SetStatusTitle (new DateTime ()); - return; - } + // Find the first (newest) conversation to render + TreeIter first_parent; + if (!tree_store.GetIterFirst (out first_parent)) + return; + + TreeIter child; + if (!tree_store.IterChildren (out child, first_parent)) + return; + + im_log = tree_store.GetValue (child, 2) as ImLog; + } SetStatusTitle (im_log.StartTime); TextTag bold = buffer.TagTable.Lookup ("bold"); - end = buffer.EndIter; + TextIter start = buffer.StartIter; + TextIter end = buffer.EndIter; foreach (ImLog.Utterance utt in im_log.Utterances) { buffer.InsertWithTags (ref end, utt.Who + ":", new TextTag[] {bold}); @@ -287,7 +356,7 @@ namespace ImLogViewer { if (highlight_text != null) HighlightSearchTerms (highlight_text); - if (search_text != null && search_text != "") + if (search_text != null && search_text.Length > 0) HighlightSearchTerms (search_text); } @@ -296,6 +365,7 @@ namespace ImLogViewer { TextBuffer buffer = conversation.Buffer; string text = buffer.GetText (buffer.StartIter, buffer.EndIter, false).ToLower (); string [] words = highlight.Split (' '); + bool scrolled = false; foreach (string word in words) { int idx = 0; @@ -307,7 +377,11 @@ namespace ImLogViewer { Gtk.TextIter start = buffer.GetIterAtOffset (idx); Gtk.TextIter end = start; end.ForwardChars (word.Length); - + if (!scrolled) { + scrolled = true; + TextMark mark = buffer.CreateMark (null, start, false); + conversation.ScrollMarkOnscreen (mark); + } buffer.ApplyTag ("highlight", start, end); idx += word.Length; @@ -324,7 +399,6 @@ namespace ImLogViewer { search_text = text; highlight_text = null; - selected_log = null; } private void OnConversationSelected (object o, EventArgs args) @@ -338,8 +412,8 @@ namespace ImLogViewer { if (log == null) return; - selected_log = log.LogFile; RenderConversation (log); + SetConversationScroll (0); } } @@ -355,38 +429,73 @@ namespace ImLogViewer { private void OnSearchClicked (object o, EventArgs args) { - if (search_entry.Text == null || search_entry.Text == "") + if (search_entry.Text.Length == 0) return; Search (search_entry.Text); - RepopulateTimeline (); + SearchTimeline (); + } + + private void SetConversationScroll (double vadj) + { + scrolledwindow.Vadjustment.Value = vadj; + scrolledwindow.Vadjustment.ChangeValue (); + scrolledwindow.Vadjustment.Change (); } - private void ScrollToLog (string scroll_log) + private void ScrollToFirstLog () { + SelectPath (new TreePath (new int [] {0, 0})); + } + + private void ScrollToLog (ImLog scroll_log) + { + if (scroll_log == null) { + ScrollToFirstLog (); + return; + } + TreeIter root_iter; - tree_store.GetIterFirst (out root_iter); + if (!tree_store.GetIterFirst (out root_iter)) + return; do { - if (tree_store.IterHasChild (root_iter)) { - TreeIter child; - tree_store.IterNthChild (out child, root_iter, 0); + if (! tree_store.IterHasChild (root_iter)) + continue; + + TreeIter child; + tree_store.IterNthChild (out child, root_iter, 0); - do { - ImLog log = tree_store.GetValue (child, 2) as ImLog; + do { + ImLog log = tree_store.GetValue (child, 2) as ImLog; - if (log.LogFile == scroll_log) { - TreePath path = tree_store.GetPath (child); - timelinetree.ExpandToPath (path); - timelinetree.Selection.SelectPath (path); - timelinetree.ScrollToCell (path, null, true, 0.5f, 0.0f); - return; - } - } while (tree_store.IterNext (ref child)); - } + if (log == scroll_log) { + SelectPath (tree_store.GetPath (child)); + return; + } + } while (tree_store.IterNext (ref child)); } while (tree_store.IterNext (ref root_iter)); } - + + private void SelectPath (TreePath path) + { + timelinetree.Selection.Changed -= OnConversationSelected; + timelinetree.ExpandToPath (path); + timelinetree.Selection.SelectPath (path); + timelinetree.ScrollToCell (path, null, true, 0.5f, 0.0f); + timelinetree.Selection.Changed += OnConversationSelected; + } + + private ImLog GetSelectedLog () + { + TreeSelection selection = timelinetree.Selection; + TreeIter iter; + + if (selection.GetSelected (out iter)) + return (ImLog) tree_store.GetValue (iter, 2); + return null; + } + private void OnClearClicked (object o, EventArgs args) { highlight_text = search_text = null; @@ -394,9 +503,8 @@ namespace ImLogViewer { clear_button.Visible = false; search_entry.Sensitive = true; - RepopulateTimeline (); - - ScrollToLog (selected_log); + RepopulateTimeline (false, scrolledwindow.Vadjustment.Value); + ScrollToLog (GetSelectedLog ()); } } } Index: ImLogViewer/Makefile.am =================================================================== RCS file: /cvs/gnome/beagle/ImLogViewer/Makefile.am,v retrieving revision 1.8 diff -u -B -p -r1.8 Makefile.am --- ImLogViewer/Makefile.am 11 Aug 2005 11:28:12 -0000 1.8 +++ ImLogViewer/Makefile.am 24 Oct 2005 18:08:49 -0000 @@ -11,7 +11,8 @@ CSFILES = \ $(srcdir)/ImLogWindow.cs LOCAL_ASSEMBLIES = \ - ../Util/Util.dll + ../Util/Util.dll \ + ../images/Images.dll ASSEMBLIES = \ $(BEAGLE_UI_LIBS) \ Index: Tiles/TileImLog.cs =================================================================== RCS file: /cvs/gnome/beagle/Tiles/TileImLog.cs,v retrieving revision 1.41 diff -u -B -p -r1.41 TileImLog.cs --- Tiles/TileImLog.cs 21 Jul 2005 20:47:46 -0000 1.41 +++ Tiles/TileImLog.cs 24 Oct 2005 18:08:51 -0000 @@ -154,8 +154,8 @@ namespace Beagle.Tile { Process p = new Process (); p.StartInfo.UseShellExecute = true; p.StartInfo.FileName = "beagle-imlogviewer"; - p.StartInfo.Arguments = String.Format ("--highlight-search \"{0}\" {1}", - Query.QuotedText, Hit ["fixme:file"]); + p.StartInfo.Arguments = String.Format ("--client \"{0}\" --highlight-search \"{1}\" {2}", + Hit ["fixme:client"], Query.QuotedText, Hit.Uri.LocalPath); try { p.Start (); Index: Util/ImBuddy.cs =================================================================== RCS file: /cvs/gnome/beagle/Util/ImBuddy.cs,v retrieving revision 1.8 diff -u -B -p -r1.8 ImBuddy.cs --- Util/ImBuddy.cs 26 Jul 2005 19:16:12 -0000 1.8 +++ Util/ImBuddy.cs 24 Oct 2005 18:09:01 -0000 @@ -65,37 +65,6 @@ namespace Beagle.Util { Logger.Log.Debug ("{0}", str); } - public class ImBuddyComparer : IComparer { - - int IComparer.Compare (Object x, Object y) { - string accountx, accounty; - - try { - accountx = ((ImBuddy) x).BuddyAccountName; - } catch { - accountx = ""; - } - - try { - accounty = ((ImBuddy) y).BuddyAccountName; - } catch { - accounty = ""; - } - - return System.String.Compare (accountx, accounty); - } - } - - public class ImBuddyAliasComparer : IComparer { - - int IComparer.Compare (Object x, Object y) { - try { - return System.String.Compare ((string)x, ((ImBuddy) y).BuddyAccountName); - } catch { - return System.String.Compare (((ImBuddy) x).BuddyAccountName, (string)y); - } - } - } } public class GaimBuddyListReader : ImBuddyListReader { Index: Util/ImLog.cs =================================================================== RCS file: /cvs/gnome/beagle/Util/ImLog.cs,v retrieving revision 1.20 diff -u -B -p -r1.20 ImLog.cs --- Util/ImLog.cs 10 Oct 2005 15:17:29 -0000 1.20 +++ Util/ImLog.cs 24 Oct 2005 18:09:02 -0000 @@ -31,25 +31,26 @@ using System.Globalization; using System.Text; using System.Text.RegularExpressions; using System.Xml; +using Mono.Unix; namespace Beagle.Util { + public enum ImClient { + Gaim, + Kopete, + } + public abstract class ImLog { public delegate void Sink (ImLog imLog); - private bool loaded = false; - - public string LogFile; - public long LogOffset; - - public string Protocol; public string Client; + public FileInfo File; + public TextReader TextReader; + public string Protocol; public DateTime StartTime; public DateTime EndTime; - public DateTime Timestamp; - public string Snippet; public string SpeakingTo; public string Identity; @@ -57,7 +58,13 @@ namespace Beagle.Util { private Hashtable speakerHash = new Hashtable (); public class Utterance { - public DateTime Timestamp; + private long timestamp; + + public DateTime Timestamp { + get { return UnixConvert.ToDateTime (timestamp); } + set { timestamp = UnixConvert.FromDateTime (value); } + } + public String Who; public String Text; } @@ -65,29 +72,11 @@ namespace Beagle.Util { ////////////////////////// - protected ImLog (string client, string protocol, string file, long offset) + protected ImLog (string client, FileInfo file, TextReader reader) { Client = client; - Protocol = protocol; - LogFile = file; - LogOffset = offset; - } - - protected ImLog (string client, string protocol, string file) : this (client, protocol, file, -1) - { } - - public Uri Uri { - get { return UriFu.PathToFileUri (this.LogFile); } - } - - public string EllipsizedSnippet { - get { - string snippet = Snippet; - // FIXME: We should try to avoid breaking mid-word - if (snippet != null && snippet.Length > 50) - snippet = snippet.Substring (0, 50) + "..."; - return snippet; - } + TextReader = reader; + File = file; } public ICollection Speakers { @@ -95,16 +84,6 @@ namespace Beagle.Util { } public IList Utterances { - get { - if (! loaded) { - Load (); - loaded = true; - } - return utterances; - } - } - - protected IList RawUtterances { get { return utterances; } } @@ -115,7 +94,7 @@ namespace Beagle.Util { Utterance utt = new Utterance (); utt.Timestamp = timestamp; utt.Who = who; - utt.Text = text; + utt.Text = text.Trim (); if (StartTime.Ticks == 0 || StartTime > timestamp) StartTime = timestamp; @@ -152,6 +131,8 @@ namespace Beagle.Util { public class GaimLog : ImLog { + public const string MimeType = "beagle/x-gaim-log"; + private static string StripTags (string line, StringBuilder builder) { int first = line.IndexOf ('<'); @@ -194,104 +175,43 @@ namespace Beagle.Util { return builder.ToString (); } - private static bool IsNewConversation (string line) - { - int i = line.IndexOf ("--- New Conv"); - return 0 <= i && i < 5; - } - - static private string REGEX_DATE = - "Conversation @ \\S+\\s+(\\S+)\\s+(\\d+)\\s+(\\d+):(\\d+):(\\d+)\\s+(\\d+)"; - - static private Regex dateRegex = new Regex (REGEX_DATE, - RegexOptions.IgnoreCase | RegexOptions.Compiled); - static private DateTimeFormatInfo dtInfo = new DateTimeFormatInfo (); - - private static DateTime NewConversationTime (string line) - { - Match m = dateRegex.Match (line); - if (m.Success) { - // I'm sure there is an easier way to do this. - String monthName = m.Groups [1].ToString (); - int day = int.Parse (m.Groups [2].ToString ()); - int hr = int.Parse (m.Groups [3].ToString ()); - int min = int.Parse (m.Groups [4].ToString ()); - int sec = int.Parse (m.Groups [5].ToString ()); - int yr = int.Parse (m.Groups [6].ToString ()); - - int mo = -1; - for (int i = 1; i <= 12; ++i) { - if (monthName == dtInfo.GetAbbreviatedMonthName (i)) { - mo = i; - break; - } - } - - if (mo != -1) - return new DateTime (yr, mo, day, hr, min, sec); - } - - Console.WriteLine ("Failed on '{0}'", line); - return new DateTime (); - } - /////////////////////////////////////// - private bool TrySnippet () + public GaimLog (FileInfo file, TextReader reader) : base ("gaim", file, reader) { - int best_word_count = 0; - - foreach (Utterance utt in RawUtterances) { - - string possible_snippet = utt.Text.Trim (); - - int word_count = StringFu.CountWords (possible_snippet, 15); - if (word_count > best_word_count) { - Snippet = possible_snippet; - best_word_count = word_count; - } - - - if (word_count > 3) - return true; + // Parse what we can from the file path + try { + string str = Path.GetFileNameWithoutExtension (file.Name); + StartTime = DateTime.ParseExact (str, "yyyy-MM-dd.HHmmss", null); + } catch (Exception) { + Logger.Log.Warn ("Could not parse date/time from filename '{0}'", file.Name); + StartTime = DateTime.Now; } - return false; - } - - // FIXME: The ending timestamp in the log will be inaccurate - // until Load is called... before that, the ending time will - // come from the timestamp of the snippet-line. - - private void SetSnippet () - { - LoadWithTermination (new LoadTerminator (TrySnippet)); - } - - /////////////////////////////////////// + // Gaim likes to represent many characters in hex-escaped %xx form + SpeakingTo = StringFu.HexUnescape (file.Directory.Name); + Identity = StringFu.HexUnescape (file.Directory.Parent.Name); - private GaimLog (string protocol, string file, long offset) : base ("gaim", protocol, file, offset) - { - SetSnippet (); - } + Protocol = file.Directory.Parent.Parent.Name; - private GaimLog (string protocol, string file) : base ("gaim", protocol, file) - { - SetSnippet (); + Load (); } // Return true if a new utterance is now available, // and false if the previous utterance was changed. - private bool ProcessLine (string line) + private void ProcessLine (string line) { - if (! line.StartsWith ("(")) { + if (line.Length == 0) + return; + + if (line [0] != '(') { AppendToPreviousUtterance (line); - return false; + return; } int j = line.IndexOf (')'); if (j == -1) { AppendToPreviousUtterance (line); - return false; + return; } string whenStr = line.Substring (1, j-1); string[] whenSplit = whenStr.Split (':'); @@ -304,10 +224,10 @@ namespace Beagle.Util { // If something goes wrong, this line probably // spills over from the previous one. AppendToPreviousUtterance (line); - return false; + return; } - line = line.Substring (j+1).Trim (); + line = line.Substring (j+2); // FIXME: this is wrong --- since we just get a time, // the date gets set to 'now' @@ -322,147 +242,37 @@ namespace Beagle.Util { int i = line.IndexOf (':'); if (i == -1) - return false; + return; string alias = line.Substring (0, i); - string text = line.Substring (i+1).Trim (); + string text = line.Substring (i+2); AddUtterance (when, alias, text); - return true; + return; } - protected delegate bool LoadTerminator (); - protected override void Load () { - ClearUtterances (); - LoadWithTermination (null); - } - - protected void LoadWithTermination (LoadTerminator terminator) - { - FileStream fs; - StreamReader sr; string line; - try { - fs = new FileStream (LogFile, - FileMode.Open, - FileAccess.Read, - FileShare.Read); - if (LogOffset > 0) - fs.Seek (LogOffset, SeekOrigin.Begin); - sr = new StreamReader (fs); - } catch (Exception e) { - // If we can't open the file, just fail. - Console.WriteLine ("Could not open '{0}' (offset={1})", LogFile, LogOffset); - Console.WriteLine (e); - return; - } - + ClearUtterances (); StringBuilder builder; builder = new StringBuilder (); - line = sr.ReadLine (); // throw away first line - if (line != null) { - - // Could the second line ever start w/ < in a non-html log? - // I hope not! - bool isHtml = line.Length > 0 && line [0] == '<'; - - while ((line = sr.ReadLine ()) != null) { - if (isHtml) - line = StripTags (line, builder); - - if (IsNewConversation (line)) - break; - - // Only check termination when a new Utterance has become - // available. - if (ProcessLine (line) - && terminator != null - && terminator ()) - break; - } - } - - sr.Close (); - fs.Close (); - } - - private static void ScanNewStyleLog (FileInfo file, ArrayList array) - { - // file.Directory.Parent.Parent.Name is the name of the current protocol (ex. aim) - ImLog log = new GaimLog (file.Directory.Parent.Parent.Name, file.FullName); - - string startStr = Path.GetFileNameWithoutExtension (file.Name); - try { - log.StartTime = DateTime.ParseExact (startStr, - "yyyy-MM-dd.HHmmss", - CultureInfo.CurrentCulture); - } catch (FormatException) { - Logger.Log.Warn ("IMLog: Could not parse date/time from '{0}', ignoring.", startStr); + line = TextReader.ReadLine (); // throw away first line + if (line == null) return; - } - - log.Timestamp = file.LastWriteTime; - - // Gaim likes to represent many characters in hex-escaped %xx form - log.SpeakingTo = StringFu.HexUnescape (file.Directory.Name); - log.Identity = StringFu.HexUnescape (file.Directory.Parent.Name); - - array.Add (log); - } - - - private static void ScanOldStyleLog (FileInfo file, ArrayList array) - { - Stream stream; - stream = new FileStream (file.FullName, - FileMode.Open, - FileAccess.Read, - FileShare.Read); - StreamReader sr = new StreamReader (stream); - string line; - long offset = 0; - - string speakingTo = Path.GetFileNameWithoutExtension (file.Name); - - line = sr.ReadLine (); - bool isHtml = line.ToLower ().StartsWith (""); - offset = line.Length + 1; - - StringBuilder builder; - builder = new StringBuilder (); - while ((line = sr.ReadLine ()) != null) { - long newOffset = offset + line.Length + 1; + // Could the second line ever start w/ < in a non-html log? + // I hope not! + bool isHtml = line.Length > 0 && line [0] == '<'; + + while ((line = TextReader.ReadLine ()) != null) { if (isHtml) line = StripTags (line, builder); - if (IsNewConversation (line)) { - ImLog log = new GaimLog ("aim", file.FullName, offset); //FIXME: protocol - log.StartTime = NewConversationTime (line); - log.Identity = "_OldGaim_"; // FIXME: parse a few lines of the log to figure this out - log.SpeakingTo = speakingTo; - - array.Add (log); - } - offset = newOffset; + ProcessLine (line); } - - sr.Close (); - stream.Close (); - } - - public static ICollection ScanLog (FileInfo file) - { - ArrayList array = new ArrayList (); - if (file.Extension == ".txt" || file.Extension == ".html") - ScanNewStyleLog (file, array); - else if (file.Extension == ".log") - ScanOldStyleLog (file, array); - return array; } } @@ -471,11 +281,24 @@ namespace Beagle.Util { // // Kopete Logs // - public class KopeteLog : ImLog { - private KopeteLog (string protocol, string file) : base ("kopete", protocol, file) - { + public const string MimeType = "beagle/x-kopete-log"; + + public KopeteLog (FileInfo file, TextReader reader) : base ("kopete", file, reader) + { + // FIXME: Artificially split logs into conversations depending on the + // amount of time elapsed betweet messages? + + // Figure out the protocol from the parent.parent foldername + Protocol = file.Directory.Parent.Name.Substring (0, file.Directory.Parent.Name.Length - 8).ToLower ().ToLower (); + Identity = file.Directory.Name; + + // FIXME: This is not safe for all kinds of file/screennames + string filename = Path.GetFileNameWithoutExtension (file.Name); + SpeakingTo = filename.Substring (0, filename.LastIndexOf ('.')); + + Load (); } private const string date_format = "yyyy M d H:m:s"; @@ -488,12 +311,12 @@ namespace Beagle.Util { DateTime base_date = DateTime.MinValue; try { - reader = new XmlTextReader (new FileStream (LogFile, + reader = new XmlTextReader (File.Open( FileMode.Open, FileAccess.Read, FileShare.Read)); } catch (Exception e) { - Console.WriteLine ("Could not open '{0}'", LogFile); + Console.WriteLine ("Could not open '{0}'", File.FullName); Console.WriteLine (e); return; } @@ -543,30 +366,7 @@ namespace Beagle.Util { reader.Close (); } - - public static ICollection ScanLog (FileInfo file) - { - ArrayList array = new ArrayList (); - - // FIXME: Artificially split logs into conversations depending on the - // amount of time elapsed betweet messages? - - // Figure out the protocol from the parent.parent foldername - string protocol = file.Directory.Parent.Name.Substring (0, file.Directory.Parent.Name.Length - 8).ToLower ().ToLower (); - string filename = Path.GetFileNameWithoutExtension (file.Name); - - ImLog log = new KopeteLog (protocol, file.FullName); - - log.Timestamp = file.LastWriteTime; - log.Identity = file.Directory.Name; - // FIXME: This is not safe for all kinds of file/screennames - log.SpeakingTo = filename.Substring (0, filename.LastIndexOf ('.')); - - array.Add (log); - - return array; - } } } Index: beagled/LuceneQueryable.cs =================================================================== RCS file: /cvs/gnome/beagle/beagled/LuceneQueryable.cs,v retrieving revision 1.69 diff -u -B -p -r1.69 LuceneQueryable.cs --- beagled/LuceneQueryable.cs 20 Oct 2005 17:41:24 -0000 1.69 +++ beagled/LuceneQueryable.cs 24 Oct 2005 18:09:09 -0000 @@ -187,13 +187,6 @@ namespace Beagle.Daemon { ///////////////////////////////////////// - virtual protected Hit PostProcessHit (Hit hit) - { - return hit; - } - - ///////////////////////////////////////// - // DEPRECATED: This does nothing, since everything is now // time-based. virtual protected double RelevancyMultiplier (Hit hit) diff -u -B -p -r1.138 Makefile.am Index: beagled/GaimLogQueryable/GaimLogQueryable.cs =================================================================== RCS file: /cvs/gnome/beagle/beagled/GaimLogQueryable/GaimLogQueryable.cs,v retrieving revision 1.44 diff -u -B -p -r1.44 GaimLogQueryable.cs --- beagled/GaimLogQueryable/GaimLogQueryable.cs 20 Sep 2005 15:51:35 -0000 1.44 +++ beagled/GaimLogQueryable/GaimLogQueryable.cs 24 Oct 2005 18:09:13 -0000 @@ -58,7 +58,7 @@ namespace Beagle.Daemon.GaimLogQueryable ///////////////////////////////////////////////// private void StartWorker() - { + { if (! Directory.Exists (log_dir)) { GLib.Timeout.Add (60000, new GLib.TimeoutHandler (CheckForExistence)); return; @@ -77,9 +77,9 @@ namespace Beagle.Daemon.GaimLogQueryable if (!Inotify.Enabled) { Scheduler.Task task = Scheduler.TaskFromHook (new Scheduler.TaskHook (CrawlHook)); - task.Tag = "Crawling ~/.gaim/logs to find new logfiles"; + task.Tag = "Crawling ~/.gaim/logs to find new logfiles"; task.Source = this; - ThisScheduler.Add (task); + ThisScheduler.Add (task); } stopwatch.Stop (); @@ -97,19 +97,18 @@ namespace Beagle.Daemon.GaimLogQueryable ///////////////////////////////////////////////// private void Crawl () - { - crawler.Crawl (); - foreach (FileInfo file in crawler.Logs) { - IndexLog (file.FullName, Scheduler.Priority.Delayed); - } - } - - private void CrawlHook (Scheduler.Task task) - { - Crawl (); - task.Reschedule = true; - task.TriggerTime = DateTime.Now.AddSeconds (polling_interval_in_seconds); - } + { + crawler.Crawl (); + foreach (FileInfo file in crawler.Logs) + IndexLog (file.FullName, Scheduler.Priority.Delayed); + } + + private void CrawlHook (Scheduler.Task task) + { + Crawl (); + task.Reschedule = true; + task.TriggerTime = DateTime.Now.AddSeconds (polling_interval_in_seconds); + } ///////////////////////////////////////////////// @@ -177,59 +176,32 @@ namespace Beagle.Daemon.GaimLogQueryable ///////////////////////////////////////////////// - private static Indexable ImLogToIndexable (ImLog log) + private static Indexable ImLogToIndexable (string filename) { - Indexable indexable = new Indexable (log.Uri); - indexable.Timestamp = log.Timestamp; - indexable.MimeType = "text/plain"; + Uri uri = UriFu.PathToFileUri (filename); + Indexable indexable = new Indexable (uri); + indexable.ContentUri = uri; + indexable.Timestamp = File.GetLastWriteTime (filename); + indexable.MimeType = GaimLog.MimeType; indexable.HitType = "IMLog"; + indexable.CacheContent = false; - indexable.Filtering = IndexableFiltering.AlreadyFiltered; - - StringBuilder text = new StringBuilder (); - foreach (ImLog.Utterance utt in log.Utterances) { - //Console.WriteLine ("[{0}][{1}]", utt.Who, utt.Text); - text.Append (utt.Text); - text.Append (" "); - } - - // FIXME: It would be cleaner to have a TextReader than streamed out - // the utterances. - - indexable.AddProperty (Property.NewDate ("fixme:starttime", log.StartTime)); - indexable.AddProperty (Property.NewDate ("fixme:endtime", log.EndTime)); - indexable.AddProperty (Property.NewUnsearched ("fixme:file", log.LogFile)); - indexable.AddProperty (Property.NewUnsearched ("fixme:offset", log.LogOffset)); - indexable.AddProperty (Property.NewUnsearched ("fixme:client", log.Client)); - indexable.AddProperty (Property.NewUnsearched ("fixme:protocol", log.Protocol)); - - // FIXME: Should these use Property.NewKeyword and be searched? - indexable.AddProperty (Property.NewUnsearched ("fixme:speakingto", log.SpeakingTo)); - indexable.AddProperty (Property.NewUnsearched ("fixme:identity", log.Identity)); - - StringReader reader = new StringReader (text.ToString ()); - indexable.SetTextReader (reader); - return indexable; } private void IndexLog (string filename, Scheduler.Priority priority) { - FileInfo info = new FileInfo (filename); - if (! info.Exists) + if (! File.Exists (filename)) return; if (IsUpToDate (filename)) return; - ICollection logs = GaimLog.ScanLog (info); - foreach (ImLog log in logs) { - Indexable indexable = ImLogToIndexable (log); - Scheduler.Task task = NewAddTask (indexable); - task.Priority = priority; - task.SubPriority = 0; - ThisScheduler.Add (task); - } + Indexable indexable = ImLogToIndexable (filename); + Scheduler.Task task = NewAddTask (indexable); + task.Priority = priority; + task.SubPriority = 0; + ThisScheduler.Add (task); } override protected double RelevancyMultiplier (Hit hit) @@ -238,43 +210,6 @@ namespace Beagle.Daemon.GaimLogQueryable "fixme:endtime", "fixme:starttime"); } - override public string GetSnippet (string[] query_terms, Hit hit) - { - // FIXME: This does the wrong thing for old-style logs. - string file = hit ["fixme:file"]; - ICollection logs = GaimLog.ScanLog (new FileInfo (file)); - IEnumerator iter = logs.GetEnumerator (); - ImLog log = null; - if (iter.MoveNext ()) - log = iter.Current as ImLog; - if (log == null) - return null; - - string result = ""; - - // FIXME: This is very lame, and doesn't do the - // right thing w/ stemming, word boundaries, etc. - foreach (ImLog.Utterance utt in log.Utterances) { - string text = utt.Text; - string who = utt.Who; - - string snippet = SnippetFu.GetSnippet (query_terms, new StringReader (text)); - - if (snippet == null || snippet == "") - continue; - - result += String.Format ("{0}: {1} ", who, snippet); - - if (result.Length > 300) - break; - } - - if (result != "") - return result; - else - return log.Snippet; - } - override protected bool HitFilter (Hit hit) { ImBuddy buddy = list.Search (hit ["fixme:speakingto"]); @@ -290,19 +225,6 @@ namespace Beagle.Daemon.GaimLogQueryable return true; } - override protected Hit PostProcessHit (Hit hit) - { - ImBuddy buddy = list.Search (hit ["fixme:speakingto"]); - - if (buddy != null) { - if (buddy.Alias != "") - hit ["fixme:speakingto_alias"] = buddy.Alias; - - if (buddy.BuddyIconLocation != "") - hit ["fixme:speakingto_icon"] = Path.Combine (icons_dir, buddy.BuddyIconLocation); - } - - return hit; - } } } + Index: beagled/KopeteQueryable/KopeteQueryable.cs =================================================================== RCS file: /cvs/gnome/beagle/beagled/KopeteQueryable/KopeteQueryable.cs,v retrieving revision 1.4 diff -u -B -p -r1.4 KopeteQueryable.cs --- beagled/KopeteQueryable/KopeteQueryable.cs 6 Oct 2005 12:40:52 -0000 1.4 +++ beagled/KopeteQueryable/KopeteQueryable.cs 24 Oct 2005 18:09:15 -0000 @@ -94,19 +94,18 @@ namespace Beagle.Daemon.KopeteQueryable ///////////////////////////////////////////////// private void Crawl () - { - crawler.Crawl (); - foreach (FileInfo file in crawler.Logs) { - IndexLog (file.FullName, Scheduler.Priority.Delayed); - } - } + { + crawler.Crawl (); + foreach (FileInfo file in crawler.Logs) + IndexLog (file.FullName, Scheduler.Priority.Delayed); + } - private void CrawlHook (Scheduler.Task task) - { - Crawl (); - task.Reschedule = true; - task.TriggerTime = DateTime.Now.AddSeconds (polling_interval_in_seconds); - } + private void CrawlHook (Scheduler.Task task) + { + Crawl (); + task.Reschedule = true; + task.TriggerTime = DateTime.Now.AddSeconds (polling_interval_in_seconds); + } ///////////////////////////////////////////////// @@ -172,53 +171,32 @@ namespace Beagle.Daemon.KopeteQueryable ///////////////////////////////////////////////// - private static Indexable ImLogToIndexable (ImLog log) + private static Indexable ImLogToIndexable (string filename) { - Indexable indexable = new Indexable (log.Uri); - indexable.Timestamp = log.Timestamp; - indexable.MimeType = "text/plain"; + Uri uri = UriFu.PathToFileUri (filename); + Indexable indexable = new Indexable (uri); + indexable.ContentUri = uri; + indexable.Timestamp = File.GetLastWriteTime (filename); + indexable.MimeType = KopeteLog.MimeType; indexable.HitType = "IMLog"; + indexable.CacheContent = false; - StringBuilder text = new StringBuilder (); - foreach (ImLog.Utterance utt in log.Utterances) { - //Console.WriteLine ("[{0}][{1}]", utt.Who, utt.Text); - text.Append (utt.Text); - text.Append (" "); - } - - indexable.AddProperty (Property.NewKeyword ("fixme:file", log.LogFile)); - indexable.AddProperty (Property.NewKeyword ("fixme:offset", log.LogOffset)); - indexable.AddProperty (Property.NewDate ("fixme:starttime", log.StartTime)); - indexable.AddProperty (Property.NewKeyword ("fixme:speakingto", log.SpeakingTo)); - indexable.AddProperty (Property.NewKeyword ("fixme:identity", log.Identity)); - indexable.AddProperty (Property.NewDate ("fixme:endtime", log.EndTime)); - - indexable.AddProperty (Property.New ("fixme:client", log.Client)); - indexable.AddProperty (Property.New ("fixme:protocol", log.Protocol)); - - StringReader reader = new StringReader (text.ToString ()); - indexable.SetTextReader (reader); - return indexable; } private void IndexLog (string filename, Scheduler.Priority priority) { - FileInfo info = new FileInfo (filename); - if (! info.Exists) + if (! File.Exists (filename)) return; if (IsUpToDate (filename)) return; - ICollection logs = KopeteLog.ScanLog (info); - foreach (ImLog log in logs) { - Indexable indexable = ImLogToIndexable (log); - Scheduler.Task task = NewAddTask (indexable); - task.Priority = priority; - task.SubPriority = 0; - ThisScheduler.Add (task); - } + Indexable indexable = ImLogToIndexable (filename); + Scheduler.Task task = NewAddTask (indexable); + task.Priority = priority; + task.SubPriority = 0; + ThisScheduler.Add (task); } override protected double RelevancyMultiplier (Hit hit) @@ -227,43 +205,6 @@ namespace Beagle.Daemon.KopeteQueryable "fixme:endtime", "fixme:starttime"); } - override public string GetSnippet (string[] query_terms, Hit hit) - { - // FIXME: This does the wrong thing for old-style logs. - string file = hit ["fixme:file"]; - ICollection logs = KopeteLog.ScanLog (new FileInfo (file)); - IEnumerator iter = logs.GetEnumerator (); - ImLog log = null; - if (iter.MoveNext ()) - log = iter.Current as ImLog; - if (log == null) - return null; - - string result = ""; - - // FIXME: This is very lame, and doesn't do the - // right thing w/ stemming, word boundaries, etc. - foreach (ImLog.Utterance utt in log.Utterances) { - string text = utt.Text; - string who = utt.Who; - - string snippet = SnippetFu.GetSnippet (query_terms, new StringReader (text)); - - if (snippet == null || snippet == "") - continue; - - result += String.Format ("{0}: {1} ", who, snippet); - - if (result.Length > 300) - break; - } - - if (result != "") - return result; - else - return log.Snippet; - } - override protected bool HitFilter (Hit hit) { ImBuddy buddy = list.Search (hit ["fixme:speakingto"]);