~josephjamesmills/+junk/mythtv-scope

« back to all changes in this revision

Viewing changes to lib/unity-scope-mythtv-videos

  • Committer: Thomas Mashos
  • Date: 2012-04-28 21:42:33 UTC
  • Revision ID: tgm4883@ubuntu.com-20120428214233-c0tnsrtef7l7p90h
Initial adding of videos, returns data but scope isn't fixed for it yet (currrently only mythtvapi.py works)
Return ALL THE THINGS!

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#! /usr/bin/python
 
2
#
 
3
# Unity Scope MythTV
 
4
#
 
5
# Copyright (C) 2012 Thomas Mashos
 
6
# thomas@mashos.com
 
7
#
 
8
# This program is free software: you can redistribute it and/or modify
 
9
# it under the terms of the GNU General Public License as published by
 
10
# the Free Software Foundation, either version 3 of the License, or
 
11
# (at your option) any later version.
 
12
#
 
13
# This program is distributed in the hope that it will be useful,
 
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
16
# GNU General Public License for more details.
 
17
#
 
18
# You should have received a copy of the GNU General Public License
 
19
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
20
#
 
21
#
 
22
#
 
23
import sys
 
24
from gi.repository import (
 
25
    GLib,
 
26
    GObject,
 
27
    Gio,
 
28
    Unity,
 
29
    Soup,
 
30
    SoupGNOME,
 
31
)
 
32
 
 
33
import mythtvapi
 
34
import socket
 
35
import os
 
36
from urlparse import urlparse
 
37
import subprocess
 
38
import codecs
 
39
import ConfigParser
 
40
import mimetypes
 
41
 
 
42
class XDGError(Exception): pass
 
43
class FileNotFoundError(Exception): pass
 
44
 
 
45
socket.setdefaulttimeout(5)
 
46
BUS_NAME = "net.launchpad.scope.video.mythtv"
 
47
 
 
48
class Daemon:
 
49
    def __init__ (self):
 
50
        self.mt = mythtvapi.MythTVService()
 
51
        self.scope = Unity.Scope.new ("/net/launchpad/scope/video/mythtv")
 
52
        self.scope.search_in_global = False
 
53
        self.scope.connect ("search-changed", self.on_search_changed)
 
54
        self.scope.connect ("filters-changed", self.on_search_changed)
 
55
        self.scope.connect ("activate-uri", self.on_activate_uri)
 
56
        
 
57
        self.scope.connect("notify::active", self.on_lens_active)
 
58
        self.scope.connect("filters-changed", self.on_filtering_changed)
 
59
        self.scope.props.sources.connect("notify::filtering",
 
60
            self.on_filtering_changed)
 
61
        self.scope.search_in_global = False
 
62
        self.scope.export()
 
63
        self.player = ''
 
64
        if os.path.isfile("/usr/bin/mythavtest"):
 
65
            self.player = "/usr/bin/mythavtest"
 
66
        self.scope.props.sources.add_option('MythTV', 'MythTV', None)
 
67
        self.sources_list = []
 
68
 
 
69
    def on_filtering_changed(self, *_):
 
70
        """Run another search when a filter change is notified."""
 
71
        self.scope.queue_search_changed(Unity.SearchType.DEFAULT)
 
72
 
 
73
    def on_lens_active(self, *_):
 
74
        """ Run a search when the lens is opened """
 
75
        if self.scope.props.active:
 
76
            self.scope.queue_search_changed(Unity.SearchType.DEFAULT)
 
77
 
 
78
    def on_activate_uri (self, scope, uri):
 
79
        if not self.player == "/usr/bin/mythavtest":
 
80
            p, VAR = self.get_defaults('video/mpeg')['exec'].split(' ', 1)
 
81
            vidplayer = subprocess.Popen(['which', p], 
 
82
            stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
 
83
            self.player = vidplayer[0].strip()
 
84
        print uri
 
85
        GLib.spawn_async([str(self.player), uri])
 
86
        return Unity.ActivationResponse(handled=Unity.HandledType.HIDE_DASH, goto_uri='')
 
87
 
 
88
    def _get_app_paths():
 
89
        paths = os.environ.get('XDG_DATA_HOME', 
 
90
            os.path.expanduser('~/.local/share/')).split(os.path.pathsep)
 
91
        paths.extend(os.environ.get('XDG_DATA_DIRS', 
 
92
            '/usr/local/share/:/usr/share/').split(os.path.pathsep))
 
93
        return paths
 
94
 
 
95
    def xdg_query(self, command, parameter):
 
96
        p = subprocess.Popen(['xdg-mime', 'query', command, parameter], 
 
97
                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 
98
        output, errors = p.communicate()
 
99
        if p.returncode or errors:
 
100
            raise XDGError('xdg-mime returned error code %d: %s' % 
 
101
                (p.returncode, errors.strip()))
 
102
        return output.strip()
 
103
 
 
104
    def locate_desktop_file(self, filename, mode='r', encoding='utf-8', _paths=_get_app_paths()):
 
105
        for path in _paths:
 
106
            for thispath, dirs, files in os.walk(os.path.join(path, 'applications')):
 
107
                if filename not in files:
 
108
                    continue
 
109
                fullname = os.path.join(thispath, filename)
 
110
                try:
 
111
                    return codecs.open(fullname, mode, encoding)
 
112
                except IOError:
 
113
                    pass
 
114
        else:
 
115
            raise FileNotFoundError(filename)
 
116
 
 
117
    def get_defaults(self, MIMETYPE):
 
118
        desktop_filename = self.xdg_query('default', MIMETYPE)
 
119
        with self.locate_desktop_file(desktop_filename) as desktop_file:
 
120
            parser = ConfigParser.ConfigParser()
 
121
            parser.readfp(desktop_file, desktop_filename)
 
122
        return dict(parser.items(parser.sections()[0]))
 
123
 
 
124
    def get_search_string (self):
 
125
        search = self.scope.props.active_search
 
126
        return search.props.search_string if search else None
 
127
 
 
128
    def on_search_changed(self, scope, search, search_type, _):
 
129
        """Get the search string, prepare a list of activated sources for 
 
130
        the server, update the model and notify the lens when we are done."""
 
131
        search_string = search.props.search_string.strip()
 
132
        # note search_string is a utf-8 encoded string, now:
 
133
        print "Search changed to %r" % search_string
 
134
        model = search.props.results_model
 
135
        model.clear()
 
136
        # Create a list of activated sources
 
137
        sources = [source for n, source in enumerate(self.sources_list)
 
138
                   if self.source_activated(str(n))]
 
139
        #Ensure MythTV is selected
 
140
        if self.source_activated('MythTV'):
 
141
            # Ensure that we are not in Global search
 
142
            if search_type is Unity.SearchType.DEFAULT:
 
143
                if len(search_string) > 0:
 
144
                    self.update_results_model(search_string, model, sources)
 
145
                    model.flush_revision_queue()
 
146
            if search:
 
147
                search.finished()
 
148
 
 
149
    def source_activated(self, source):
 
150
        """Return the state of a sources filter option."""
 
151
        active = self.scope.props.sources.get_option(source).props.active
 
152
        filtering = self.scope.props.sources.props.filtering
 
153
        if (active and filtering) or (not active and not filtering):
 
154
            return True
 
155
        else:
 
156
            return False
 
157
 
 
158
    def update_results_model(self, search, model, sources):
 
159
        '''
 
160
        Autos,Comedy,Education,Entertainment,Film,Games,Howto,Music,News,
 
161
        Nonprofit,People,Animals,Tech,Sports,Travel
 
162
        '''
 
163
        categories = []
 
164
        try:                    
 
165
            for option in self.scope.get_filter('categories').options:
 
166
                if option.props.active:
 
167
                    categories.append(option.props.id)
 
168
        except:
 
169
            categories = []
 
170
        for result in self.mt.search(SEARCHTERM = search, CATEGORY = categories):
 
171
            mimetype = mimetypes.guess_type(result[8])[0]
 
172
            if self.player == "/usr/bin/mythavtest":
 
173
                BACKEND = urlparse(result[13]).netloc
 
174
                URI = 'myth://'+result[7]+'@'+BACKEND+'/'+result[8]
 
175
            else:
 
176
                URI = result[13]+"/Content/GetFile?StorageGroup="+result[7]+"&FileName="+result[8]
 
177
            
 
178
            icon = Gio.ThemedIcon.new ("video").to_string()
 
179
            NAME = result[0]+' - S'+result[11]+'E'+result[12]+' - '+result[1]
 
180
                        
 
181
            model.append(
 
182
                URI, #uri
 
183
                icon, #icon
 
184
                3, #category
 
185
                mimetype, #mimetype
 
186
                NAME, #display_name
 
187
                result[2], #comment
 
188
                URI) #dnd_uri
 
189
            
 
190
        model.flush_revision_queue ()
 
191
        #if self.scope.props.active_search:
 
192
        #    self.scope.props.active_search.emit('finished')
 
193
 
 
194
if __name__ == "__main__":
 
195
    session_bus_connection = Gio.bus_get_sync (Gio.BusType.SESSION, None)
 
196
    session_bus = Gio.DBusProxy.new_sync (session_bus_connection, 0, None,
 
197
                                        'org.freedesktop.DBus',
 
198
                                        '/org/freedesktop/DBus',
 
199
                                        'org.freedesktop.DBus', None)
 
200
    result = session_bus.call_sync('RequestName',
 
201
                                    GLib.Variant ("(su)", (BUS_NAME, 0x4)),
 
202
                                    0, -1, None)
 
203
                                       
 
204
    # Unpack variant response with signature "(u)". 1 means we got it.
 
205
    result = result.unpack()[0]
 
206
        
 
207
    if result != 1 :
 
208
        print >> sys.stderr, "Failed to own name %s. Unity-Scope-MythTV is working." % BUS_NAME
 
209
        raise SystemExit (1)
 
210
        
 
211
    daemon = Daemon()
 
212
    GObject.MainLoop().run()