~ps-jenkins/unity-scope-texdoc/latestsnapshot-0.1daily13.05.31ubuntu.unity.next-0ubuntu1

« back to all changes in this revision

Viewing changes to src/unity_texdoc_daemon.py

  • Committer: Mark Tully
  • Date: 2013-02-25 23:56:38 UTC
  • Revision ID: markjtully@gmail.com-20130225235638-yls7e7n1g0d48d6e
Updated to new API
Converted to Python 3

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#! /usr/bin/python
 
1
#! /usr/bin/python3
2
2
# -*- coding: utf-8 -*-
3
3
 
4
 
# Copyright(C) 2012 name <email>
 
4
# Copyright(C) 2013 name <email>
5
5
# This program is free software: you can redistribute it and/or modify it
6
6
# under the terms of the GNU General Public License version 3, as published
7
7
# by the Free Software Foundation.
14
14
# You should have received a copy of the GNU General Public License along
15
15
# with this program.  If not, see <http://www.gnu.org/licenses/>.
16
16
 
17
 
from gi.repository import GLib, GObject, Gio
18
 
from gi.repository import Unity, UnityExtras
 
17
from gi.repository import GLib, Gio
 
18
from gi.repository import Unity
19
19
import gettext
20
20
import re
21
21
import os.path
22
22
from subprocess import Popen, PIPE
23
23
from mimetypes import guess_type
24
 
from HTMLParser import HTMLParser
 
24
from html.parser import HTMLParser
25
25
 
26
26
try:
27
27
    from pyPdf import PdfFileReader
37
37
gettext.textdomain(APP_NAME)
38
38
_ = gettext.gettext
39
39
 
40
 
BUS_NAME = 'com.canonical.Unity.Scope.Help.Texdoc'
41
 
BUS_PATH = '/com/canonical/unity/scope/help/texdoc'
 
40
GROUP_NAME = 'com.canonical.Unity.Scope.Help.Texdoc'
 
41
UNIQUE_PATH = '/com/canonical/unity/scope/help/texdoc'
42
42
 
 
43
SEARCH_HINT = _('Search Texdoc')
 
44
NO_RESULTS_HINT = _('Sorry, there are no Texdoc results that match your search.')
 
45
PROVIDER_CREDITS = _('')
43
46
SVG_DIR = '/usr/share/icons/unity-icon-theme/places/svg/'
44
 
CAT_0_ICON = Gio.ThemedIcon.new(SVG_DIR + 'group-help.svg')
45
 
CAT_0_TITLE = _('Texdoc')
46
 
 
47
 
NO_RESULTS_HINT = _('Sorry, there are no Texdoc results that match your search.')
48
 
SEARCH_HINT = _('Search Texdoc')
49
 
SEARCH_URI = ''
50
 
 
 
47
PROVIDER_ICON = SVG_DIR + 'service-texdoc.svg'
 
48
DEFAULT_RESULT_ICON = SVG_DIR + 'result-help.svg'
 
49
DEFAULT_RESULT_MIMETYPE = 'text/html'
 
50
DEFAULT_RESULT_TYPE = Unity.ResultType.DEFAULT
51
51
TEXDOC_RE = re.compile('^\s*(\d+)\s+(.+?)$')
52
52
 
53
 
 
54
 
class Daemon:
55
 
    def __init__(self):
56
 
        self.scope = Unity.Scope.new(BUS_PATH, 'texdoc')
57
 
        self.scope.props.search_hint = SEARCH_HINT
58
 
        self.scope.search_in_global = True
59
 
        cats = []
60
 
        cats.append(Unity.Category.new(CAT_0_TITLE.lower(),
61
 
                                       CAT_0_TITLE,
62
 
                                       CAT_0_ICON,
63
 
                                       Unity.CategoryRenderer.VERTICAL_TILE))
64
 
        self.scope.props.categories = cats
65
 
        self.preferences = Unity.PreferencesManager.get_default()
66
 
        self.preferences.connect('notify::remote-content-search', self._on_preference_changed)
67
 
        self.scope.connect('search-changed', self.on_search_changed)
68
 
        self.scope.export()
69
 
 
70
 
    def _on_preference_changed(self, *_):
71
 
        self.scope.queue_search_changed(Unity.SearchType.DEFAULT)
72
 
 
73
 
    def on_search_changed(self, scope, search, search_type, *_):
74
 
        model = search.props.results_model
75
 
        model.clear()
76
 
        if self.preferences.props.remote_content_search != Unity.PreferencesManagerRemoteContent.ALL:
77
 
            search.finished()
78
 
            return
79
 
        search_string = search.props.search_string.strip()
80
 
        print('Search changed to \'%s\'' % search_string)
81
 
        self.update_results_model(search_string, model)
82
 
        search.set_reply_hint('no-results-hint',
83
 
                              GLib.Variant.new_string(NO_RESULTS_HINT))
84
 
        search.finished()
85
 
 
86
 
    def update_results_model(self, search, results):
87
 
 
88
 
        result = []
89
 
        if len(search) > 2:   # don't run texdoc for strings shorter than 3 chars
90
 
            proc = Popen(["texdoc", "--list", "--nointeract", search], stdout=PIPE)
91
 
            os.waitpid(proc.pid, 0)
92
 
            out = proc.communicate()[0]
93
 
            for line in out.splitlines():
94
 
                m = TEXDOC_RE.match(line)
95
 
                if m:
96
 
                    result.append(m.group(2))
97
 
        for res in result:
98
 
            info = self.get_file_info(res)
99
 
            if info['mimetype']:
100
 
                # Only handle known file types, otherwise we might get
101
 
                # Makefiles and things like that.
102
 
                results.append(uri=info['uri'],
103
 
                               icon_hint=info['icon'].to_string(),
104
 
                               category=0,
105
 
                               mimetype=info['mimetype'],
106
 
                               title=info['filename'],
107
 
                               comment=info['title'],
108
 
                               dnd_uri=info['uri'],
109
 
                               result_type=Unity.ResultType.DEFAULT)
110
 
 
111
 
    def get_file_info(self, filepath):
112
 
        info = {}
113
 
        info['uri'] = 'file://' + filepath
114
 
        info['filename'] = os.path.basename(filepath)
115
 
        info['mimetype'] = guess_type(info['uri'])[0] or ''
116
 
        if info['mimetype']:
117
 
            info['icon'] = Gio.content_type_get_icon(info['mimetype'])
118
 
            if info['mimetype'] == 'application/pdf' and PDF_AVAILABLE:
119
 
                info['title'] = self.get_pdf_title(filepath)
120
 
            elif info['mimetype'] == 'text/html':
121
 
                info['title'] = self.get_html_title(filepath)
122
 
            else:
123
 
                info['title'] = ''
 
53
c1 = {'id': 'technical',
 
54
      'name': _('Technical Documents'),
 
55
      'icon': SVG_DIR + 'group-installed.svg',
 
56
      'renderer': Unity.CategoryRenderer.VERTICAL_TILE}
 
57
CATEGORIES = [c1]
 
58
 
 
59
FILTERS = []
 
60
 
 
61
EXTRA_METADATA = []
 
62
 
 
63
 
 
64
def get_file_info(filepath):
 
65
    info = {}
 
66
    info['uri'] = 'file://' + filepath
 
67
    info['filename'] = os.path.basename(filepath)
 
68
    info['mimetype'] = guess_type(info['uri'])[0] or ''
 
69
    if info['mimetype']:
 
70
        info['icon'] = Gio.content_type_get_icon(info['mimetype'])
 
71
        if info['mimetype'] == 'application/pdf' and PDF_AVAILABLE:
 
72
            info['title'] = get_pdf_title(filepath)
 
73
        elif info['mimetype'] == 'text/html':
 
74
            info['title'] = get_html_title(filepath)
124
75
        else:
125
 
            info['icon'] = Gio.ThemedIcon.new('text-x-generic')
126
76
            info['title'] = ''
127
 
        return info
128
 
 
129
 
    def get_pdf_title(self, filepath):
130
 
        with open(filepath, 'rb') as pdffile:
131
 
            reader = PdfFileReader(pdffile)
132
 
            try:
133
 
                return reader.documentInfo.title or ''
134
 
            except PdfReadError:
135
 
                return ''
136
 
 
137
 
    def get_html_title(self, filepath):
138
 
 
139
 
        class HTMLTitleParser(HTMLParser):
140
 
            def __init__(self):
141
 
                HTMLParser.__init__(self)
 
77
    else:
 
78
        info['icon'] = Gio.ThemedIcon.new('text-x-generic')
 
79
        info['title'] = ''
 
80
    return info
 
81
 
 
82
 
 
83
def get_pdf_title(filepath):
 
84
    with open(filepath, 'rb') as pdffile:
 
85
        reader = PdfFileReader(pdffile)
 
86
        try:
 
87
            return reader.documentInfo.title or ''
 
88
        except PdfReadError:
 
89
            return ''
 
90
 
 
91
 
 
92
def get_html_title(filepath):
 
93
 
 
94
    class HTMLTitleParser(HTMLParser):
 
95
        def __init__(self):
 
96
            HTMLParser.__init__(self)
 
97
            self.is_title = False
 
98
            self.title_complete = False
 
99
            self.title = ''
 
100
 
 
101
        def handle_starttag(self, tag, attrs):
 
102
            if tag == 'title':
 
103
                self.is_title = True
 
104
 
 
105
        def handle_data(self, data):
 
106
            if self.is_title:
 
107
                self.title += data
 
108
 
 
109
        def handle_endtag(self, tag):
 
110
            if tag == 'title':
 
111
                self.title_complete = True
142
112
                self.is_title = False
143
 
                self.title_complete = False
144
 
                self.title = ''
145
 
 
146
 
            def handle_starttag(self, tag, attrs):
147
 
                if tag == 'title':
148
 
                    self.is_title = True
149
 
 
150
 
            def handle_data(self, data):
151
 
                if self.is_title:
152
 
                    self.title += data
153
 
 
154
 
            def handle_endtag(self, tag):
155
 
                if tag == 'title':
156
 
                    self.title_complete = True
157
 
                    self.is_title = False
158
 
 
159
 
        with open(filepath, 'r') as htmlfile:
160
 
            parser = HTMLTitleParser()
161
 
            for line in htmlfile:
162
 
                parser.feed(line)
163
 
                if parser.title_complete:
164
 
                    return parser.title
165
 
        return ''
166
 
 
167
 
if __name__ == '__main__':
168
 
    daemon = UnityExtras.dbus_own_name(BUS_NAME, Daemon, None)
169
 
    if daemon:
170
 
        GLib.unix_signal_add(0, 2, lambda x: daemon.quit(), None)
171
 
        daemon.run([])
 
113
 
 
114
    with open(filepath, 'r') as htmlfile:
 
115
        parser = HTMLTitleParser()
 
116
        for line in htmlfile:
 
117
            parser.feed(line)
 
118
            if parser.title_complete:
 
119
                return parser.title
 
120
 
 
121
 
 
122
def search(search, filters):
 
123
    '''
 
124
    Search for help documents matching the search string
 
125
    '''
 
126
    results = []
 
127
    result = []
 
128
    if len(search) > 2:   # don't run texdoc for strings shorter than 3 chars
 
129
        proc = Popen(["texdoc", "--list", "--nointeract", search], stdout=PIPE)
 
130
        os.waitpid(proc.pid, 0)
 
131
        out = proc.communicate()[0]
 
132
        out = out.decode('utf8')
 
133
        for line in out.splitlines():
 
134
            m = TEXDOC_RE.match(line)
 
135
            if m:
 
136
                result.append(m.group(2))
 
137
    for res in result:
 
138
        info = get_file_info(res)
 
139
        if info['mimetype']:
 
140
            # Only handle known file types, otherwise we might get
 
141
            # Makefiles and things like that.
 
142
            results.append({'uri': info['uri'],
 
143
                            'icon': info['icon'].to_string(),
 
144
                            'category': 0,
 
145
                            'mimetype': info['mimetype'],
 
146
                            'title': info['filename'],
 
147
                            'comment': info['title']})
 
148
    return results
 
149
 
 
150
 
 
151
def activate(scope, uri):
 
152
    '''
 
153
    Open the url in the default webbrowser
 
154
    Args:
 
155
      uri: The url to be opened
 
156
    '''
 
157
    uri = uri[:len(uri) - 3]
 
158
    parameters = ["devhelp", "-s", uri]
 
159
    subprocess.Popen(parameters)
 
160
    return Unity.ActivationResponse(handled=Unity.HandledType.HIDE_DASH, goto_uri='')
 
161
 
 
162
 
 
163
# Classes below this point establish communication
 
164
# with Unity, you probably shouldn't modify them.
 
165
 
 
166
 
 
167
class MySearch(Unity.ScopeSearchBase):
 
168
    def __init__(self, search_context):
 
169
        super(MySearch, self).__init__()
 
170
        self.set_search_context(search_context)
 
171
 
 
172
    def do_run(self):
 
173
        '''
 
174
        Adds results to the model
 
175
        '''
 
176
        try:
 
177
            result_set = self.search_context.result_set
 
178
            for i in search(self.search_context.search_query,
 
179
                            self.search_context.filter_state):
 
180
                if not 'uri' in i or not i['uri'] or i['uri'] == '':
 
181
                    continue
 
182
                if not 'icon' in i or not i['icon'] or i['icon'] == '':
 
183
                    i['icon'] = DEFAULT_RESULT_ICON
 
184
                if not 'mimetype' in i or not i['mimetype'] or i['mimetype'] == '':
 
185
                    i['mimetype'] = DEFAULT_RESULT_MIMETYPE
 
186
                if not 'result_type' in i or not i['result_type'] or i['result_type'] == '':
 
187
                    i['result_type'] = DEFAULT_RESULT_TYPE
 
188
                if not 'category' in i or not i['category'] or i['category'] == '':
 
189
                    i['category'] = 0
 
190
                if not 'title' in i or not i['title']:
 
191
                    i['title'] = ''
 
192
                if not 'comment' in i or not i['comment']:
 
193
                    i['comment'] = ''
 
194
                if not 'dnd_uri' in i or not i['dnd_uri'] or i['dnd_uri'] == '':
 
195
                    i['dnd_uri'] = i['uri']
 
196
                i['metadata'] = {}
 
197
                if EXTRA_METADATA:
 
198
                    for e in i:
 
199
                        for m in EXTRA_METADATA:
 
200
                            if m['id'] == e:
 
201
                                i['metadata'][e] = i[e]
 
202
                i['metadata']['provider_credits'] = GLib.Variant('s', PROVIDER_CREDITS)
 
203
                result = Unity.ScopeResult.create(str(i['uri']), str(i['icon']),
 
204
                                                  i['category'], i['result_type'],
 
205
                                                  str(i['mimetype']), str(i['title']),
 
206
                                                  str(i['comment']), str(i['dnd_uri']),
 
207
                                                  i['metadata'])
 
208
                result_set.add_result(result)
 
209
        except Exception as error:
 
210
            print(error)
 
211
 
 
212
 
 
213
class Scope(Unity.AbstractScope):
 
214
    def __init__(self):
 
215
        Unity.AbstractScope.__init__(self)
 
216
 
 
217
    def do_get_search_hint(self):
 
218
        return SEARCH_HINT
 
219
 
 
220
    def do_get_schema(self):
 
221
        '''
 
222
        Adds specific metadata fields
 
223
        '''
 
224
        schema = Unity.Schema.new()
 
225
        if EXTRA_METADATA:
 
226
            for m in EXTRA_METADATA:
 
227
                schema.add_field(m['id'], m['type'], m['field'])
 
228
        #FIXME should be REQUIRED for credits
 
229
        schema.add_field('provider_credits', 's', Unity.SchemaFieldType.OPTIONAL)
 
230
        return schema
 
231
 
 
232
    def do_get_categories(self):
 
233
        '''
 
234
        Adds categories
 
235
        '''
 
236
        cs = Unity.CategorySet.new()
 
237
        if CATEGORIES:
 
238
            for c in CATEGORIES:
 
239
                cat = Unity.Category.new(c['id'], c['name'],
 
240
                                         Gio.ThemedIcon.new(c['icon']),
 
241
                                         c['renderer'])
 
242
                cs.add(cat)
 
243
        return cs
 
244
 
 
245
    def do_get_filters(self):
 
246
        '''
 
247
        Adds filters
 
248
        '''
 
249
        fs = Unity.FilterSet.new()
 
250
        #if FILTERS:
 
251
        #
 
252
        return fs
 
253
 
 
254
    def do_get_group_name(self):
 
255
        return GROUP_NAME
 
256
 
 
257
    def do_get_unique_name(self):
 
258
        return UNIQUE_PATH
 
259
 
 
260
    def do_create_search_for_query(self, search_context):
 
261
        se = MySearch(search_context)
 
262
        return se
 
263
 
 
264
 
 
265
def load_scope():
 
266
    return Scope()