~mmcg069/software-center/reviews-and-netstatus

« back to all changes in this revision

Viewing changes to softwarecenter/view/purchaseview.py

  • Committer: Matthew McGowan
  • Date: 2011-01-01 09:24:23 UTC
  • mfrom: (1354.2.40 software-center)
  • Revision ID: matthew.joseph.mcgowan@gmail.com-20110101092423-7xjj9ra1adjdx1h8
merge w trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2010 Canonical
 
2
#
 
3
# Authors:
 
4
#  Michael Vogt
 
5
#  Gary Lasker
 
6
#
 
7
# This program is free software; you can redistribute it and/or modify it under
 
8
# the terms of the GNU General Public License as published by the Free Software
 
9
# Foundation; version 3.
 
10
#
 
11
# This program is distributed in the hope that it will be useful, but WITHOUT
 
12
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 
13
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 
14
# details.
 
15
#
 
16
# You should have received a copy of the GNU General Public License along with
 
17
# this program; if not, write to the Free Software Foundation, Inc.,
 
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
19
 
 
20
import glib
 
21
import gtk
 
22
import logging
 
23
import os
 
24
import simplejson
 
25
import sys
 
26
import urllib
 
27
import webkit
 
28
import gobject
 
29
 
 
30
from gettext import gettext as _
 
31
 
 
32
from softwarecenter.backend import get_install_backend
 
33
 
 
34
LOG = logging.getLogger(__name__)
 
35
 
 
36
class ScrolledWebkitWindow(gtk.ScrolledWindow):
 
37
 
 
38
    def __init__(self):
 
39
        super(ScrolledWebkitWindow, self).__init__()
 
40
        self.webkit = webkit.WebView()
 
41
        settings = self.webkit.get_settings()
 
42
        settings.set_property("enable-plugins", False)
 
43
        self.webkit.show()
 
44
        # embed the webkit view in a scrolled window
 
45
        self.add(self.webkit)
 
46
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
47
 
 
48
class PurchaseView(gtk.VBox):
 
49
    """ 
 
50
    View that displays the webkit-based UI for purchasing an item. 
 
51
    """
 
52
    
 
53
    LOADING_HTML = """
 
54
<html>
 
55
<head>
 
56
 <title></title>
 
57
</head>
 
58
<style type="text/css">
 
59
html {
 
60
  background: #fff;
 
61
  color: #000;
 
62
  font: sans-serif;
 
63
  font: caption;
 
64
  text-align: center;
 
65
  position: absolute;
 
66
  top: 0;
 
67
  bottom: 0;
 
68
  width: 100%%;
 
69
  height: 100%%;
 
70
  display: table;
 
71
}
 
72
body {
 
73
  display: table-cell;
 
74
  vertical-align: middle;
 
75
}
 
76
h1 {
 
77
  background: url(file:///usr/share/software-center/images/spinner.gif) top center no-repeat;
 
78
  padding-top: 48px; /* leaves room for the spinner above */
 
79
  font-size: 100%%;
 
80
  font-weight: normal;
 
81
}
 
82
</style>
 
83
<body>
 
84
 <h1>%s</h1>
 
85
</body>
 
86
</html>
 
87
""" % _("Connecting to payment service...")
 
88
 
 
89
    __gsignals__ = {
 
90
         'purchase-succeeded' : (gobject.SIGNAL_RUN_LAST,
 
91
                                 gobject.TYPE_NONE,
 
92
                                 ()),
 
93
         'purchase-failed'    : (gobject.SIGNAL_RUN_LAST,
 
94
                                 gobject.TYPE_NONE,
 
95
                                 ()),
 
96
         'purchase-cancelled-by-user' : (gobject.SIGNAL_RUN_LAST,
 
97
                                         gobject.TYPE_NONE,
 
98
                                         ()),
 
99
    }
 
100
 
 
101
    def __init__(self):
 
102
        gtk.VBox.__init__(self)
 
103
        self.wk = None
 
104
 
 
105
    def init_view(self):
 
106
        if self.wk is None:
 
107
            self.wk = ScrolledWebkitWindow()
 
108
            self.wk.webkit.connect("create-web-view", 
 
109
                                   self._on_create_webview_request)
 
110
            # a possible way to do IPC (script or title change)
 
111
            self.wk.webkit.connect("script-alert", self._on_script_alert)
 
112
            self.wk.webkit.connect("title-changed", self._on_title_changed)
 
113
            
 
114
    def initiate_purchase(self, app, iconname, url=None, html=None):
 
115
        """
 
116
        initiates the purchase workflow inside the embedded webkit window
 
117
        for the item specified       
 
118
        """
 
119
        self.init_view()
 
120
        self.app = app
 
121
        self.iconname = iconname
 
122
        self.wk.webkit.load_html_string(self.LOADING_HTML, "file:///")
 
123
        self.wk.show()
 
124
        while gtk.events_pending():
 
125
            gtk.main_iteration()
 
126
        if url:
 
127
            self.wk.webkit.load_uri(url)
 
128
        elif html:
 
129
            self.wk.webkit.load_html_string(html, "file:///")
 
130
        else:
 
131
            self.wk.webkit.load_html_string(DUMMY_HTML, "file:///")
 
132
        self.pack_start(self.wk)
 
133
        # only for debugging
 
134
        if os.environ.get("SOFTWARE_CENTER_DEBUG_BUY"):
 
135
            glib.timeout_add_seconds(1, _generate_events, self)
 
136
        
 
137
    def _on_create_webview_request(self, view, frame, parent=None):
 
138
        LOG.debug("_on_create_webview_request")
 
139
        popup = gtk.Dialog()
 
140
        popup.set_size_request(750,400)
 
141
        popup.set_title("")
 
142
        popup.set_modal(True)
 
143
        popup.set_transient_for(None)
 
144
        wk = ScrolledWebkitWindow()
 
145
        wk.show()
 
146
        popup.vbox.pack_start(wk)
 
147
        popup.show()
 
148
        return wk.webkit
 
149
 
 
150
    def _on_script_alert(self, view, frame, message):
 
151
        self._process_json(message)
 
152
        # stop further processing to avoid actually showing the alter
 
153
        return True
 
154
 
 
155
    def _on_title_changed(self, view, frame, title):
 
156
        #print "on_title_changed", view, frame, title
 
157
        # see wkwidget.py _on_title_changed() for a code example
 
158
        self._process_json(title)
 
159
 
 
160
    def _process_json(self, json_string):
 
161
        try:
 
162
            LOG.debug("server returned: '%s'" % json_string)
 
163
            res = simplejson.loads(json_string)
 
164
            #print res
 
165
        except:
 
166
            LOG.debug("error processing json: '%s'" % json_string)
 
167
            return
 
168
        if res["successful"] == False:
 
169
            if res.get("user_canceled", False):
 
170
                self.emit("purchase-cancelled-by-user")
 
171
                return
 
172
            # this is what the agent implements
 
173
            elif "failures" in res:
 
174
                LOG.error("the server returned a error: '%s'" % res["failures"])
 
175
            # show a generic error, the "failures" string we get from the
 
176
            # server is way too technical to show, but we do log it
 
177
            self.emit("purchase-failed")
 
178
            return
 
179
        else:
 
180
            self.emit("purchase-succeeded")
 
181
            # gather data from response
 
182
            deb_line = res["deb_line"]
 
183
            signing_key_id = res["signing_key_id"]
 
184
            # add repo and key
 
185
            get_install_backend().add_repo_add_key_and_install_app(deb_line,
 
186
                                                                   signing_key_id,
 
187
                                                                   self.app,
 
188
                                                                   self.iconname)
 
189
 
 
190
# just used for testing --------------------------------------------
 
191
DUMMY_HTML = """
 
192
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
 
193
       "http://www.w3.org/TR/html4/loose.dtd">
 
194
<html>
 
195
<head>
 
196
 <title></title>
 
197
</head>
 
198
<body>
 
199
 <script type="text/javascript">
 
200
  function changeTitle(title) { document.title = title; }
 
201
  function success() { changeTitle('{ "successful" : true, \
 
202
                                      "deb_line" : "deb https://user:pass@private-ppa.launchpad.net/mvo/ubuntu lucid main", \
 
203
                                      "package_name" : "2vcard", \
 
204
                                      "application_name" : "The 2vcard app", \
 
205
                                      "signing_key_id" : "1024R/0EB12F05"\
 
206
                                    }') }
 
207
  function cancel() { changeTitle('{ "successful" : false }') }
 
208
 </script>
 
209
 <h1>Purchase test page</h1>
 
210
 <p> To buy Frobunicator for 99$ you need to enter your credit card info</p>
 
211
 <p>  Enter your credit card info  </p>
 
212
 <p>
 
213
 <input type="entry">
 
214
 </p>
 
215
 <input type="button" name="test_button2" 
 
216
        value="Cancel"
 
217
      onclick='cancel()'
 
218
 />
 
219
 <input type="button" name="test_button" 
 
220
        value="Buy now"
 
221
      onclick='success()'
 
222
 />
 
223
</body>
 
224
</html>
 
225
    """
 
226
 
 
227
# synthetic key event generation
 
228
def _send_keys(view, s):
 
229
    print "_send_keys", s
 
230
    MAPPING = { '@'     : 'at',
 
231
                '.'     : 'period',
 
232
                '\t'    : 'Tab',
 
233
                '\n'    : 'Return',
 
234
                '?'     : 'question',
 
235
                '\a'    : 'Down',  # fake 
 
236
                ' '     : 'space',
 
237
                '\v'    : 'Page_Down', # fake
 
238
              }
 
239
    
 
240
    for key in s:
 
241
        event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
 
242
        event.window = view.window
 
243
        if key.isdigit():
 
244
            key = "_"+key
 
245
        if hasattr(gtk.keysyms, key):
 
246
            event.keyval = getattr(gtk.keysyms, key)
 
247
        else:
 
248
            event.keyval = getattr(gtk.keysyms, MAPPING[key])
 
249
        gtk.main_do_event(event)
 
250
  
 
251
 
 
252
# \a means down key - its a just a fake to get it working
 
253
LOGIN = os.environ.get("SOFTWARE_CENTER_LOGIN") or "michael.vogt@ubuntu.com"
 
254
# for some reason the "space" key before on checkbox does not work when
 
255
# the event is generated, so this needs to be done manually :/
 
256
PAYMENT_DETAILS = "\tstreet1\tstreet2\tcity\tstate\t1234\t\a\t\a\a\t"\
 
257
                  "ACCEPTED\t4111111111111111\t1234\t\a\t\a\a\t\t\t \v"
 
258
# state-name, window title, keys
 
259
STATES = [ ('login', 'Log in', LOGIN+"\t"),
 
260
           ('confirm-sso', 'Authenticate to', '\n'),
 
261
           ('enter-payment', 'Confirm Payment Details', PAYMENT_DETAILS),
 
262
           ('confirm-payment', 'title-the-same-as-before', '\t\n'),
 
263
           ('end-state', 'no-title', ''),
 
264
         ]
 
265
def _generate_events(view):
 
266
    global STATES
 
267
 
 
268
    (state, title, keys) = STATES[0]
 
269
 
 
270
    print "_generate_events: in state", state
 
271
 
 
272
    current_title = view.wk.webkit.get_property("title")
 
273
    if current_title and current_title.startswith(title):
 
274
        print "found state", state
 
275
        _send_keys(view, keys)
 
276
        STATES.pop(0)
 
277
 
 
278
    return True
 
279
 
 
280
#     # for debugging only    
 
281
#    def _on_key_press(dialog, event):
 
282
#        print event, event.keyval
 
283
 
 
284
if __name__ == "__main__":
 
285
    #url = "http://www.animiertegifs.de/java-scripts/alertbox.php"
 
286
    #url = "http://www.ubuntu.com"
 
287
    #d = PurchaseDialog(app=None, html=DUMMY_HTML)
 
288
    #d = PurchaseDialog(app=None, url="http://spiegel.de")
 
289
    from softwarecenter.enums import BUY_SOMETHING_HOST
 
290
    url = BUY_SOMETHING_HOST+"/subscriptions/en/ubuntu/maverick/+new/?%s" % ( 
 
291
        urllib.urlencode({
 
292
                'archive_id' : "mvo/private-test", 
 
293
                'arch' : "i386",
 
294
                }))
 
295
    # use cmdline if available
 
296
    if len(sys.argv) > 1:
 
297
        url = sys.argv[1]
 
298
    # useful for debugging
 
299
    #d.connect("key-press-event", _on_key_press)
 
300
    #glib.timeout_add_seconds(1, _generate_events, d)
 
301
    
 
302
    widget = PurchaseView()
 
303
    widget.initiate_purchase(app=None, iconname=None, html=DUMMY_HTML)
 
304
 
 
305
    window = gtk.Window()
 
306
    window.add(widget)
 
307
    window.set_size_request(600, 500)
 
308
    window.set_position(gtk.WIN_POS_CENTER)
 
309
    window.show_all()
 
310
    window.connect('destroy', gtk.main_quit)
 
311
 
 
312
    gtk.main()
 
313