3
* GNOME Do is the legal property of its developers. Please refer to the
4
* COPYRIGHT file distributed with this
7
* This program is free software: you can redistribute it and/or modify
8
* it under the terms of the GNU General Public License as published by
9
* the Free Software Foundation, either version 3 of the License, or
10
* (at your option) any later version.
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
17
* You should have received a copy of the GNU General Public License
18
* along with this program. If not, see <http://www.gnu.org/licenses/>.
23
using System.Reflection;
24
using System.Collections.Generic;
32
public class UniverseManager
36
/// How long between update events (seconds).
38
const int UpdateInterval = 60;
41
/// Maximum amount of time to spend updating (millseconds).
43
const int MaxUpdateTime = 125;
45
const int MaxSearchResults = 1000;
47
Dictionary<string, List<IObject>> firstResults;
48
Dictionary<IObject, IObject> universe;
51
/// Contains types we've seen while loading plugins.
53
Dictionary<Type, Assembly> loadedTypes;
55
List<DoItemSource> doItemSources;
56
List<DoAction> doActions;
58
// Keep track of next data structures to update.
60
int firstResultsCursor;
62
public UniverseManager()
64
universe = new Dictionary<IObject, IObject> ();
65
doItemSources = new List<DoItemSource> ();
66
doActions = new List<DoAction> ();
67
firstResults = new Dictionary<string, List<IObject>> ();
68
loadedTypes = new Dictionary<Type, Assembly> ();
69
itemSourceCursor = firstResultsCursor = 0;
72
internal void Initialize ()
79
GLib.Timeout.Add (UpdateInterval * 1000,
80
new GLib.TimeoutHandler (OnTimeoutUpdate));
83
private bool OnTimeoutUpdate ()
85
if (!Do.Controller.IsSummoned) {
86
Gtk.Application.Invoke (delegate {
93
private void Update ()
98
// Keep track of the total time (in ms) we have spend updating.
99
// We spend half of MaxUpdateTime updating item sources, then
100
// another half of MaxUpdateTime updating first results lists.
102
while (t_update < MaxUpdateTime / 2) {
103
DoItemSource itemSource;
104
ICollection<IItem> oldItems;
105
Dictionary<IObject, DoItem> newItems;
108
itemSourceCursor = (itemSourceCursor + 1) % doItemSources.Count;
109
itemSource = doItemSources[itemSourceCursor];
110
newItems = new Dictionary<IObject, DoItem> ();
111
// Remember old items.
112
oldItems = itemSource.Items;
113
// Update the item source.
114
itemSource.UpdateItems ();
115
// Create a map of the new items.
116
foreach (DoItem newItem in itemSource.Items) {
117
newItems[newItem] = newItem;
119
// Update the universe by either updating items, adding new items,
120
// or removing items.
121
foreach (DoItem newItem in itemSource.Items) {
122
if (universe.ContainsKey (newItem)) {
123
// We're updating an item. This updates the item across all
124
// first results lists.
125
(universe[newItem] as DoItem).Inner = newItem.Inner;
127
// We're adding a new item. It might take a few minutes to show
128
// up in all results lists.
129
universe[newItem] = newItem;
132
// See if there are any old items that didn't make it into the
133
// set of new items. These items need to be removed from the universe.
134
foreach (DoItem oldItem in oldItems) {
135
if (!newItems.ContainsKey (oldItem) &&
136
universe.ContainsKey (oldItem)) {
137
universe.Remove (oldItem);
140
Log.Info ("Updated \"{0}\" Item Source.", itemSource.Name);
141
t_update += (DateTime.Now - then).Milliseconds;
144
// Updating a first results list takes about 50ms at most, so we can afford
145
// to update a couple of them.
147
while (t_update < MaxUpdateTime / 2) {
148
string firstResultKey = null;
149
int currentFirstResultsList = 0;
152
firstResultsCursor = (firstResultsCursor + 1) % firstResults.Count;
153
// Now pick a first results list to update.
154
foreach (KeyValuePair<string, List<IObject>> keyval in firstResults) {
155
if (currentFirstResultsList == firstResultsCursor) {
156
firstResultKey = keyval.Key;
159
currentFirstResultsList++;
161
if (firstResults.ContainsKey (firstResultKey)) {
162
firstResults.Remove (firstResultKey);
164
firstResults[firstResultKey] = SortAndNarrowResults (universe.Values, firstResultKey, null, true);
165
Log.Info ("Updated first results for '{0}'.", firstResultKey);
166
t_update += (DateTime.Now - then).Milliseconds;
170
internal ICollection<DoItemSource> ItemSources
172
get { return doItemSources.AsReadOnly (); }
175
protected void LoadBuiltins ()
177
// Load from Do.Addins asembly.
178
LoadAssembly (typeof (IItem).Assembly);
179
// Load from main application assembly.
180
LoadAssembly (typeof (DoItem).Assembly);
183
private IEnumerable<string> PluginsDirs
188
dirs = new List<string>();
189
dirs.Add (Paths.UserPlugins);
190
dirs.AddRange (Paths.SystemPlugins);
195
protected void LoadPlugins ()
197
foreach (string plugin_dir in PluginsDirs) {
198
Log.Info ("Searching for plugins in directory {0}", plugin_dir);
203
files = System.IO.Directory.GetFiles (plugin_dir);
204
} catch (Exception e) {
205
Log.Warn ("Could not read plugins directory {0}: {1}", plugin_dir, e.Message);
209
foreach (string file in files) {
212
if (!file.EndsWith (".dll")) continue;
214
plugin = Assembly.LoadFile (file);
215
LoadAssembly (plugin);
216
} catch (Exception e) {
217
Log.Error ("Encountered and error while trying to load plugin {0}: {1}",
225
private void LoadAssembly (Assembly plugin)
227
if (plugin == null) return;
229
foreach (Type type in plugin.GetTypes ()) {
230
if (type.IsAbstract) continue;
231
if (type == typeof (VoidAction)) continue;
232
if (type == typeof (DoItem)) continue;
233
if (type == typeof (DoAction)) continue;
234
if (type == typeof (DoItemSource)) continue;
235
if (loadedTypes.ContainsKey (type)) {
236
Log.Warn ("Duplicate plugin type detected; {0} may be a duplicate plugin.",
241
loadedTypes[type] = plugin;
242
foreach (Type iface in type.GetInterfaces ()) {
243
if (iface == typeof (IItemSource)) {
244
IItemSource source = null;
247
source = System.Activator.CreateInstance (type) as IItemSource;
248
} catch (Exception e) {
250
Log.Error ("Failed to load item source from {0}: {1}",
251
plugin.Location, e.Message);
253
if (source != null) {
254
doItemSources.Add (new DoItemSource (source));
255
Log.Info ("Successfully loaded \"{0}\" item source.", source.Name);
258
if (iface == typeof (IAction)) {
259
IAction action = null;
262
action = System.Activator.CreateInstance (type) as IAction;
263
} catch (Exception e) {
265
Log.Error ("Failed to load action from {0}: {1}",
266
plugin.Location, e.Message);
268
if (action != null) {
269
doActions.Add (new DoAction (action));
270
Log.Info ("Successfully loaded \"{0}\" action.", action.Name);
277
protected List<IObject>
278
SortResults (IEnumerable<IObject> broadResults, string query, IObject other, bool strict)
280
List<IObject> results;
282
results = new List<IObject> ();
283
foreach (DoObject obj in broadResults) {
284
if (strict && query.Length > 0 &&
285
!obj.CanBeFirstResultForKeypress (query[0]))
288
obj.UpdateRelevance (query, other as DoObject);
289
if (obj.Relevance > 0) {
297
protected List<IObject>
298
SortAndNarrowResults (IEnumerable<IObject> broadResults, string query, IObject other, bool strict)
300
List<IObject> results;
302
results = SortResults (broadResults, query, other, strict);
303
// Shorten the list if neccessary.
304
if (results.Count > MaxSearchResults)
305
results = results.GetRange (0, MaxSearchResults);
309
private void BuildFirstResults ()
311
// For each starting character, add every matching object from the universe to
312
// the firstResults list corresponding to that character.
313
for (char keypress = 'a'; keypress <= 'z'; keypress++) {
314
firstResults[keypress.ToString ()] = SortAndNarrowResults (
315
universe.Values, keypress.ToString (), null, true);
319
private void BuildUniverse ()
322
foreach (DoAction action in doActions) {
323
universe[action] = action;
327
foreach (DoItemSource source in doItemSources) {
328
ICollection<IItem> items;
330
items = source.Items;
331
if (items.Count == 0) {
332
source.UpdateItems ();
333
items = source.Items;
335
foreach (DoItem item in items) {
336
universe[item] = item;
339
Log.Info ("Universe contains {0} objects.", universe.Count);
342
public void Search (ref SearchContext context)
344
List<IObject> results = new List<IObject> ();
346
// First check to see if the search context is equivalent to the
347
// lastContext or parentContext. Just return that context if it is.
348
SearchContext oldContext = context.EquivalentPreviousContextIfExists ();
349
if (oldContext != null) {
350
context = oldContext.GetContinuedContext ();
353
else if (context.ParentSearch) {
354
context = ParentContext (context);
357
else if (context.ChildrenSearch) {
358
// TODO: Children are not filtered at all. This needs to be fixed.
360
context = ChildContext (context);
364
if (context.Independent) {
365
results = IndependentResults (context);
367
results = DependentResults (context);
369
results.AddRange (SpecialItemsForContext (context));
371
context.Results = results.ToArray ();
372
// Keep a stack of incremental results.
373
context = context.GetContinuedContext ();
376
private SearchContext ParentContext (SearchContext context)
378
//Since we are dealing with the parent, turn off the finding parent
380
context.ParentSearch = false;
381
//Check to see if parent context exists first
382
if (context.ParentContext == null)
385
context = context.ParentContext;
386
context.ChildrenSearch = false;
390
private SearchContext ChildContext (SearchContext context)
393
List<IObject> children;
394
SearchContext newContext;
396
// Check to if the current object has children first
397
if (context.Selection is IItem) {
398
children = ChildrenOfItem (context.Selection as IItem);
400
children = new List<IObject> ();
402
if (children.Count == 0) {
403
context.ChildrenSearch = false;
406
children = SortResults (children, "", null, false);
408
// Increase relevance of the parent.
409
parent = context.Selection as DoObject;
410
if (parent != null) {
411
parent.IncreaseRelevance (context.Query, null);
414
newContext = context.Clone () as SearchContext;
415
newContext.ParentContext = context;
416
newContext.Query = string.Empty;
417
newContext.Results = children.ToArray ();
418
newContext.ChildrenSearch = false;
419
newContext.LastContext = new SearchContext (false);
421
// We need to do something like this (filter the children), but DR's work
423
// if (!context.Independent) {
424
// newContext.Results = DependentResults (newContext).ToArray ();
426
context.ChildrenSearch = true;
427
return newContext.GetContinuedContext ();
430
private List<IObject> DependentResults (SearchContext context)
432
List<IObject> results = null;
433
string query = context.Query.ToLower ();
435
if (context.ActionSearch && context.Query.Length == 0) {
436
return InitialActionResults (context);
438
else if (context.LastContext.LastContext != null) {
439
return FilterPreviousSearchResultsWithContinuedContext (context);
442
// Or else this is a brand new search:
443
if (firstResults.ContainsKey (query))
444
results = new List<IObject> (firstResults[query]);
446
results = new List<IObject> (universe.Values);
448
// Filter the results appropriately.
449
if (context.ModifierItemsSearch) {
450
// Use a dictionary to get the intersection of the dynamic modifier
451
// items for all items in the context.
452
Dictionary <IItem, IItem> dynamicModItems = new Dictionary<IItem, IItem> ();
453
foreach (IItem item in context.Items) {
454
foreach (IItem modItem in
455
context.Action.DynamicModifierItemsForItem (item)) {
456
dynamicModItems[modItem] = modItem;
459
// Add the intersected set to the results list.
460
foreach (IItem modItem in dynamicModItems.Values) {
461
results.Insert (0, modItem);
463
results = GetModItemsFromList (context, results);
464
// We need to sort because we added the out-of-order dynamic modifier
466
results = SortResults (results, context.Query, context.Items[0], false);
469
results = GetItemsFromList (context, results);
470
results = SortResults (results, context.Query, null, false);
476
private List<IObject> SpecialItemsForContext (SearchContext context)
478
List<IObject> results = new List<IObject> ();
479
IItem textItem = new DoTextItem (context.Query);
481
// If we're on modifier items, add a text item if it's supported.
482
if (context.ModifierItemsSearch) {
483
if (context.Action.SupportsModifierItemForItems (context.Items.ToArray (), textItem)) {
484
results.Add (textItem);
487
// Same if we're on items.
488
else if (context.ItemsSearch) {
489
if (context.Action.SupportsItem (textItem)) {
490
results.Add (textItem);
493
// If independent, always add.
494
else if (context.Independent) {
495
results.Add (textItem);
501
// This generates a list of modifier items supported by the context in a given initial list.
502
public List<IObject> GetModItemsFromList (SearchContext context, List<IObject> initialList)
504
List<IObject> results = new List<IObject> ();
505
IItem[] items = context.Items.ToArray ();
507
foreach (IObject iobject in initialList) {
508
if (iobject is IItem) {
509
// If the item is supported add it
510
if (context.Action.SupportsModifierItemForItems (items, iobject as IItem)) {
511
results.Add (iobject);
518
// Same as GetModItemsFrom list but for items
519
public List<IObject> GetItemsFromList (SearchContext context, List<IObject> initialList)
521
List<IObject> results = new List<IObject> ();
522
foreach (IObject iobject in initialList) {
523
if (iobject is IItem) {
524
if (context.Action.SupportsItem (iobject as IItem)) {
525
results.Add (iobject);
532
// This will filter out the results in the previous context that match the current query
533
private List<IObject> FilterPreviousSearchResultsWithContinuedContext (SearchContext context)
535
return SortResults (context.LastContext.Results, context.Query, null, false);
538
private List<IObject> IndependentResults (SearchContext context)
541
List<IObject> results;
543
query = context.Query.ToLower ();
544
if (context.LastContext.LastContext != null) {
545
// We can build on the last results.
546
// example: searched for "f" then "fi"
547
results = FilterPreviousSearchResultsWithContinuedContext (context);
549
} else if (firstResults.ContainsKey (query)) {
550
// If someone typed a single key, BOOM we're done.
551
results = new List<IObject> (firstResults[query]);
554
// Or we just have to do an expensive search...
555
results = SortAndNarrowResults (universe.Values, query, null, false);
560
// This method gives us all the actions that are supported by all the items in the list
561
private List<IObject> InitialActionResults (SearchContext context)
563
List<IObject> actions = new List<IObject> ();
564
List<IObject> actions_to_remove = new List<IObject> ();
567
foreach (IItem item in context.Items) {
568
List<IObject> item_actions = ActionsForItem (item);
570
// If this is the first item in the list, add all of its supported actions.
572
actions.AddRange (item_actions);
575
// For every subsequent item, check every action in the
576
// pre-existing list if its not supported by this item, remove
579
foreach (IAction action in actions) {
580
if (!item_actions.Contains (action)) {
581
actions_to_remove.Add (action);
586
foreach (IObject rm in actions_to_remove)
588
return SortResults (actions, context.Query, context.Items[0], false);
591
public List<IObject> ActionsForItem (IItem item)
593
List<IObject> item_actions;
595
item_actions = new List<IObject> ();
596
foreach (IAction action in doActions) {
597
if (action.SupportsItem (item)) {
598
item_actions.Add (action);
604
public List<IObject> ChildrenOfItem (IItem parent)
606
List<IObject> children;
608
children = new List<IObject> ();
609
foreach (DoItemSource source in doItemSources) {
610
foreach (IObject child in source.ChildrenOfItem (parent))
611
children.Add (child);