Merge lp:~ralsina/ubuntuone-control-panel/even_more_unique_than_the_other_snowflake into lp:ubuntuone-control-panel

Proposed by Roberto Alsina
Status: Merged
Approved by: Roberto Alsina
Approved revision: 253
Merged at revision: 250
Proposed branch: lp:~ralsina/ubuntuone-control-panel/even_more_unique_than_the_other_snowflake
Merge into: lp:ubuntuone-control-panel
Diff against target: 324 lines (+265/-4)
6 files modified
ubuntuone/controlpanel/gui/qt/main/__init__.py (+4/-4)
ubuntuone/controlpanel/gui/qt/uniqueapp/__init__.py (+28/-0)
ubuntuone/controlpanel/gui/qt/uniqueapp/linux.py (+29/-0)
ubuntuone/controlpanel/gui/qt/uniqueapp/tests/__init__.py (+17/-0)
ubuntuone/controlpanel/gui/qt/uniqueapp/tests/test_windows.py (+135/-0)
ubuntuone/controlpanel/gui/qt/uniqueapp/windows.py (+52/-0)
To merge this branch: bzr merge lp:~ralsina/ubuntuone-control-panel/even_more_unique_than_the_other_snowflake
Reviewer Review Type Date Requested Status
Manuel de la Peña (community) Approve
Diego Sarmentero (community) Approve
Review via email: mp+86912@code.launchpad.net

Commit message

Make control panel a unique instance app on windows.

Description of the change

Make control panel a unique instance app on windows.

Since on windows the control panel has a tray icon, the second time control panel is instantiated, it should activate that instance instead of launching.

To test (on windows only), if you don't have the whole windows setup:

Get this zip: http://ubuntuone.com/5nigS7qTQkJvP032vaTByK

It contains trunk + this branch, compiled into exes, you can just run things by entering the dist folder.

Start ubuntuone-control-panel-qt. On another terminal, do it again. You should only have one control panel running.

If you start the first one with --minimized, then the second one should make
it show its window.

To post a comment you must log in.
Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

Missing docstrings

review: Needs Fixing
Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

+1

review: Approve
Revision history for this message
Manuel de la Peña (mandel) wrote :

Besides the fact that there are some:
+# Authors: Roberto Alsina <email address hidden>
and we are suppose to remove those, all the rest seems perfectly ok.

review: Approve
Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :
Download full text (1.0 MiB)

The attempt to merge lp:~ralsina/ubuntuone-control-panel/even_more_unique_than_the_other_snowflake into lp:ubuntuone-control-panel failed. Below is the output from the failed tests.

running build
Compiled data/qt/loadingoverlay.ui into ubuntuone/controlpanel/gui/qt/ui/loadingoverlay_ui.py
Compiled data/qt/preferences.ui into ubuntuone/controlpanel/gui/qt/ui/preferences_ui.py
Compiled data/qt/signin.ui into ubuntuone/controlpanel/gui/qt/ui/signin_ui.py
compiled data/qt/images.qrc into ubuntuone/controlpanel/gui/qt/ui/images_rc.py
Compiled data/qt/folders.ui into ubuntuone/controlpanel/gui/qt/ui/folders_ui.py
Compiled data/qt/devices.ui into ubuntuone/controlpanel/gui/qt/ui/devices_ui.py
Compiled data/qt/filesyncstatus.ui into ubuntuone/controlpanel/gui/qt/ui/filesyncstatus_ui.py
Compiled data/qt/device.ui into ubuntuone/controlpanel/gui/qt/ui/device_ui.py
Compiled data/qt/mainwindow.ui into ubuntuone/controlpanel/gui/qt/ui/mainwindow_ui.py
Compiled data/qt/device_remote.ui into ubuntuone/controlpanel/gui/qt/ui/device_remote_ui.py
Compiled data/qt/controlpanel.ui into ubuntuone/controlpanel/gui/qt/ui/controlpanel_ui.py
Compiled data/qt/account.ui into ubuntuone/controlpanel/gui/qt/ui/account_ui.py
running build_py
creating build
creating build/lib.linux-i686-2.7
creating build/lib.linux-i686-2.7/ubuntuone
copying ubuntuone/__init__.py -> build/lib.linux-i686-2.7/ubuntuone
creating build/lib.linux-i686-2.7/ubuntuone/controlpanel
copying ubuntuone/controlpanel/login_client.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel
copying ubuntuone/controlpanel/__init__.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel
copying ubuntuone/controlpanel/logger.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel
copying ubuntuone/controlpanel/dbus_service.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel
copying ubuntuone/controlpanel/replication_client.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel
copying ubuntuone/controlpanel/cache.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel
copying ubuntuone/controlpanel/backend.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel
creating build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui
copying ubuntuone/controlpanel/gui/__init__.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui
creating build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/gtk
copying ubuntuone/controlpanel/gui/gtk/package_manager.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/gtk
copying ubuntuone/controlpanel/gui/gtk/__init__.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/gtk
copying ubuntuone/controlpanel/gui/gtk/gui.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/gtk
copying ubuntuone/controlpanel/gui/gtk/widgets.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/gtk
creating build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/qt
copying ubuntuone/controlpanel/gui/qt/device.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/qt
copying ubuntuone/controlpanel/gui/qt/filesyncstatus.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/qt
copying ubuntuone/controlpanel/gui/qt/loadingoverlay.py -> build/lib.linux-i686-2.7/ubuntuone/controlpanel/gui/qt
copying ubun...

253. By Roberto Alsina

lint

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ubuntuone/controlpanel/gui/qt/main/__init__.py'
2--- ubuntuone/controlpanel/gui/qt/main/__init__.py 2011-11-14 11:46:39 +0000
3+++ ubuntuone/controlpanel/gui/qt/main/__init__.py 2012-01-04 11:47:24 +0000
4@@ -18,13 +18,11 @@
5
6 import sys
7
8-from PyQt4 import QtGui
9-
10 # Module used to include the resources into this file
11 # Unused import images_rc, pylint: disable=W0611
12 from ubuntuone.controlpanel.gui.qt.ui import images_rc
13 # pylint: enable=W0611
14-
15+from ubuntuone.controlpanel.gui.qt.uniqueapp import UniqueApplication
16
17 # Invalid name "source", pylint: disable=C0103
18 if sys.platform == 'win32':
19@@ -42,7 +40,7 @@
20 # because u1trial already provides a reactor.
21
22 # The main loop MUST be initialized before importing the reactor
23- app = QtGui.QApplication(sys.argv)
24+ app = UniqueApplication(sys.argv, "ubuntuone-control-panel")
25 source.main(app)
26
27 # Reimport 'qt4reactor', 'reactor', 'start', pylint: disable=W0404, F0401
28@@ -59,6 +57,8 @@
29 icon, window = start(reactor.stop,
30 minimized=minimized, with_icon=with_icon)
31 # pylint: enable=W0612
32+ if icon:
33+ app.new_instance.connect(icon.restore_window)
34
35 reactor.run()
36 # pylint: enable=E1101
37
38=== added directory 'ubuntuone/controlpanel/gui/qt/uniqueapp'
39=== added file 'ubuntuone/controlpanel/gui/qt/uniqueapp/__init__.py'
40--- ubuntuone/controlpanel/gui/qt/uniqueapp/__init__.py 1970-01-01 00:00:00 +0000
41+++ ubuntuone/controlpanel/gui/qt/uniqueapp/__init__.py 2012-01-04 11:47:24 +0000
42@@ -0,0 +1,28 @@
43+# -*- coding: utf-8 -*-
44+#
45+# Copyright 2011 Canonical Ltd.
46+#
47+# This program is free software: you can redistribute it and/or modify it
48+# under the terms of the GNU General Public License version 3, as published
49+# by the Free Software Foundation.
50+#
51+# This program is distributed in the hope that it will be useful, but
52+# WITHOUT ANY WARRANTY; without even the implied warranties of
53+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
54+# PURPOSE. See the GNU General Public License for more details.
55+#
56+# You should have received a copy of the GNU General Public License along
57+# with this program. If not, see <http://www.gnu.org/licenses/>.
58+
59+"""A QApplication that starts a single instance."""
60+
61+#pylint: disable=W0404
62+import sys
63+
64+if sys.platform == "win32":
65+ import windows as platform
66+else:
67+ import linux as platform
68+
69+#pylint: disable=C0103
70+UniqueApplication = platform.UniqueApplication
71
72=== added file 'ubuntuone/controlpanel/gui/qt/uniqueapp/linux.py'
73--- ubuntuone/controlpanel/gui/qt/uniqueapp/linux.py 1970-01-01 00:00:00 +0000
74+++ ubuntuone/controlpanel/gui/qt/uniqueapp/linux.py 2012-01-04 11:47:24 +0000
75@@ -0,0 +1,29 @@
76+# -*- coding: utf-8 -*-
77+#
78+# Copyright 2011 Canonical Ltd.
79+#
80+# This program is free software: you can redistribute it and/or modify it
81+# under the terms of the GNU General Public License version 3, as published
82+# by the Free Software Foundation.
83+#
84+# This program is distributed in the hope that it will be useful, but
85+# WITHOUT ANY WARRANTY; without even the implied warranties of
86+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
87+# PURPOSE. See the GNU General Public License for more details.
88+#
89+# You should have received a copy of the GNU General Public License along
90+# with this program. If not, see <http://www.gnu.org/licenses/>.
91+
92+"""A QApplication that starts a single instance."""
93+
94+from PyQt4 import QtGui, QtCore
95+
96+
97+class UniqueApplication(QtGui.QApplication):
98+
99+ """A dummy UniqueApplication class."""
100+
101+ new_instance = QtCore.pyqtSignal()
102+
103+ def __init__(self, argv, key):
104+ super(UniqueApplication, self).__init__(argv)
105
106=== added directory 'ubuntuone/controlpanel/gui/qt/uniqueapp/tests'
107=== added file 'ubuntuone/controlpanel/gui/qt/uniqueapp/tests/__init__.py'
108--- ubuntuone/controlpanel/gui/qt/uniqueapp/tests/__init__.py 1970-01-01 00:00:00 +0000
109+++ ubuntuone/controlpanel/gui/qt/uniqueapp/tests/__init__.py 2012-01-04 11:47:24 +0000
110@@ -0,0 +1,17 @@
111+# -*- coding: utf-8 -*-
112+#
113+# Copyright 2011-2012 Canonical Ltd.
114+#
115+# This program is free software: you can redistribute it and/or modify it
116+# under the terms of the GNU General Public License version 3, as published
117+# by the Free Software Foundation.
118+#
119+# This program is distributed in the hope that it will be useful, but
120+# WITHOUT ANY WARRANTY; without even the implied warranties of
121+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
122+# PURPOSE. See the GNU General Public License for more details.
123+#
124+# You should have received a copy of the GNU General Public License along
125+# with this program. If not, see <http://www.gnu.org/licenses/>.
126+
127+"""Tests for the UniqueApplication class."""
128
129=== added file 'ubuntuone/controlpanel/gui/qt/uniqueapp/tests/test_windows.py'
130--- ubuntuone/controlpanel/gui/qt/uniqueapp/tests/test_windows.py 1970-01-01 00:00:00 +0000
131+++ ubuntuone/controlpanel/gui/qt/uniqueapp/tests/test_windows.py 2012-01-04 11:47:24 +0000
132@@ -0,0 +1,135 @@
133+# -*- coding: utf-8 -*-
134+#
135+# Copyright 2011-2012 Canonical Ltd.
136+#
137+# This program is free software: you can redistribute it and/or modify it
138+# under the terms of the GNU General Public License version 3, as published
139+# by the Free Software Foundation.
140+#
141+# This program is distributed in the hope that it will be useful, but
142+# WITHOUT ANY WARRANTY; without even the implied warranties of
143+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
144+# PURPOSE. See the GNU General Public License for more details.
145+#
146+# You should have received a copy of the GNU General Public License along
147+# with this program. If not, see <http://www.gnu.org/licenses/>.
148+
149+"""Tests for the UniqueApplication class."""
150+
151+from PyQt4 import QtCore
152+from twisted.internet.defer import inlineCallbacks
153+
154+from ubuntuone.controlpanel.gui.qt.uniqueapp import windows as uniqueapp
155+from ubuntuone.controlpanel.tests import TestCase
156+
157+
158+#pylint: disable=C0103
159+class FakeLocalSocket(object):
160+ """A fake QLocalSocket."""
161+
162+ def __init__(self):
163+ self.connect_calls = []
164+ self.connect_timeouts = []
165+ self.connect_succeeds = True
166+
167+ def connectToServer(self, *args, **kwargs):
168+ """Fake connectToServer."""
169+ self.connect_calls.append((args, kwargs))
170+
171+ def waitForConnected(self, timeout):
172+ """Fake waitForConnected."""
173+ self.connect_timeouts.append(timeout)
174+ return self.connect_succeeds
175+
176+
177+class FakeSignal(object):
178+
179+ """A fake PyQt signal."""
180+
181+ def __init__(self, *args, **kwargs):
182+ """Initialize."""
183+ self.target = None
184+
185+ def connect(self, target):
186+ """Fake connect."""
187+ self.target = target
188+
189+ def disconnect(self, *args):
190+ """Fake disconnect."""
191+ self.target = None
192+
193+ def emit(self, *args):
194+ """Fake emit."""
195+ if self.target:
196+ self.target(*args)
197+
198+
199+class FakeLocalServer(object):
200+
201+ """A fake QLocalServer."""
202+
203+ def __init__(self):
204+ self.newConnection = FakeSignal()
205+ self.listen_args = []
206+
207+ def listen(self, *args, **kwargs):
208+ """Fake listen."""
209+ self.listen_args.append((args, kwargs))
210+
211+
212+class FakeApplication(object):
213+ """A fake QApplication."""
214+
215+
216+class UniqueAppTestCase(TestCase):
217+ """Test the UniqueAppplication class."""
218+
219+ @inlineCallbacks
220+ def setUp(self):
221+ yield super(UniqueAppTestCase, self).setUp()
222+ self.local_socket = FakeLocalSocket()
223+ self.patch(uniqueapp.QtNetwork, "QLocalSocket",
224+ lambda: self.local_socket)
225+ self.local_server = FakeLocalServer()
226+ self.patch(uniqueapp.QtNetwork, "QLocalServer",
227+ lambda parent: self.local_server)
228+ self.patch(uniqueapp.sys, "exit", self._set_called)
229+ self.fake_quit = FakeSignal()
230+ self.patch(uniqueapp.UniqueApplication, "aboutToQuit", self.fake_quit)
231+ self.patch(uniqueapp.QtGui, "QApplication", FakeApplication)
232+
233+ def test_client_socket(self):
234+ """Check that the client socket is used correctly."""
235+ self.local_socket.connect_succeeds = True
236+ uniqueapp.UniqueApplication([], "key")
237+ self.assertEqual(self.local_socket.connect_calls,
238+ [(("key", QtCore.QIODevice.WriteOnly), {})])
239+ self.assertEqual(self.local_socket.connect_timeouts,
240+ [500])
241+ # The connection succeeds, so it should stop
242+ self.assertEqual(self._called, ((), {}))
243+
244+ def test_server_socket(self):
245+ """Check that the server socket is used correctly."""
246+ self.local_socket.connect_succeeds = False
247+ uniqueapp.UniqueApplication([], "key")
248+ self.assertEqual(self.local_socket.connect_calls,
249+ [(("key", QtCore.QIODevice.WriteOnly), {})])
250+ self.assertEqual(self.local_socket.connect_timeouts,
251+ [500])
252+
253+ # Should not exit
254+ self.assertEqual(self._called, False)
255+
256+ def test_signal_connection(self):
257+ """Check that new_instance is correctly connected."""
258+ app = uniqueapp.UniqueApplication([], "key")
259+ # Yes, this is ugly. I can't find any other meaningful
260+ # way to compare them though.
261+ self.assertEqual(str(app.server.newConnection.target.__self__),
262+ str(app.new_instance))
263+
264+ def test_cleanup(self):
265+ """Check that cleanup is called with the right key."""
266+ app = uniqueapp.UniqueApplication([], "key")
267+ self.assertEqual(self.fake_quit.target, app.cleanup)
268
269=== added file 'ubuntuone/controlpanel/gui/qt/uniqueapp/windows.py'
270--- ubuntuone/controlpanel/gui/qt/uniqueapp/windows.py 1970-01-01 00:00:00 +0000
271+++ ubuntuone/controlpanel/gui/qt/uniqueapp/windows.py 2012-01-04 11:47:24 +0000
272@@ -0,0 +1,52 @@
273+# -*- coding: utf-8 -*-
274+#
275+# Copyright 2011 Canonical Ltd.
276+#
277+# This program is free software: you can redistribute it and/or modify it
278+# under the terms of the GNU General Public License version 3, as published
279+# by the Free Software Foundation.
280+#
281+# This program is distributed in the hope that it will be useful, but
282+# WITHOUT ANY WARRANTY; without even the implied warranties of
283+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
284+# PURPOSE. See the GNU General Public License for more details.
285+#
286+# You should have received a copy of the GNU General Public License along
287+# with this program. If not, see <http://www.gnu.org/licenses/>.
288+
289+"""A QApplication that starts a single instance."""
290+
291+import sys
292+
293+from PyQt4 import QtNetwork, QtGui, QtCore
294+
295+
296+class UniqueApplication(QtGui.QApplication):
297+
298+ """A QApplication that can only be started once.
299+
300+ Also, the new instance notifies the old one to show
301+ its window.
302+ """
303+
304+ new_instance = QtCore.pyqtSignal()
305+
306+ def __init__(self, argv, key):
307+ super(UniqueApplication, self).__init__(argv)
308+ self.key = key
309+ self.server = QtNetwork.QLocalServer(self)
310+ self.server.newConnection.connect(self.new_instance.emit)
311+ self.aboutToQuit.connect(self.cleanup)
312+ # Try to connect to existing app
313+ socket = QtNetwork.QLocalSocket()
314+ socket.connectToServer(key, QtCore.QIODevice.WriteOnly)
315+ if socket.waitForConnected(500):
316+ # Connected, exit
317+ sys.exit()
318+
319+ # Not connected, start server
320+ self.ready = self.server.listen(key)
321+
322+ def cleanup(self):
323+ """Remove the socket when we die."""
324+ self.server.removeServer(self.key)

Subscribers

People subscribed via source and target branches