~elementary-apps/slingshot/synapse-desktop-only

« back to all changes in this revision

Viewing changes to lib/synapse-plugins/opensearch.vala

  • Committer: Tom Beckmann
  • Date: 2014-06-12 09:14:57 UTC
  • Revision ID: tomjonabc@gmail.com-20140612091457-1spb2qtytxybnvkg
Remove all plugins but desktop-file and command one, fix code style

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
 * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
3
 
 *
4
 
 * This program is free software; you can redistribute it and/or modify
5
 
 * it under the terms of the GNU General Public License as published by
6
 
 * the Free Software Foundation; either version 2 of the License, or
7
 
 * (at your option) any later version.
8
 
 *
9
 
 * This program is distributed in the hope that it will be useful,
10
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
 * GNU General Public License for more details.
13
 
 *
14
 
 * You should have received a copy of the GNU General Public License
15
 
 * along with this program; if not, write to the Free Software
16
 
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
17
 
 *
18
 
 * Authored by Michal Hruby <michal.mhr@gmail.com>
19
 
 *
20
 
 */
21
 
 
22
 
namespace Synapse
23
 
{
24
 
  // verbatim string
25
 
  private const string GOOGLE_SEARCH_XML = """
26
 
<?xml version="1.0" encoding="UTF-8"?>
27
 
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
28
 
  <ShortName>Google</ShortName>
29
 
  <Description>Search the web using google.com</Description>
30
 
  <Url type="text/html" method="get" template="http://www.google.com/search?q={searchTerms}&amp;hl={language}"/>
31
 
  <Url type="application/x-suggestions+json" template="http://suggestqueries.google.com/complete/search?output=firefox&amp;client=firefox&amp;hl=en&amp;q={searchTerms}"/>
32
 
 
33
 
  <Developer>Synapse dev team</Developer>
34
 
  <InputEncoding>UTF-8</InputEncoding>
35
 
</OpenSearchDescription>
36
 
""";
37
 
  private const string GOOGLE_MAPS_XML = """
38
 
<?xml version="1.0" encoding="UTF-8"?>
39
 
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
40
 
  <ShortName>Google Maps</ShortName>
41
 
  <Description>Search using Google Maps</Description>
42
 
  <Url type="text/html" method="get" template="http://maps.google.com/maps?q={searchTerms}&amp;hl={language}"/>
43
 
 
44
 
  <Developer>Synapse dev team</Developer>
45
 
  <InputEncoding>UTF-8</InputEncoding>
46
 
</OpenSearchDescription>
47
 
""";
48
 
 
49
 
  public class OpenSearchPlugin: Object, Activatable, ActionProvider
50
 
  {
51
 
    public bool enabled { get; set; default = true; }
52
 
 
53
 
    public void activate ()
54
 
    {
55
 
      actions = new Gee.ArrayList<SearchAction> ();
56
 
      load_xmls.begin ();
57
 
    }
58
 
 
59
 
    public void deactivate ()
60
 
    {
61
 
      
62
 
    }
63
 
 
64
 
    private class OpenSearchParser: Object
65
 
    {
66
 
      const MarkupParser parser =
67
 
      {
68
 
        start, end, text, null, null
69
 
      };
70
 
 
71
 
      MarkupParseContext context;
72
 
      bool is_opensearch = false;
73
 
      bool in_name_elem = false;
74
 
      bool in_description_elem = false;
75
 
 
76
 
      bool has_name = false;
77
 
      bool has_desc = false;
78
 
      bool has_url = false;
79
 
 
80
 
      public string short_name { get; set; }
81
 
      public string description { get; set; }
82
 
      public string query_url { get; set; }
83
 
      public string suggestion_url { get; set; }
84
 
      
85
 
      construct
86
 
      {
87
 
        context = new MarkupParseContext (parser, 0, this, null);
88
 
      }
89
 
 
90
 
      public bool parse (string content) throws MarkupError
91
 
      {
92
 
        return context.parse (content, -1);
93
 
      }
94
 
      
95
 
      public bool has_valid_result ()
96
 
      {
97
 
        return is_opensearch && has_name && has_desc && has_url;
98
 
      }
99
 
 
100
 
      private void process_url (string[] attrs, string[] vals)
101
 
      {
102
 
        uint len = strv_length (attrs);
103
 
        bool main_type = false;
104
 
        bool suggestion_type = false;
105
 
        
106
 
        for (uint i=0; i<len; i++)
107
 
        {
108
 
          switch (attrs[i])
109
 
          {
110
 
            case "type":
111
 
              if (vals[i] == "text/html") main_type = true;
112
 
              else if (vals[i] == "application/x-suggestions+json") suggestion_type = true;
113
 
              break;
114
 
            case "template":
115
 
              if (main_type)
116
 
              {
117
 
                query_url = vals[i];
118
 
                has_url = true;
119
 
              }
120
 
              else if (suggestion_type) suggestion_url = vals[i];
121
 
              break;
122
 
            default: break;
123
 
          }
124
 
        }
125
 
      }
126
 
 
127
 
      private void start (MarkupParseContext ctx, string name,
128
 
                          string[] attr_names, string[] attr_vals) throws MarkupError
129
 
      {
130
 
        switch (name)
131
 
        {
132
 
          case "SearchPlugin": //try to support Mozilla OpenSearch xmls
133
 
            if ("xmlns:os" in attr_names)
134
 
              is_opensearch = true;
135
 
            break;
136
 
          case "OpenSearchDescription": is_opensearch = true; break;
137
 
          case "os:ShortName":
138
 
          case "ShortName": has_name = true; in_name_elem = true; break;
139
 
          case "os:Description":
140
 
          case "Description": has_desc = true; in_description_elem = true; break;
141
 
          case "os:Url":
142
 
          case "Url": process_url (attr_names, attr_vals); break;
143
 
          default: break;
144
 
        }
145
 
      }
146
 
      
147
 
      private void end (MarkupParseContext ctx, string name) throws MarkupError
148
 
      {
149
 
        switch (name)
150
 
        {
151
 
          case "os:ShortName":
152
 
          case "ShortName": in_name_elem = false; break;
153
 
          case "os:Description":
154
 
          case "Description": in_description_elem = false; break;
155
 
          default: break;
156
 
        }
157
 
      }
158
 
      
159
 
      private void text (MarkupParseContext ctx, string text, size_t text_len) throws MarkupError
160
 
      {
161
 
        if (in_name_elem) short_name = text;
162
 
        else if (in_description_elem) description = text;
163
 
      }
164
 
    }
165
 
    
166
 
    private class SearchAction: Object, Match
167
 
    {
168
 
      // from Match interface
169
 
      public string title { get; construct set; }
170
 
      public string description { get; set; }
171
 
      public string icon_name { get; construct set; }
172
 
      public bool has_thumbnail { get; construct set; }
173
 
      public string thumbnail_path { get; construct set; }
174
 
      public MatchType match_type { get; construct set; }
175
 
      
176
 
      public int default_relevancy { get; set; default = Match.Score.INCREMENT_MINOR; }
177
 
      public string query_template { get; construct set; }
178
 
 
179
 
      public void execute (Match? match)
180
 
      {
181
 
        try
182
 
        {
183
 
          string what = (match is TextMatch) ?
184
 
            (match as TextMatch).get_text () : match.title;
185
 
          AppInfo.launch_default_for_uri (get_query_url (what),
186
 
                                          new Gdk.AppLaunchContext ());
187
 
        }
188
 
        catch (Error err)
189
 
        {
190
 
          warning ("%s", err.message);
191
 
        }
192
 
      }
193
 
      
194
 
      protected string get_query_url (string query)
195
 
      {
196
 
        string result;
197
 
        result = query_template.replace ("{searchTerms}",
198
 
                                         Uri.escape_string (query, "", false));
199
 
        result = result.replace ("{language}", get_lang ());
200
 
        // FIXME: remove all other "{codes}"
201
 
 
202
 
        return result;
203
 
      }
204
 
      
205
 
      protected string get_lang ()
206
 
      {
207
 
        string? result = null;
208
 
        foreach (unowned string lang in Intl.get_language_names ())
209
 
        {
210
 
          if (lang.length == 2)
211
 
          {
212
 
            result = lang;
213
 
            break;
214
 
          }
215
 
        }
216
 
 
217
 
        return result ?? "en";
218
 
      }
219
 
 
220
 
      public SearchAction (string name, string description, string url)
221
 
      {
222
 
        Object (title: name,
223
 
                description: description,
224
 
                query_template: url,
225
 
                has_thumbnail: false, icon_name: "applications-internet");
226
 
      }
227
 
    }
228
 
    
229
 
    private class Config: ConfigObject
230
 
    {
231
 
      public bool use_internal { get; set; default = true; }
232
 
 
233
 
      private string[] _search_engines;
234
 
      public string[] search_engines
235
 
      {
236
 
        get
237
 
        {
238
 
          return _search_engines;
239
 
        }
240
 
        set
241
 
        {
242
 
          _search_engines = value;
243
 
        }
244
 
      }
245
 
    }
246
 
    
247
 
    static void register_plugin ()
248
 
    {
249
 
      DataSink.PluginRegistry.get_default ().register_plugin (
250
 
        typeof (OpenSearchPlugin),
251
 
        "OpenSearch",
252
 
        _ ("Search the web."),
253
 
        "applications-internet",
254
 
        register_plugin
255
 
      );
256
 
    }
257
 
 
258
 
    static construct
259
 
    {
260
 
      register_plugin ();
261
 
 
262
 
      // keep in sync with the internal XMLs!
263
 
      unowned string dummy;
264
 
      dummy = N_ ("Google");
265
 
      dummy = N_ ("Search the web using google.com");
266
 
      dummy = N_ ("Google Maps");
267
 
      dummy = N_ ("Search using Google Maps");
268
 
    }
269
 
 
270
 
    private Gee.List<SearchAction> actions;
271
 
    private Config config;
272
 
 
273
 
    construct
274
 
    {
275
 
      var cs = ConfigService.get_default ();
276
 
      config = (Config) cs.get_config ("plugins", "opensearch", typeof (Config));
277
 
    }
278
 
    
279
 
    private async void load_xmls ()
280
 
    {
281
 
      OpenSearchParser parser;
282
 
      if (config.use_internal)
283
 
      {
284
 
        Gee.List<unowned string> internals = new Gee.ArrayList<unowned string> ();
285
 
        internals.add (GOOGLE_SEARCH_XML);
286
 
        internals.add (GOOGLE_MAPS_XML);
287
 
        foreach (unowned string s in internals)
288
 
        {
289
 
          parser = new OpenSearchParser ();
290
 
          try
291
 
          {
292
 
            parser.parse (s);
293
 
            if (parser.has_valid_result ())
294
 
            {
295
 
              actions.add (new SearchAction (_ (parser.short_name),
296
 
                                             _ (parser.description),
297
 
                                             parser.query_url));
298
 
            }
299
 
          }
300
 
          catch (Error no_way) { /* this really shouldn't happen */ }
301
 
        }
302
 
      }
303
 
 
304
 
      string[] xmls = config.search_engines;
305
 
      foreach (unowned string xml in xmls)
306
 
      {
307
 
        string xml_path = xml;
308
 
        if (xml_path.has_prefix ("~"))
309
 
        {
310
 
          xml_path = Environment.get_home_dir () +
311
 
                     xml_path.substring (1);
312
 
        }
313
 
        var f = File.new_for_path (xml_path);
314
 
        try
315
 
        {
316
 
          uint8[] file_contents;
317
 
          string contents;
318
 
          size_t len;
319
 
          yield f.load_contents_async (null, out file_contents, null);
320
 
          contents = (string) file_contents;
321
 
          len = file_contents.length;
322
 
          
323
 
          parser = new OpenSearchParser ();
324
 
          parser.parse (contents);
325
 
          if (parser.has_valid_result ())
326
 
          {
327
 
            actions.add (new SearchAction (parser.short_name,
328
 
                                           parser.description,
329
 
                                           parser.query_url));
330
 
          }
331
 
          else warning ("Unable to parse search plugin [%s]", xml);
332
 
        }
333
 
        catch (Error err)
334
 
        {
335
 
          warning ("Unable to load search plugin [%s]: %s", xml, err.message);
336
 
        }
337
 
      }
338
 
    }
339
 
    
340
 
    public bool handles_unknown ()
341
 
    {
342
 
      return true;
343
 
    }
344
 
 
345
 
    public ResultSet? find_for_match (ref Query query, Match match)
346
 
    {
347
 
      if (match.match_type != MatchType.UNKNOWN &&
348
 
          match.match_type != MatchType.TEXT)
349
 
      {
350
 
        return null;
351
 
      }
352
 
      var my_flags = QueryFlags.ACTIONS | QueryFlags.INTERNET;
353
 
      if ((query.query_type & my_flags) == 0) return null;
354
 
 
355
 
      bool query_empty = query.query_string == "";
356
 
      var results = new ResultSet ();
357
 
 
358
 
      if (query_empty)
359
 
      {
360
 
        foreach (var action in actions)
361
 
        {
362
 
          results.add (action, action.default_relevancy);
363
 
        }
364
 
      }
365
 
      else
366
 
      {
367
 
        var matchers = Query.get_matchers_for_query (query.query_string, 0,
368
 
          RegexCompileFlags.CASELESS);
369
 
        foreach (var item in actions)
370
 
        {
371
 
          foreach (var matcher in matchers)
372
 
          {
373
 
            if (matcher.key.match (item.title))
374
 
            {
375
 
              results.add (item, matcher.value);
376
 
              break;
377
 
            }
378
 
          }
379
 
        }
380
 
      }
381
 
 
382
 
      return results;
383
 
    }
384
 
  }
385
 
}