├── .gitignore
├── LICENSE
├── README.md
├── changelog.md
├── data
├── icons
│ ├── pithos-large.ico
│ ├── pithos-small.ico
│ └── scalable
│ │ └── apps
│ │ ├── pithos-mono.svg
│ │ └── pithos.svg
├── media
│ ├── album_default.png
│ ├── album_default.svg
│ ├── icon.png
│ ├── pithos-mono.png
│ ├── rate_bg.png
│ └── rate_bg.svg
└── ui
│ ├── AboutPithosDialog.ui
│ ├── PithosWindow.ui
│ ├── PreferencesPithosDialog.ui
│ ├── SearchDialog.ui
│ ├── StationsDialog.ui
│ ├── about_pithos_dialog.xml
│ ├── pithos_window.xml
│ ├── preferences_pithos_dialog.xml
│ ├── search_dialog.xml
│ └── stations_dialog.xml
├── pithos.bat
├── pithos.pyw
├── pithos
├── AboutPithosDialog.py
├── PreferencesPithosDialog.py
├── SearchDialog.py
├── StationsDialog.py
├── __init__.py
├── gobject_worker.py
├── pandora
│ ├── __init__.py
│ ├── blowfish.py
│ ├── fake.py
│ └── pandora.py
├── pithosconfig.py
├── plugin.py
├── plugins
│ ├── __init__.py
│ ├── growl.py
│ ├── mediakeys.py
│ ├── notification_icon.py
│ └── scrobble.py
└── pylast.py
└── windows
├── MoveFileFolder.nsh
├── dependencies.nsi
├── detect.nsi
├── download.nsi
├── install.nsi
└── redist.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.msi
3 | *.exe
4 | /pithos/pithos.ini
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Pithos for Windows
2 | =============
3 |
4 | Pithos is a native Pandora Radio client for ~~Linux~~ Windows. It's much more lightweight than the Pandora.com web client.
5 |
6 | **NOTE** This project is no longer maintained. Upstream [Pithos](https://github.com/pithos/pithos) is actively developed however does not directly support Windows. If you would like to use Pithos on Windows your best chance is probably using the Linux version on Windows via [WSL](https://docs.microsoft.com/en-us/windows/wsl/about).
7 |
8 |
9 | Installation
10 | -----------
11 | Newest release can be found [here](https://github.com/TingPing/pithos-for-windows/releases) and read notes below.
12 |
13 | 64bit users can just download this [zip](https://github.com/TingPing/pithos-for-windows/archive/master.zip) and extract it, though 64bit isn't recommended and you will have to manage the dependencies yourself.
14 |
15 | See [redist.txt](https://github.com/TingPing/pithos-for-windows/blob/master/windows/redist.txt) for optional plugin requirements.
16 |
17 | Notes
18 | -----
19 |
20 | You must remove pygtk if installed with an older version.
21 |
22 | If Python is installed to a custom directory(not C:\python27) it must be in the PATH environment variable to work (google to find out how)
23 |
24 | If previous versions crashed please try out the newest version as this has been resolved.
25 |
26 | ------------------
27 |
28 | 
29 |
30 | ------------------
31 |
32 | Pithos is not affiliated with or endorsed by Pandora Media, Inc.
33 |
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | $$title: Changelog
2 |
3 | ### 0.3.17
4 | 2012-05-03
5 |
6 | * Switch to JSON API to work around XMLRPC API update. Thanks to
7 | Christopher Eby for the patch.
8 | [(lp:988395)](https://bugs.launchpad.net/pithos/+bug/988395)
9 |
10 | ### 0.3.16
11 | 2012-04-19
12 |
13 | * Numerous bugfixes
14 | * [(lp:905204)](https://bugs.launchpad.net/pithos/+bug/905204)
15 | * [(lp:876984)](https://bugs.launchpad.net/pithos/+bug/876984)
16 | * [(lp:976327)](https://bugs.launchpad.net/pithos/+bug/976327)
17 | * [(lp:883610)](https://bugs.launchpad.net/pithos/+bug/883610)
18 | * [(lp:891225)](https://bugs.launchpad.net/pithos/+bug/891225)
19 | * [(lp:880142)](https://bugs.launchpad.net/pithos/+bug/880142)
20 | * [(lp:836393)](https://bugs.launchpad.net/pithos/+bug/836393)
21 | * [(lp:907278)](https://bugs.launchpad.net/pithos/+bug/907278)
22 | * [(lp:812626)](https://bugs.launchpad.net/pithos/+bug/812626)
23 |
24 |
25 | * Minor User Interface improvements
26 | * [(lp:904589)](https://bugs.launchpad.net/pithos/+bug/904589)
27 | * [(lp:909198)](https://bugs.launchpad.net/pithos/+bug/909198)
28 |
29 | Thanks to Adam Porter, Derek Ditch, Martin Langhoff, and Malcolm Lewis, who contributed to this release.
30 |
31 | ### 0.3.14
32 | 2011-12-14
33 |
34 | * Use misc.sync method to get Pandora time offset. Fixes "You have no chance
35 | to survive make your time" [(lp:743198)](https://bugs.launchpad.net/pithos/+bug/743198)
36 | (thanks Matt Harrison, Adam Haile)
37 | * Improve error message and make it clear which messages come from Pandora
38 | * Fix media key support on Ubuntu 11.10 [(lp:902322)](https://bugs.launchpad.net/pithos/+bug/902322) (thanks RJP Computing)
39 |
40 | ### 0.3.13
41 | 2011-11-09
42 |
43 | * Use SSL for login - fixes AUTH_WEB_LOGIN_NOT_ALLOWED error from Pandora
44 | change 2 minutes after 0.3.12 release
45 |
46 | ### 0.3.12
47 | 2011-11-09
48 |
49 | * Pandora protocol v33
50 | * Less confusing error dialog for the next Pandora update
51 | * Notify plugin: escape song info so album names with special characters
52 | display properly on non-Ubuntu Gnome.
53 |
54 | ### 0.3.11
55 | 2011-09-22
56 |
57 | * Pandora protocol v32
58 | * Setting for audio format (by Stefan Nelson-Lindall)
59 |
60 | ### 0.3.10
61 | 2011-07-09
62 |
63 | * Pandora protocol v31; No key change, minor change to createStation
64 |
65 | ### 0.3.9
66 | 2011-04-27
67 |
68 | * New Pandora encryption keys [(lp:771804)](https://bugs.launchpad.net/pithos/+bug/771804)
69 | * Enable gstreamer progressive download to improve stream quality and work around [gstreamer bug 648786](https://bugzilla.gnome.org/show_bug.cgi?id=648786) on Ubuntu 11.04. [(lp:705271)](https://bugs.launchpad.net/pithos/+bug/705271) [(lp:759699)](https://bugs.launchpad.net/pithos/+bug/759699)
70 |
71 | ### 0.3.8
72 | 2011-04-12
73 |
74 | * CVE-2011-1500: Fix password leak to local users through file permissions (by Luke Faraone) [(lp:733307)](https://bugs.launchpad.net/pithos/+bug/733307)
75 | * Correctly handle hour-long songs (by Luke Faraone) [(lp:734962)](https://bugs.launchpad.net/pithos/+bug/734962)
76 | * Fix "TypeError: could not convert argument to correct param type" (by Rick Spencer) [(lp:706681)](https://bugs.launchpad.net/pithos/+bug/706681)
77 |
78 |
79 | ### 0.3.7
80 | 2011-01-09
81 |
82 | * Allow feedback to be removed from songs (by Christopher Eby) [(lp:659581)](https://bugs.launchpad.net/pithos/+bug/659581)
83 | * Don't save Pandora feedback to last.fm [(lp:636600)](https://bugs.launchpad.net/pithos/+bug/636600)
84 | * Minor bugfixes [(lp:670131)](https://bugs.launchpad.net/pithos/+bug/670131) [(lp:658230)](https://bugs.launchpad.net/pithos/+bug/658230)
85 |
86 | ### 0.3.6
87 | 2010-11-06
88 |
89 | * New Pandora encryption keys [(lp:671265)](https://bugs.launchpad.net/pithos/+bug/671265)
90 |
91 | ### 0.3.5
92 | 2010-10-30
93 |
94 | * Minor bugfixes [(lp:625095)](https://bugs.launchpad.net/pithos/+bug/625095) [(lp:628923)](https://bugs.launchpad.net/pithos/+bug/628923) [(lp:626980)](https://bugs.launchpad.net/pithos/+bug/626980)
95 | * Run properly on multi-user systems [(lp:667896)](https://bugs.launchpad.net/pithos/+bug/667896)
96 | * Screensaver pause plugin (by Matthew Gregg)
97 | * Integrate volume control with PulseAudio [(lp:650515)](https://bugs.launchpad.net/pithos/+bug/650515)
98 | * New Pandora encryption keys [(lp:655507)](https://bugs.launchpad.net/pithos/+bug/625095)
99 |
100 | ### 0.3.1
101 | 2010-08-26
102 |
103 | * Send song ratings to Last.fm [(lp:621360)](https://bugs.launchpad.net/pithos/+bug/621360)
104 | * Fixes to keybinder media key support
105 | * Better error messages
106 | * Dump debug log and XML to /tmp/pithos.debug.log for better debugging and bug reports
107 | * Don't crash if QuickMix is empty [(lp:622864)](https://bugs.launchpad.net/pithos/+bug/622864)
108 |
109 | ### 0.3
110 | 2010-08-19
111 |
112 | * Last.fm Scrobbling support [(lp:609246)](https://bugs.launchpad.net/pithos/+bug/609246)
113 | * Rewrite Libpiano in Python to avoid C dependency
114 | * Raise existing window if pithos command is run when already open [(lp:610574)](https://bugs.launchpad.net/pithos/+bug/610574)
115 | * Support bookmarking of songs and artists
116 | * Volume control (by Stephen Ostrow) [(lp:617597)](https://bugs.launchpad.net/pithos/+bug/617597)
117 | * Show song info in window title (by Stephen Ostrow) [(lp:614851)](https://bugs.launchpad.net/pithos/+bug/614851)
118 | * Double click a future song to play it (by Stephen Ostrow) [(lp:614852)](https://bugs.launchpad.net/pithos/+bug/614852)
119 | * Make stations dialog sortable (by Stephen Ostrow) [(lp:615415)](https://bugs.launchpad.net/pithos/+bug/615415)
120 | * Notification shown when play/pause media keys are pressed [(lp:614821)](https://bugs.launchpad.net/pithos/+bug/614821)
121 | * Other bugfixes [(lp:615160)](https://bugs.launchpad.net/pithos/+bug/615160) [(lp:614850)](https://bugs.launchpad.net/pithos/+bug/614850)
122 |
123 | ### 0.2.1
124 | 2010-08-02
125 |
126 | * Ellipsize long song titles / artist names [(lp:610859)](https://bugs.launchpad.net/pithos/+bug/610859)
127 | * Request Email instead of Username [(lp:609265)](https://bugs.launchpad.net/pithos/+bug/609265)
128 |
129 | ### 0.2
130 | 2010-07-24
131 |
132 | * Notification icon (by Vince Spicer and Vinod Khare)
133 | * User interface improvements
134 | * DBUS API
135 |
136 | ### 0.1
137 | January 2010
138 |
139 | * Play / Pause / Next Song
140 | * Switching stations
141 | * Remembering user name and password
142 | * Cover Art
143 | * Thumbs Up / Thumbs Down / Tired of this song
144 | * Notification popup with song info
145 | * Launching pandora.com song info page and station page
146 | * Reconnecting when pandora session times out
147 | * Editing QuickMix
148 | * Creating stations
149 | * Media Key support
150 | * Proxy support
151 |
--------------------------------------------------------------------------------
/data/icons/pithos-large.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TingPing/pithos-for-windows/02a93e76ff6b616e14f10dfc04d1532bd7d98940/data/icons/pithos-large.ico
--------------------------------------------------------------------------------
/data/icons/pithos-small.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TingPing/pithos-for-windows/02a93e76ff6b616e14f10dfc04d1532bd7d98940/data/icons/pithos-small.ico
--------------------------------------------------------------------------------
/data/icons/scalable/apps/pithos-mono.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
268 |
--------------------------------------------------------------------------------
/data/icons/scalable/apps/pithos.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
219 |
--------------------------------------------------------------------------------
/data/media/album_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TingPing/pithos-for-windows/02a93e76ff6b616e14f10dfc04d1532bd7d98940/data/media/album_default.png
--------------------------------------------------------------------------------
/data/media/album_default.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
118 |
--------------------------------------------------------------------------------
/data/media/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TingPing/pithos-for-windows/02a93e76ff6b616e14f10dfc04d1532bd7d98940/data/media/icon.png
--------------------------------------------------------------------------------
/data/media/pithos-mono.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TingPing/pithos-for-windows/02a93e76ff6b616e14f10dfc04d1532bd7d98940/data/media/pithos-mono.png
--------------------------------------------------------------------------------
/data/media/rate_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TingPing/pithos-for-windows/02a93e76ff6b616e14f10dfc04d1532bd7d98940/data/media/rate_bg.png
--------------------------------------------------------------------------------
/data/media/rate_bg.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
109 |
--------------------------------------------------------------------------------
/data/ui/AboutPithosDialog.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
38 |
39 |
--------------------------------------------------------------------------------
/data/ui/PreferencesPithosDialog.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 333
7 | False
8 | 5
9 | Preferences
10 | center
11 | pithos
12 | normal
13 |
14 |
15 | True
16 | False
17 | 2
18 |
19 |
20 | True
21 | False
22 | end
23 |
24 |
25 | gtk-cancel
26 | False
27 | True
28 | True
29 | True
30 | False
31 | True
32 |
33 |
34 |
35 | False
36 | False
37 | 0
38 |
39 |
40 |
41 |
42 | gtk-ok
43 | False
44 | True
45 | True
46 | True
47 | False
48 | True
49 |
50 |
51 |
52 | False
53 | False
54 | 1
55 |
56 |
57 |
58 |
59 | False
60 | True
61 | end
62 | 0
63 |
64 |
65 |
66 |
67 | True
68 | False
69 | 15
70 | 3
71 | 5
72 | 5
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | True
115 | False
116 | 0
117 | Email
118 |
119 |
120 | 1
121 | 2
122 | 1
123 | 2
124 |
125 |
126 |
127 |
128 | True
129 | True
130 | •
131 |
132 |
133 | 2
134 | 3
135 | 1
136 | 2
137 |
138 |
139 |
140 |
141 | True
142 | False
143 | 0
144 | Password
145 |
146 |
147 | 1
148 | 2
149 | 2
150 | 3
151 |
152 |
153 |
154 |
155 | True
156 | True
157 | False
158 | •
159 |
160 |
161 | 2
162 | 3
163 | 2
164 | 3
165 |
166 |
167 |
168 |
169 | Pandora One Subscriber
170 | False
171 | True
172 | True
173 | False
174 | False
175 | 0.5
176 | True
177 |
178 |
179 | 2
180 | 3
181 | 3
182 | 4
183 |
184 |
185 |
186 |
187 | True
188 | False
189 | 0
190 | Pandora.com Account
191 |
192 |
193 |
194 |
195 |
196 | 3
197 | 5
198 |
199 |
200 |
201 |
202 | True
203 | False
204 | 0
205 | Options
206 |
207 |
208 |
209 |
210 |
211 | 3
212 | 8
213 | 9
214 | 5
215 |
216 |
217 |
218 |
219 | Show Growl notifications
220 | False
221 | True
222 | True
223 | False
224 | False
225 | 0.5
226 | True
227 |
228 |
229 | 1
230 | 3
231 | 10
232 | 11
233 |
234 |
235 |
236 |
237 | True
238 | False
239 | <small><a href='http://pandora.com'>Create an account at pandora.com</a></small>
240 | True
241 |
242 |
243 | 1
244 | 3
245 | 4
246 | 5
247 |
248 |
249 |
250 |
251 | True
252 | False
253 | 0
254 | Audio Quality
255 |
256 |
257 | 1
258 | 2
259 | 6
260 | 7
261 |
262 |
263 |
264 |
265 | True
266 | False
267 |
268 |
269 | 2
270 | 3
271 | 6
272 | 7
273 |
274 |
275 |
276 |
277 | True
278 | False
279 | 0
280 | Proxy URL
281 |
282 |
283 | 1
284 | 2
285 | 7
286 | 8
287 |
288 |
289 |
290 |
291 | True
292 | True
293 | •
294 |
295 |
296 | 2
297 | 3
298 | 7
299 | 8
300 |
301 |
302 |
303 |
304 | Show notification area icon
305 | False
306 | True
307 | True
308 | False
309 | False
310 | 0.5
311 | True
312 |
313 |
314 | 1
315 | 3
316 | 11
317 | 12
318 |
319 |
320 |
321 |
322 | True
323 | False
324 | 0
325 | Last.fm Scrobbling
326 |
327 |
328 |
329 |
330 |
331 | 3
332 | 13
333 | 14
334 | 5
335 |
336 |
337 |
338 |
339 | Authenticate
340 | False
341 | True
342 | True
343 | True
344 | False
345 |
346 |
347 | 1
348 | 3
349 | 14
350 | 15
351 |
352 |
353 |
354 |
355 | True
356 | False
357 | 0
358 | Advanced Settings
359 |
360 |
361 |
362 |
363 |
364 | 3
365 | 5
366 | 6
367 | 5
368 |
369 |
370 |
371 |
372 | Use Mediakeys
373 | False
374 | True
375 | True
376 | False
377 | False
378 | 0.5
379 | True
380 |
381 |
382 | 1
383 | 3
384 | 9
385 | 10
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 | False
394 | True
395 | 5
396 | 1
397 |
398 |
399 |
400 |
401 |
402 | button2
403 | button1
404 |
405 |
406 |
407 |
--------------------------------------------------------------------------------
/data/ui/SearchDialog.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 380
8 | 350
9 | 5
10 | Search
11 | pithos
12 | normal
13 | False
14 |
15 |
16 | True
17 | 2
18 |
19 |
20 | True
21 | 4
22 |
23 |
24 | True
25 |
26 |
27 | True
28 | True
29 | •
30 |
31 |
32 |
33 | 0
34 |
35 |
36 |
37 |
38 | Search
39 | True
40 | True
41 | True
42 | image1
43 |
44 |
45 |
46 | False
47 | False
48 | 1
49 |
50 |
51 |
52 |
53 | False
54 | 0
55 |
56 |
57 |
58 |
59 | True
60 | True
61 | automatic
62 | automatic
63 |
64 |
65 | True
66 | True
67 | False
68 |
69 |
70 |
71 | column
72 |
73 |
74 |
75 | 1
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | 1
85 |
86 |
87 |
88 |
89 | 1
90 |
91 |
92 |
93 |
94 | True
95 | end
96 |
97 |
98 | gtk-cancel
99 | True
100 | True
101 | True
102 | True
103 |
104 |
105 | False
106 | False
107 | 0
108 |
109 |
110 |
111 |
112 | gtk-ok
113 | True
114 | False
115 | True
116 | True
117 | True
118 |
119 |
120 |
121 | False
122 | False
123 | 1
124 |
125 |
126 |
127 |
128 | False
129 | end
130 | 0
131 |
132 |
133 |
134 |
135 |
136 | button2
137 | okbtn
138 |
139 |
140 |
141 | True
142 | gtk-find
143 |
144 |
145 |
--------------------------------------------------------------------------------
/data/ui/StationsDialog.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 480
8 | 420
9 | 1
10 | Manage Stations
11 | pithos
12 | normal
13 |
14 |
15 |
16 | True
17 | vertical
18 | 2
19 |
20 |
21 | True
22 | True
23 | automatic
24 | automatic
25 |
26 |
27 | True
28 | True
29 |
30 |
31 |
32 |
33 | 1
34 |
35 |
36 |
37 |
38 | True
39 | end
40 |
41 |
42 | Add Station
43 | True
44 | True
45 | True
46 |
47 |
48 |
49 | False
50 | False
51 | 0
52 | True
53 |
54 |
55 |
56 |
57 | Genre Stations
58 | False
59 | True
60 | True
61 | True
62 |
63 |
64 |
65 | False
66 | False
67 | 1
68 | True
69 |
70 |
71 |
72 |
73 | Refresh Stations
74 | True
75 | True
76 | True
77 |
78 |
79 |
80 | False
81 | False
82 | 2
83 | True
84 |
85 |
86 |
87 |
88 | gtk-close
89 | True
90 | True
91 | True
92 | True
93 |
94 |
95 |
96 | False
97 | False
98 | 3
99 |
100 |
101 |
102 |
103 | False
104 | end
105 | 0
106 |
107 |
108 |
109 |
110 |
111 | add_station
112 | add_genre_station
113 | refresh_stations
114 | close
115 |
116 |
117 |
156 |
157 | True
158 | gtk-edit
159 |
160 |
161 | 5
162 | normal
163 | True
164 | warning
165 |
166 |
167 | True
168 | vertical
169 | 2
170 |
171 |
172 | True
173 | end
174 |
175 |
176 | gtk-cancel
177 | True
178 | True
179 | True
180 | True
181 |
182 |
183 | False
184 | False
185 | 0
186 |
187 |
188 |
189 |
190 | gtk-delete
191 | True
192 | True
193 | True
194 | True
195 |
196 |
197 | False
198 | False
199 | 1
200 |
201 |
202 |
203 |
204 | False
205 | end
206 | 0
207 |
208 |
209 |
210 |
211 |
212 | button2
213 | button1
214 |
215 |
216 |
217 |
--------------------------------------------------------------------------------
/data/ui/about_pithos_dialog.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/data/ui/pithos_window.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/data/ui/preferences_pithos_dialog.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/data/ui/search_dialog.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/data/ui/stations_dialog.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/pithos.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | REM
3 | REM Win32 launch script for Pithos based on Exailes
4 | REM
5 | REM Since GStreamer SDK and OSSBuild are a bit difficult to work with, we
6 | REM go through and set things up for the user so they don't need to worry
7 | REM too much about PATH variables being set properly and other madness.
8 | REM
9 | REM Additionally, this script tries to be a bit more verbose and let the
10 | REM user know more about the errors that they are seeing, instead of just a
11 | REM stack trace.
12 | REM
13 |
14 | setlocal
15 |
16 | set PYTHON_EXE=pythonw.exe
17 |
18 | echo Detecting Pithos requirements (this may take a minute):
19 |
20 | REM Detect Python in the path
21 | for %%X in (%PYTHON_EXE%) do (set PYTHON_BIN=%%~$PATH:X)
22 | if defined PYTHON_BIN set PYTHON_VIA=environment
23 | if defined PYTHON_BIN goto python_found
24 |
25 | REM No python in path, see if its in a default location. Prefer
26 | REM Python 2.7, since our installer ships with that as default
27 | set PYTHON_BIN=C:\Python27\%PYTHON_EXE%
28 | set PYTHON_VIA=hardcoded
29 | if exist python goto python_found
30 |
31 | :python_found
32 | echo Python : %PYTHON_BIN% (via %PYTHON_VIA%)
33 | if %PYTHON_VIA%==environment set PYTHON_BIN=%PYTHON_EXE%
34 | if %PYTHON_VIA%==hardcoded echo "If this is incorrect add the correct one to the PATH environment variable (google is your friend)"
35 |
36 | REM Detect GStreamer SDK
37 | set GST_VIA=environment
38 | set GST_SDK=N
39 | if defined GSTREAMER_SDK_ROOT_X86 set GST_SDK=%GSTREAMER_SDK_ROOT_X86%
40 | if defined GSTREAMER_SDK_ROOT_X86_64 set GST_SDK=%GSTREAMER_SDK_ROOT_X86_64%
41 |
42 | if not "%GST_SDK%" == "N" goto pygst_env_found
43 |
44 | REM For some reason the GStreamer SDK doesn't define the environment
45 | REM variables globally, so we just have to cheat if we can't do it
46 | REM the 'correct' way
47 | if exist C:\gstreamer-sdk\0.10\x86\bin goto found_pygst_x86_hardcoded
48 | if exist C:\gstreamer-sdk\0.10\x86_64\bin goto found_pygst_x64_hardcoded
49 | goto nogst
50 |
51 | :found_pygst_x86_hardcoded
52 | set GSTREAMER_SDK_ROOT_X86=C:\gstreamer-sdk\0.10\x86
53 | set GST_SDK=%GSTREAMER_SDK_ROOT_X86%
54 | set GST_VIA=hardcoded path
55 | goto pygst_env_found
56 |
57 | :found_pygst_x64_hardcoded
58 | set GSTREAMER_SDK_ROOT_X64=C:\gstreamer-sdk\0.10\x86_64
59 | set GST_SDK=%GSTREAMER_SDK_ROOT_X64%
60 | set GST_VIA=hardcoded path
61 | goto pygst_env_found
62 |
63 | :pygst_env_found
64 | echo GStreamer SDK Runtime : %GST_SDK% (via %GST_VIA%)
65 |
66 | REM
67 | REM Then try to setup the environment properly for GStreamer SDK
68 | REM -> Note that we put the GST path first, so that any needed DLLs
69 | REM are searched for there first, hopefully avoiding DLL hell
70 | REM
71 |
72 | set PATH=%GST_SDK%\bin;%PATH%
73 | set PYGST_BINDINGS=%GST_SDK%\lib\python2.7\site-packages
74 | if defined PYTHONPATH set PYTHONPATH=%PYGST_BINDINGS%;%PYTHONPATH%
75 | if not defined PYTHONPATH set PYTHONPATH=%PYGST_BINDINGS%
76 |
77 | :: Do this in case user has installed Python33 and selected for it to be added to PATH. Pithos does not work with Python33, it gives an exit code ^> 0.
78 | if exist "C:\Python27\%PYTHON_BIN%" set "PYTHON_BIN=C:\Python27\%PYTHON_BIN%"
79 |
80 | %PYTHON_BIN% -c "import pygst;pygst.require('0.10');import gst"
81 | if not %ERRORLEVEL% == 0 goto badgst
82 |
83 | :pygst_found
84 | echo GStreamer Python Bindings : %PYGST_BINDINGS%
85 |
86 | REM Detect PyGTK. We do detection here since it may be in the GStreamer SDK
87 | %PYTHON_BIN% -c "import pygtk;pygtk.require('2.0');import gtk"
88 | if not %ERRORLEVEL% == 0 goto badgtk
89 |
90 | echo PyGTK : OK
91 |
92 | echo Dependencies good, starting Pithos.
93 | echo.
94 |
95 | goto start_pithos
96 |
97 | REM Various errors
98 |
99 | :nopython
100 | echo Python 2.7 was not detected. Please include the python directory in your
101 | echo PATH, or install it. You can download it at http://www.python.com/
102 | echo.
103 | pause && goto end
104 |
105 | :nogst
106 | echo GStreamer SDK Runtime : not found
107 | echo.
108 | echo GStreamer SDK Runtime was not found.
109 | echo.
110 | echo You can download the 32bit GST SDK runtime at http://www.gstreamer.com/
111 | echo.
112 | pause && goto end
113 |
114 | :badgst
115 | echo GStreamer Python Bindings : not found
116 | echo.
117 | echo The python bindings for GStreamer could not be imported. Please re-run the
118 | echo installer and ensure that the python bindings are selected for
119 | echo installation (they should be selected by default).
120 | echo.
121 | echo You can download the 32bit GST SDK runtime at http://www.gstreamer.com/
122 | echo.
123 | pause && goto end
124 |
125 | :badgtk
126 | echo.
127 | echo PyGTK 2.x could not be imported. It is installed by default with the
128 | echo GStreamer SDK (select GTK Python Bindings), or you can use the
129 | echo PyGTK all-in-one installer from http://www.pygtk.org/
130 | echo.
131 | echo Note that the PyGTK all-in-one installer is NOT compatible with
132 | echo the GStreamer SDK. Please Uninstall it.
133 | echo.
134 | echo You can download the 32bit GST SDK runtime at http://www.gstreamer.com/
135 | echo.
136 | pause && goto end
137 |
138 |
139 | :start_pithos
140 | pushd %~dp0
141 | start %PYTHON_BIN% pithos.pyw
142 | popd
143 | goto end
144 |
145 | :end
146 | endlocal
147 |
--------------------------------------------------------------------------------
/pithos/AboutPithosDialog.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
2 | ### BEGIN LICENSE
3 | # Copyright (C) 2010-2012 Kevin Mehall
4 | #This program is free software: you can redistribute it and/or modify it
5 | #under the terms of the GNU General Public License version 3, as published
6 | #by the Free Software Foundation.
7 | #
8 | #This program is distributed in the hope that it will be useful, but
9 | #WITHOUT ANY WARRANTY; without even the implied warranties of
10 | #MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 | #PURPOSE. See the GNU General Public License for more details.
12 | #
13 | #You should have received a copy of the GNU General Public License along
14 | #with this program. If not, see .
15 | ### END LICENSE
16 |
17 | import sys
18 | import os
19 | import gtk
20 |
21 | from pithos.pithosconfig import getdatapath
22 |
23 | class AboutPithosDialog(gtk.AboutDialog):
24 | __gtype_name__ = "AboutPithosDialog"
25 |
26 | def __init__(self):
27 | """__init__ - This function is typically not called directly.
28 | Creation of a AboutPithosDialog requires redeading the associated ui
29 | file and parsing the ui definition extrenally,
30 | and then calling AboutPithosDialog.finish_initializing().
31 |
32 | Use the convenience function NewAboutPithosDialog to create
33 | NewAboutPithosDialog objects.
34 |
35 | """
36 | pass
37 |
38 | def finish_initializing(self, builder):
39 | """finish_initalizing should be called after parsing the ui definition
40 | and creating a AboutPithosDialog object with it in order to finish
41 | initializing the start of the new AboutPithosDialog instance.
42 |
43 | """
44 | #get a reference to the builder and set up the signals
45 | self.builder = builder
46 | self.builder.connect_signals(self)
47 |
48 | #code for other initialization actions should be added here
49 |
50 | def NewAboutPithosDialog():
51 | """NewAboutPithosDialog - returns a fully instantiated
52 | AboutPithosDialog object. Use this function rather than
53 | creating a AboutPithosDialog instance directly.
54 |
55 | """
56 |
57 | #look for the ui file that describes the ui
58 | ui_filename = os.path.join(getdatapath(), 'ui', 'AboutPithosDialog.ui')
59 | if not os.path.exists(ui_filename):
60 | ui_filename = None
61 |
62 | builder = gtk.Builder()
63 | builder.add_from_file(ui_filename)
64 | dialog = builder.get_object("about_pithos_dialog")
65 | dialog.finish_initializing(builder)
66 | return dialog
67 |
68 | if __name__ == "__main__":
69 | dialog = NewAboutPithosDialog()
70 | dialog.show()
71 | gtk.main()
72 |
73 |
--------------------------------------------------------------------------------
/pithos/PreferencesPithosDialog.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
2 | ### BEGIN LICENSE
3 | # Copyright (C) 2010 Kevin Mehall
4 | #This program is free software: you can redistribute it and/or modify it
5 | #under the terms of the GNU General Public License version 3, as published
6 | #by the Free Software Foundation.
7 | #
8 | #This program is distributed in the hope that it will be useful, but
9 | #WITHOUT ANY WARRANTY; without even the implied warranties of
10 | #MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 | #PURPOSE. See the GNU General Public License for more details.
12 | #
13 | #You should have received a copy of the GNU General Public License along
14 | #with this program. If not, see .
15 | ### END LICENSE
16 |
17 | import sys
18 | import os
19 | import stat
20 | import logging
21 |
22 | import gtk
23 | import gobject
24 |
25 | from pithos.pithosconfig import getdatapath, valid_audio_formats
26 | from pithos.plugins.scrobble import LastFmAuth
27 |
28 | configfilename = os.path.join(os.environ['appdata'], 'Pithos\\pithos.ini')
29 |
30 |
31 | class PreferencesPithosDialog(gtk.Dialog):
32 | __gtype_name__ = "PreferencesPithosDialog"
33 | prefernces = {}
34 |
35 | def __init__(self):
36 | """__init__ - This function is typically not called directly.
37 | Creation of a PreferencesPithosDialog requires redeading the associated ui
38 | file and parsing the ui definition extrenally,
39 | and then calling PreferencesPithosDialog.finish_initializing().
40 |
41 | Use the convenience function NewPreferencesPithosDialog to create
42 | NewAboutPithosDialog objects.
43 | """
44 |
45 | pass
46 |
47 | def finish_initializing(self, builder):
48 | """finish_initalizing should be called after parsing the ui definition
49 | and creating a AboutPithosDialog object with it in order to finish
50 | initializing the start of the new AboutPithosDialog instance.
51 | """
52 |
53 | # get a reference to the builder and set up the signals
54 | self.builder = builder
55 | self.builder.connect_signals(self)
56 |
57 | # initialize the "Audio format" combobox backing list
58 | audio_quality_combo = self.builder.get_object('prefs_audio_quality')
59 | fmt_store = gtk.ListStore(gobject.TYPE_STRING)
60 | for audio_format, quality in valid_audio_formats:
61 | fmt_store.append((quality,))
62 | audio_quality_combo.set_model(fmt_store)
63 | render_text = gtk.CellRendererText()
64 | audio_quality_combo.pack_start(render_text, expand=True)
65 | audio_quality_combo.add_attribute(render_text, "text", 0)
66 |
67 | self.__load_preferences()
68 |
69 |
70 | def get_preferences(self):
71 | """get_preferences - returns a dictionary object that contains
72 | preferences for pithos.
73 | """
74 | return self.__preferences
75 |
76 | def __load_preferences(self):
77 | #default preferences that will be overwritten if some are saved
78 | self.__preferences = {
79 | "username":'',
80 | "password":'',
81 | "pandora_one":False,
82 | "growl":False,
83 | "last_station_id":None,
84 | "proxy":'',
85 | "show_icon": False,
86 | "lastfm_key": False,
87 | "mediakeys": False,
88 | "volume": 0.5,
89 | "audio_quality": valid_audio_formats[0][0],
90 | }
91 |
92 | try:
93 | f = open(configfilename)
94 | except IOError:
95 | f = []
96 |
97 | for line in f:
98 | sep = line.find('=')
99 | key = line[:sep]
100 | if key in self.__preferences:
101 | val = line[sep+1:].strip()
102 | if val == 'None': val=None
103 | elif val == 'False': val=False
104 | elif val == 'True': val=True
105 | self.__preferences[key]=val
106 | self.setup_fields()
107 |
108 | def save(self):
109 | existed = os.path.exists(configfilename)
110 | try:
111 | f = open(configfilename, 'w')
112 | except IOError:
113 | pass
114 |
115 | if not existed:
116 | os.makedirs(configfilename[:-10])
117 | f = open(configfilename, 'w')
118 |
119 | for key in self.__preferences:
120 | f.write('%s=%s\n'%(key, self.__preferences[key]))
121 | f.close()
122 |
123 | def setup_fields(self):
124 | self.builder.get_object('prefs_username').set_text(self.__preferences["username"])
125 | self.builder.get_object('prefs_password').set_text(self.__preferences["password"])
126 | self.builder.get_object('checkbutton_pandora_one').set_active(self.__preferences["pandora_one"])
127 | self.builder.get_object('prefs_proxy').set_text(self.__preferences["proxy"])
128 |
129 | audio_quality_combo = self.builder.get_object('prefs_audio_quality')
130 | try:
131 | audio_pref_idx = list(audio_format for audio_format, quality in valid_audio_formats).index(self.__preferences["audio_quality"])
132 | except ValueError:
133 | audio_pref_idx = 0
134 | audio_quality_combo.set_active(audio_pref_idx)
135 |
136 | self.builder.get_object('checkbutton_icon').set_active(self.__preferences["show_icon"])
137 | self.builder.get_object('checkbutton_growl').set_active(self.__preferences["growl"])
138 | self.builder.get_object('checkbutton_mediakeys').set_active(self.__preferences["mediakeys"])
139 |
140 | self.lastfm_auth = LastFmAuth(self.__preferences, "lastfm_key", self.builder.get_object('lastfm_btn'))
141 |
142 | def ok(self, widget, data=None):
143 | """ok - The user has elected to save the changes.
144 | Called before the dialog returns gtk.RESONSE_OK from run().
145 | """
146 |
147 | self.__preferences["username"] = self.builder.get_object('prefs_username').get_text()
148 | self.__preferences["password"] = self.builder.get_object('prefs_password').get_text()
149 | self.__preferences["pandora_one"] = self.builder.get_object('checkbutton_pandora_one').get_active()
150 | self.__preferences["growl"] = self.builder.get_object('checkbutton_growl').get_active()
151 | self.__preferences["mediakeys"] = self.builder.get_object('checkbutton_mediakeys').get_active()
152 | self.__preferences["proxy"] = self.builder.get_object('prefs_proxy').get_text()
153 | self.__preferences["audio_quality"] = valid_audio_formats[self.builder.get_object('prefs_audio_quality').get_active()][0]
154 | self.__preferences["show_icon"] = self.builder.get_object('checkbutton_icon').get_active()
155 |
156 | self.save()
157 |
158 | def cancel(self, widget, data=None):
159 | """cancel - The user has elected cancel changes.
160 | Called before the dialog returns gtk.RESPONSE_CANCEL for run()
161 | """
162 |
163 | self.setup_fields() # restore fields to previous values
164 | pass
165 |
166 |
167 | def NewPreferencesPithosDialog():
168 | """NewPreferencesPithosDialog - returns a fully instantiated
169 | PreferencesPithosDialog object. Use this function rather than
170 | creating a PreferencesPithosDialog instance directly.
171 | """
172 |
173 | #look for the ui file that describes the ui
174 | ui_filename = os.path.join(getdatapath(), 'ui', 'PreferencesPithosDialog.ui')
175 | if not os.path.exists(ui_filename):
176 | ui_filename = None
177 |
178 | builder = gtk.Builder()
179 | builder.add_from_file(ui_filename)
180 | dialog = builder.get_object("preferences_pithos_dialog")
181 | dialog.finish_initializing(builder)
182 | return dialog
183 |
184 | if __name__ == "__main__":
185 | dialog = NewPreferencesPithosDialog()
186 | dialog.show()
187 | gtk.main()
188 |
189 |
--------------------------------------------------------------------------------
/pithos/SearchDialog.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
2 | ### BEGIN LICENSE
3 | # Copyright (C) 2010-2012 Kevin Mehall
4 | #This program is free software: you can redistribute it and/or modify it
5 | #under the terms of the GNU General Public License version 3, as published
6 | #by the Free Software Foundation.
7 | #
8 | #This program is distributed in the hope that it will be useful, but
9 | #WITHOUT ANY WARRANTY; without even the implied warranties of
10 | #MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 | #PURPOSE. See the GNU General Public License for more details.
12 | #
13 | #You should have received a copy of the GNU General Public License along
14 | #with this program. If not, see .
15 | ### END LICENSE
16 |
17 | import sys
18 | import os
19 | import gtk, gobject
20 | import cgi
21 |
22 | from pithos.pithosconfig import getdatapath
23 |
24 | class SearchDialog(gtk.Dialog):
25 | __gtype_name__ = "SearchDialog"
26 |
27 | def __init__(self):
28 | """__init__ - This function is typically not called directly.
29 | Creation of a SearchDialog requires redeading the associated ui
30 | file and parsing the ui definition extrenally,
31 | and then calling SearchDialog.finish_initializing().
32 |
33 | Use the convenience function NewSearchDialog to create
34 | a SearchDialog object.
35 |
36 | """
37 | pass
38 |
39 | def finish_initializing(self, builder, worker_run):
40 | """finish_initalizing should be called after parsing the ui definition
41 | and creating a SearchDialog object with it in order to finish
42 | initializing the start of the new SearchDialog instance.
43 |
44 | """
45 | #get a reference to the builder and set up the signals
46 | self.builder = builder
47 | self.builder.connect_signals(self)
48 |
49 | self.entry = self.builder.get_object('entry')
50 | self.treeview = self.builder.get_object('treeview')
51 | self.okbtn = self.builder.get_object('okbtn')
52 | self.searchbtn = self.builder.get_object('searchbtn')
53 | self.model = gtk.ListStore(gobject.TYPE_PYOBJECT, str)
54 | self.treeview.set_model(self.model)
55 |
56 | self.worker_run = worker_run
57 |
58 | self.result = None
59 |
60 |
61 | def ok(self, widget, data=None):
62 | """ok - The user has elected to save the changes.
63 | Called before the dialog returns gtk.RESONSE_OK from run().
64 |
65 | """
66 |
67 |
68 | def cancel(self, widget, data=None):
69 | """cancel - The user has elected cancel changes.
70 | Called before the dialog returns gtk.RESPONSE_CANCEL for run()
71 |
72 | """
73 | pass
74 |
75 | def search_clicked(self, widget):
76 | self.search(self.entry.get_text())
77 |
78 | def search(self, query):
79 | if not query: return
80 | def callback(results):
81 | self.model.clear()
82 | for i in results:
83 | if i.resultType is 'song':
84 | mk = "%s by %s"%(cgi.escape(i.title), cgi.escape(i.artist))
85 | elif i.resultType is 'artist':
86 | mk = "%s (artist)"%(cgi.escape(i.name))
87 | self.model.append((i, mk))
88 | self.treeview.show()
89 | self.searchbtn.set_sensitive(True)
90 | self.searchbtn.set_label("Search")
91 | self.worker_run('search', (query,), callback, "Searching...")
92 | self.searchbtn.set_sensitive(False)
93 | self.searchbtn.set_label("Searching...")
94 |
95 | def get_selected(self):
96 | sel = self.treeview.get_selection().get_selected()
97 | if sel:
98 | return self.treeview.get_model().get_value(sel[1], 0)
99 |
100 | def cursor_changed(self, *ignore):
101 | self.result = self.get_selected()
102 | self.okbtn.set_sensitive(not not self.result)
103 |
104 |
105 | def NewSearchDialog(worker_run):
106 | """NewSearchDialog - returns a fully instantiated
107 | dialog-camel_case_nameDialog object. Use this function rather than
108 | creating SearchDialog instance directly.
109 |
110 | """
111 |
112 | #look for the ui file that describes the ui
113 | ui_filename = os.path.join(getdatapath(), 'ui', 'SearchDialog.ui')
114 | if not os.path.exists(ui_filename):
115 | ui_filename = None
116 |
117 | builder = gtk.Builder()
118 | builder.add_from_file(ui_filename)
119 | dialog = builder.get_object("search_dialog")
120 | dialog.finish_initializing(builder, worker_run)
121 | return dialog
122 |
123 | if __name__ == "__main__":
124 | dialog = NewSearchDialog()
125 | dialog.show()
126 | gtk.main()
127 |
128 |
--------------------------------------------------------------------------------
/pithos/StationsDialog.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
2 | ### BEGIN LICENSE
3 | # Copyright (C) 2010-2012 Kevin Mehall
4 | #This program is free software: you can redistribute it and/or modify it
5 | #under the terms of the GNU General Public License version 3, as published
6 | #by the Free Software Foundation.
7 | #
8 | #This program is distributed in the hope that it will be useful, but
9 | #WITHOUT ANY WARRANTY; without even the implied warranties of
10 | #MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 | #PURPOSE. See the GNU General Public License for more details.
12 | #
13 | #You should have received a copy of the GNU General Public License along
14 | #with this program. If not, see .
15 | ### END LICENSE
16 |
17 | import sys
18 | import os
19 | import gtk
20 | import logging
21 | import webbrowser
22 |
23 | from pithos.pithosconfig import getdatapath
24 | from pithos import SearchDialog
25 |
26 | class StationsDialog(gtk.Dialog):
27 | __gtype_name__ = "StationsDialog"
28 |
29 | def __init__(self):
30 | """__init__ - This function is typically not called directly.
31 | Creation of a StationsDialog requires redeading the associated ui
32 | file and parsing the ui definition extrenally,
33 | and then calling StationsDialog.finish_initializing().
34 |
35 | Use the convenience function NewStationsDialog to create
36 | a StationsDialog object.
37 |
38 | """
39 | pass
40 |
41 | def finish_initializing(self, builder, pithos):
42 | """finish_initalizing should be called after parsing the ui definition
43 | and creating a StationsDialog object with it in order to finish
44 | initializing the start of the new StationsDialog instance.
45 |
46 | """
47 | #get a reference to the builder and set up the signals
48 | self.builder = builder
49 | self.builder.connect_signals(self)
50 |
51 | self.pithos = pithos
52 | self.model = pithos.stations_model
53 | self.worker_run = pithos.worker_run
54 | self.quickmix_changed = False
55 | self.searchDialog = None
56 |
57 | self.modelfilter = self.model.filter_new()
58 | self.modelfilter.set_visible_func(lambda m, i: m.get_value(i, 0) and not m.get_value(i, 0).isQuickMix)
59 |
60 | self.modelsortable = gtk.TreeModelSort(self.modelfilter)
61 | """
62 | @todo Leaving it as sorting by date added by default.
63 | Probably should make a radio select in the window or an option in program options for user preference
64 | """
65 | # self.modelsortable.set_sort_column_id(1, gtk.SORT_ASCENDING)
66 |
67 | self.treeview = self.builder.get_object("treeview")
68 | self.treeview.set_model(self.modelsortable)
69 | self.treeview.connect('button_press_event', self.on_treeview_button_press_event)
70 |
71 | name_col = gtk.TreeViewColumn()
72 | name_col.set_title("Name")
73 | render_text = gtk.CellRendererText()
74 | render_text.set_property('editable', True)
75 | render_text.connect("edited", self.station_renamed)
76 | name_col.pack_start(render_text, expand=True)
77 | name_col.add_attribute(render_text, "text", 1)
78 | name_col.set_expand(True)
79 | name_col.set_sort_column_id(1)
80 | self.treeview.append_column(name_col)
81 |
82 | qm_col = gtk.TreeViewColumn()
83 | qm_col.set_title("In QuickMix")
84 | render_toggle = gtk.CellRendererToggle()
85 | qm_col.pack_start(render_toggle, expand=True)
86 | def qm_datafunc(column, cell, model, iter):
87 | if model.get_value(iter,0).useQuickMix:
88 | cell.set_active(True)
89 | else:
90 | cell.set_active(False)
91 | qm_col.set_cell_data_func(render_toggle, qm_datafunc)
92 | render_toggle.connect("toggled", self.qm_toggled)
93 | self.treeview.append_column(qm_col)
94 |
95 | self.station_menu = builder.get_object("station_menu")
96 |
97 | def qm_toggled(self, renderer, path):
98 | station = self.modelfilter[path][0]
99 | station.useQuickMix = not station.useQuickMix
100 | self.quickmix_changed = True
101 |
102 | def station_renamed(self, cellrenderertext, path, new_text):
103 | station = self.modelfilter[path][0]
104 | self.worker_run(station.rename, (new_text,), context='net', message="Renaming Station...")
105 | self.model[self.modelfilter.convert_path_to_child_path(path)][1] = new_text
106 |
107 | def selected_station(self):
108 | sel = self.treeview.get_selection().get_selected()
109 | if sel:
110 | return self.treeview.get_model().get_value(sel[1], 0)
111 |
112 | def on_treeview_button_press_event(self, treeview, event):
113 | if event.button == 3:
114 | x = int(event.x)
115 | y = int(event.y)
116 | time = event.time
117 | pthinfo = treeview.get_path_at_pos(x, y)
118 | if pthinfo is not None:
119 | path, col, cellx, celly = pthinfo
120 | treeview.grab_focus()
121 | treeview.set_cursor( path, col, 0)
122 | self.station_menu.popup( None, None, None, event.button, time)
123 | return True
124 |
125 | def on_menuitem_listen(self, widget):
126 | station = self.selected_station()
127 | self.pithos.station_changed(station)
128 | self.hide()
129 |
130 | def on_menuitem_info(self, widget):
131 | webbrowser.open(self.selected_station().info_url)
132 |
133 | def on_menuitem_rename(self, widget):
134 | sel = self.treeview.get_selection().get_selected()
135 | path = self.treeview.get_model().get_path(sel[1])
136 | self.treeview.set_cursor(path, self.treeview.get_column(0) ,True)
137 |
138 | def on_menuitem_delete(self, widget):
139 | station = self.selected_station()
140 |
141 | dialog = self.builder.get_object("delete_confirm_dialog")
142 | dialog.set_property("text", "Are you sure you want to delete the station \"%s\"?"%(station.name))
143 | response = dialog.run()
144 | dialog.hide()
145 |
146 | if response:
147 | self.worker_run(station.delete, context='net', message="Deleting Station...")
148 | del self.pithos.stations_model[self.pithos.station_index(station)]
149 | if self.pithos.current_station is station:
150 | self.pithos.station_changed(self.model[0][0])
151 |
152 | def add_station(self, widget):
153 | if self.searchDialog:
154 | self.searchDialog.present()
155 | else:
156 | self.searchDialog = SearchDialog.NewSearchDialog(self.worker_run)
157 | self.searchDialog.show_all()
158 | self.searchDialog.connect("response", self.add_station_cb)
159 |
160 | def refresh_stations(self, widget):
161 | self.pithos.refresh_stations(self.pithos)
162 |
163 | def add_station_cb(self, dialog, response):
164 | print "in add_station_cb", dialog.result, response
165 | if response == 1:
166 | self.worker_run("add_station_by_music_id", (dialog.result.musicId,), self.station_added, "Creating station...")
167 | dialog.hide()
168 | dialog.destroy()
169 | self.searchDialog = None
170 |
171 | def station_added(self, station):
172 | logging.debug("1 "+ repr(station))
173 | it = self.model.insert_after(self.model.get_iter(1), (station, station.name))
174 | logging.debug("2 "+ repr(it))
175 | self.pithos.station_changed(station)
176 | logging.debug("3 ")
177 | self.modelfilter.refilter()
178 | logging.debug("4")
179 | self.treeview.set_cursor(0)
180 | logging.debug("5 ")
181 |
182 | def add_genre_station(self, widget):
183 | """
184 | This is just a stub for the non-completed buttn
185 | """
186 |
187 | def on_close(self, widget, data=None):
188 | self.hide()
189 |
190 | if self.quickmix_changed:
191 | self.worker_run("save_quick_mix", message="Saving QuickMix...")
192 | self.quickmix_changed = False
193 |
194 | logging.info("closed dialog")
195 | return True
196 |
197 | def NewStationsDialog(pithos):
198 | """NewStationsDialog - returns a fully instantiated
199 | Dialog object. Use this function rather than
200 | creating StationsDialog instance directly.
201 |
202 | """
203 |
204 | #look for the ui file that describes the ui
205 | ui_filename = os.path.join(getdatapath(), 'ui', 'StationsDialog.ui')
206 | if not os.path.exists(ui_filename):
207 | ui_filename = None
208 |
209 | builder = gtk.Builder()
210 | builder.add_from_file(ui_filename)
211 | dialog = builder.get_object("stations_dialog")
212 | dialog.finish_initializing(builder, pithos)
213 | return dialog
214 |
215 | if __name__ == "__main__":
216 | dialog = NewStationsDialog()
217 | dialog.show()
218 | gtk.main()
219 |
220 |
--------------------------------------------------------------------------------
/pithos/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TingPing/pithos-for-windows/02a93e76ff6b616e14f10dfc04d1532bd7d98940/pithos/__init__.py
--------------------------------------------------------------------------------
/pithos/gobject_worker.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
2 | ### BEGIN LICENSE
3 | # Copyright (C) 2010-2012 Kevin Mehall
4 | #This program is free software: you can redistribute it and/or modify it
5 | #under the terms of the GNU General Public License version 3, as published
6 | #by the Free Software Foundation.
7 | #
8 | #This program is distributed in the hope that it will be useful, but
9 | #WITHOUT ANY WARRANTY; without even the implied warranties of
10 | #MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 | #PURPOSE. See the GNU General Public License for more details.
12 | #
13 | #You should have received a copy of the GNU General Public License along
14 | #with this program. If not, see .
15 | ### END LICENSE
16 |
17 | import threading
18 | import Queue
19 | import gobject
20 | import traceback
21 | gobject.threads_init()
22 |
23 | class GObjectWorker():
24 | def __init__(self):
25 | self.thread = threading.Thread(target=self._run)
26 | self.thread.daemon = True
27 | self.queue = Queue.Queue()
28 | self.thread.start()
29 |
30 | def _run(self):
31 | while True:
32 | command, args, callback, errorback = self.queue.get()
33 | try:
34 | result = command(*args)
35 | if callback:
36 | gobject.idle_add(callback, result)
37 | except Exception, e:
38 | e.traceback = traceback.format_exc()
39 | if errorback:
40 | gobject.idle_add(errorback, e)
41 |
42 | def send(self, command, args=(), callback=None, errorback=None):
43 | if errorback is None: errorback = self._default_errorback
44 | self.queue.put((command, args, callback, errorback))
45 |
46 | def _default_errorback(self, error):
47 | print "Unhandled exception in worker thread:\n", error.traceback
48 |
49 | if __name__ == '__main__':
50 | worker = GObjectWorker()
51 | import time, gtk
52 |
53 | def test_cmd(a, b):
54 | print "running..."
55 | time.sleep(5)
56 | print "done"
57 | return a*b
58 |
59 | def test_cb(result):
60 | print "got result", result
61 |
62 | print "sending"
63 | worker.send(test_cmd, (3,4), test_cb)
64 | worker.send(test_cmd, ((), ()), test_cb) #trigger exception in worker to test error handling
65 |
66 | gtk.main()
67 |
68 |
69 |
--------------------------------------------------------------------------------
/pithos/pandora/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
2 | ### BEGIN LICENSE
3 | # Copyright (C) 2010 Kevin Mehall
4 | #This program is free software: you can redistribute it and/or modify it
5 | #under the terms of the GNU General Public License version 3, as published
6 | #by the Free Software Foundation.
7 | #
8 | #This program is distributed in the hope that it will be useful, but
9 | #WITHOUT ANY WARRANTY; without even the implied warranties of
10 | #MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 | #PURPOSE. See the GNU General Public License for more details.
12 | #
13 | #You should have received a copy of the GNU General Public License along
14 | #with this program. If not, see .
15 | ### END LICENSE
16 |
17 | from pithos.pandora.pandora import *
18 |
19 | def make_pandora(testing=False):
20 | if testing:
21 | from pithos.pandora.fake import FakePandora
22 | return FakePandora()
23 | else:
24 | return Pandora()
25 |
--------------------------------------------------------------------------------
/pithos/pandora/blowfish.py:
--------------------------------------------------------------------------------
1 | #
2 | # blowfish.py
3 | # Copyright (C) 2002 Michael Gilfix
4 | #
5 | # This module is open source; you can redistribute it and/or
6 | # modify it under the terms of the GPL or Artistic License.
7 | # These licenses are available at http://www.opensource.org
8 | #
9 | # This software must be used and distributed in accordance
10 | # with the law. The author claims no liability for its
11 | # misuse.
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.
16 | #
17 |
18 | """
19 | Blowfish Encryption
20 |
21 | This module is a pure python implementation of Bruce Schneier's
22 | encryption scheme 'Blowfish'. Blowish is a 16-round Feistel Network
23 | cipher and offers substantial speed gains over DES.
24 |
25 | The key is a string of length anywhere between 64 and 448 bits, or
26 | equivalently 8 and 56 bytes. The encryption and decryption functions operate
27 | on 64-bit blocks, or 8 byte strings.
28 |
29 | Send questions, comments, bugs my way:
30 | Michael Gilfix
31 | """
32 |
33 | __author__ = "Michael Gilfix "
34 |
35 | class Blowfish:
36 |
37 | """Blowfish encryption Scheme
38 |
39 | This class implements the encryption and decryption
40 | functionality of the Blowfish cipher.
41 |
42 | Public functions:
43 |
44 | def __init__ (self, key)
45 | Creates an instance of blowfish using 'key'
46 | as the encryption key. Key is a string of
47 | length ranging from 8 to 56 bytes (64 to 448
48 | bits). Once the instance of the object is
49 | created, the key is no longer necessary.
50 |
51 | def encrypt (self, data):
52 | Encrypt an 8 byte (64-bit) block of text
53 | where 'data' is an 8 byte string. Returns an
54 | 8-byte encrypted string.
55 |
56 | def decrypt (self, data):
57 | Decrypt an 8 byte (64-bit) encrypted block
58 | of text, where 'data' is the 8 byte encrypted
59 | string. Returns an 8-byte string of plaintext.
60 |
61 | def cipher (self, xl, xr, direction):
62 | Encrypts a 64-bit block of data where xl is
63 | the upper 32-bits and xr is the lower 32-bits.
64 | 'direction' is the direction to apply the
65 | cipher, either ENCRYPT or DECRYPT constants.
66 | returns a tuple of either encrypted or decrypted
67 | data of the left half and right half of the
68 | 64-bit block.
69 |
70 | Private members:
71 |
72 | def __round_func (self, xl)
73 | Performs an obscuring function on the 32-bit
74 | block of data 'xl', which is the left half of
75 | the 64-bit block of data. Returns the 32-bit
76 | result as a long integer.
77 |
78 | """
79 |
80 | # Cipher directions
81 | ENCRYPT = 0
82 | DECRYPT = 1
83 |
84 | # For the __round_func
85 | modulus = long (2) ** 32
86 |
87 | def __init__ (self, key):
88 |
89 | if not key or len (key) < 8 or len (key) > 56:
90 | raise RuntimeError, "Attempted to initialize Blowfish cipher with key of invalid length: %s" %len (key)
91 |
92 | self.p_boxes = [
93 | 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344,
94 | 0xA4093822, 0x299F31D0, 0x082EFA98, 0xEC4E6C89,
95 | 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C,
96 | 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917,
97 | 0x9216D5D9, 0x8979FB1B
98 | ]
99 |
100 | self.s_boxes = [
101 | [
102 | 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7,
103 | 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99,
104 | 0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16,
105 | 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E,
106 | 0x0D95748F, 0x728EB658, 0x718BCD58, 0x82154AEE,
107 | 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013,
108 | 0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF,
109 | 0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E,
110 | 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60,
111 | 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440,
112 | 0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE,
113 | 0xA15486AF, 0x7C72E993, 0xB3EE1411, 0x636FBC2A,
114 | 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E,
115 | 0xAFD6BA33, 0x6C24CF5C, 0x7A325381, 0x28958677,
116 | 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193,
117 | 0x61D809CC, 0xFB21A991, 0x487CAC60, 0x5DEC8032,
118 | 0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88,
119 | 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239,
120 | 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E,
121 | 0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0,
122 | 0x6A51A0D2, 0xD8542F68, 0x960FA728, 0xAB5133A3,
123 | 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98,
124 | 0xA1F1651D, 0x39AF0176, 0x66CA593E, 0x82430E88,
125 | 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE,
126 | 0xE06F75D8, 0x85C12073, 0x401A449F, 0x56C16AA6,
127 | 0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D,
128 | 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B,
129 | 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7,
130 | 0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA,
131 | 0xC1A94FB6, 0x409F60C4, 0x5E5C9EC2, 0x196A2463,
132 | 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F,
133 | 0x6DFC511F, 0x9B30952C, 0xCC814544, 0xAF5EBD09,
134 | 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3,
135 | 0xC0CBA857, 0x45C8740F, 0xD20B5F39, 0xB9D3FBDB,
136 | 0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x402C7279,
137 | 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8,
138 | 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB,
139 | 0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82,
140 | 0x9E5C57BB, 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB,
141 | 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573,
142 | 0x695B27B0, 0xBBCA58C8, 0xE1FFA35D, 0xB8F011A0,
143 | 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B,
144 | 0x9A53E479, 0xB6F84565, 0xD28E49BC, 0x4BFB9790,
145 | 0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8,
146 | 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4,
147 | 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0,
148 | 0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7,
149 | 0x8FF6E2FB, 0xF2122B64, 0x8888B812, 0x900DF01C,
150 | 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD,
151 | 0x2F2F2218, 0xBE0E1777, 0xEA752DFE, 0x8B021FA1,
152 | 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299,
153 | 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, 0xD2ADA8D9,
154 | 0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477,
155 | 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF,
156 | 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49,
157 | 0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF,
158 | 0x2464369B, 0xF009B91E, 0x5563911D, 0x59DFA6AA,
159 | 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5,
160 | 0x83260376, 0x6295CFA9, 0x11C81968, 0x4E734A41,
161 | 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915,
162 | 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, 0x81E67400,
163 | 0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915,
164 | 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664,
165 | 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A
166 | ],
167 | [
168 | 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623,
169 | 0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266,
170 | 0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1,
171 | 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E,
172 | 0x3F54989A, 0x5B429D65, 0x6B8FE4D6, 0x99F73FD6,
173 | 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1,
174 | 0x4CDD2086, 0x8470EB26, 0x6382E9C6, 0x021ECC5E,
175 | 0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1,
176 | 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737,
177 | 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8,
178 | 0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF,
179 | 0xAE0CF51A, 0x3CB574B2, 0x25837A58, 0xDC0921BD,
180 | 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701,
181 | 0x3AE5E581, 0x37C2DADC, 0xC8B57634, 0x9AF3DDA7,
182 | 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41,
183 | 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, 0x183EB331,
184 | 0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF,
185 | 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF,
186 | 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E,
187 | 0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87,
188 | 0x7A584718, 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C,
189 | 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2,
190 | 0xEF1C1847, 0x3215D908, 0xDD433B37, 0x24C2BA16,
191 | 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD,
192 | 0x71DFF89E, 0x10314E55, 0x81AC77D6, 0x5F11199B,
193 | 0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509,
194 | 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E,
195 | 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3,
196 | 0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F,
197 | 0x803E89D6, 0x5266C825, 0x2E4CC978, 0x9C10B36A,
198 | 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4,
199 | 0xF2F74EA7, 0x361D2B3D, 0x1939260F, 0x19C27960,
200 | 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66,
201 | 0xE3BC4595, 0xA67BC883, 0xB17F37D1, 0x018CFF28,
202 | 0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802,
203 | 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84,
204 | 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510,
205 | 0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF,
206 | 0xB5735C90, 0x4C70A239, 0xD59E9E0B, 0xCBAADE14,
207 | 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E,
208 | 0x648B1EAF, 0x19BDF0CA, 0xA02369B9, 0x655ABB50,
209 | 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7,
210 | 0x9B540B19, 0x875FA099, 0x95F7997E, 0x623D7DA8,
211 | 0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281,
212 | 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99,
213 | 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696,
214 | 0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128,
215 | 0x58EBF2EF, 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73,
216 | 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0,
217 | 0x45EEE2B6, 0xA3AAABEA, 0xDB6C4F15, 0xFACB4FD0,
218 | 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105,
219 | 0xD81E799E, 0x86854DC7, 0xE44B476A, 0x3D816250,
220 | 0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3,
221 | 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285,
222 | 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00,
223 | 0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061,
224 | 0x3372F092, 0x8D937E41, 0xD65FECF1, 0x6C223BDB,
225 | 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E,
226 | 0xA6078084, 0x19F8509E, 0xE8EFD855, 0x61D99735,
227 | 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC,
228 | 0x9E447A2E, 0xC3453484, 0xFDD56705, 0x0E1E9EC9,
229 | 0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340,
230 | 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20,
231 | 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7
232 | ],
233 | [
234 | 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934,
235 | 0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068,
236 | 0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF,
237 | 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840,
238 | 0x4D95FC1D, 0x96B591AF, 0x70F4DDD3, 0x66A02F45,
239 | 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504,
240 | 0x96EB27B3, 0x55FD3941, 0xDA2547E6, 0xABCA0A9A,
241 | 0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB,
242 | 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE,
243 | 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6,
244 | 0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42,
245 | 0x20FE9E35, 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B,
246 | 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2,
247 | 0x3A6EFA74, 0xDD5B4332, 0x6841E7F7, 0xCA7820FB,
248 | 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527,
249 | 0x55533A3A, 0x20838D87, 0xFE6BA9B7, 0xD096954B,
250 | 0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33,
251 | 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C,
252 | 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3,
253 | 0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC,
254 | 0x07F9C9EE, 0x41041F0F, 0x404779A4, 0x5D886E17,
255 | 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564,
256 | 0x257B7834, 0x602A9C60, 0xDFF8E8A3, 0x1F636C1B,
257 | 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115,
258 | 0x6B2395E0, 0x333E92E1, 0x3B240B62, 0xEEBEB922,
259 | 0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728,
260 | 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0,
261 | 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E,
262 | 0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37,
263 | 0xA812DC60, 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D,
264 | 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804,
265 | 0xF1290DC7, 0xCC00FFA3, 0xB5390F92, 0x690FED0B,
266 | 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3,
267 | 0xBB132F88, 0x515BAD24, 0x7B9479BF, 0x763BD6EB,
268 | 0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D,
269 | 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C,
270 | 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350,
271 | 0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9,
272 | 0x44421659, 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A,
273 | 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE,
274 | 0x9DBC8057, 0xF0F7C086, 0x60787BF8, 0x6003604D,
275 | 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC,
276 | 0x83426B33, 0xF01EAB71, 0xB0804187, 0x3C005E5F,
277 | 0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61,
278 | 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2,
279 | 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9,
280 | 0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2,
281 | 0x466E598E, 0x20B45770, 0x8CD55591, 0xC902DE4C,
282 | 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E,
283 | 0xB77F19B6, 0xE0A9DC09, 0x662D09A1, 0xC4324633,
284 | 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10,
285 | 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, 0x2868F169,
286 | 0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52,
287 | 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027,
288 | 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5,
289 | 0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62,
290 | 0x11E69ED7, 0x2338EA63, 0x53C2DD94, 0xC2C21634,
291 | 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76,
292 | 0x6F05E409, 0x4B7C0188, 0x39720A3D, 0x7C927C24,
293 | 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC,
294 | 0xED545578, 0x08FCA5B5, 0xD83D7CD3, 0x4DAD0FC4,
295 | 0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C,
296 | 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837,
297 | 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0
298 | ],
299 | [
300 | 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B,
301 | 0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE,
302 | 0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B,
303 | 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4,
304 | 0x5748AB2F, 0xBC946E79, 0xC6A376D2, 0x6549C2C8,
305 | 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6,
306 | 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, 0xBE5EE304,
307 | 0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22,
308 | 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4,
309 | 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6,
310 | 0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9,
311 | 0xC72FEFD3, 0xF752F7DA, 0x3F046F69, 0x77FA0A59,
312 | 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593,
313 | 0xE990FD5A, 0x9E34D797, 0x2CF0B7D9, 0x022B8B51,
314 | 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28,
315 | 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, 0x5A88F54C,
316 | 0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B,
317 | 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28,
318 | 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C,
319 | 0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD,
320 | 0xC3EB9E15, 0x3C9057A2, 0x97271AEC, 0xA93A072A,
321 | 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319,
322 | 0x7533D928, 0xB155FDF5, 0x03563482, 0x8ABA3CBB,
323 | 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F,
324 | 0x4DE81751, 0x3830DC8E, 0x379D5862, 0x9320F991,
325 | 0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32,
326 | 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680,
327 | 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166,
328 | 0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE,
329 | 0x5BBEF7DD, 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB,
330 | 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5,
331 | 0x72EACEA8, 0xFA6484BB, 0x8D6612AE, 0xBF3C6F47,
332 | 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370,
333 | 0x740E0D8D, 0xE75B1357, 0xF8721671, 0xAF537D5D,
334 | 0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84,
335 | 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048,
336 | 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8,
337 | 0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD,
338 | 0xA08839E1, 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9,
339 | 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7,
340 | 0x1A908749, 0xD44FBD9A, 0xD0DADECB, 0xD50ADA38,
341 | 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F,
342 | 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, 0x27D9459C,
343 | 0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525,
344 | 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1,
345 | 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442,
346 | 0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964,
347 | 0x9F1F9532, 0xE0D392DF, 0xD3A0342B, 0x8971F21E,
348 | 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8,
349 | 0xDF359F8D, 0x9B992F2E, 0xE60B6F47, 0x0FE3F11D,
350 | 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F,
351 | 0x1618B166, 0xFD2C1D05, 0x848FD2C5, 0xF6FB2299,
352 | 0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02,
353 | 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC,
354 | 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614,
355 | 0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A,
356 | 0xC9AA53FD, 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6,
357 | 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B,
358 | 0x53113EC0, 0x1640E3D3, 0x38ABBD60, 0x2547ADF0,
359 | 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060,
360 | 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, 0x4CF9AA7E,
361 | 0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9,
362 | 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F,
363 | 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6
364 | ]
365 | ]
366 |
367 | # Cycle through the p-boxes and round-robin XOR the
368 | # key with the p-boxes
369 | key_len = len (key)
370 | index = 0
371 | for i in range (len (self.p_boxes)):
372 | val = (ord (key[index % key_len]) << 24) + \
373 | (ord (key[(index + 1) % key_len]) << 16) + \
374 | (ord (key[(index + 2) % key_len]) << 8) + \
375 | ord (key[(index + 3) % key_len])
376 | self.p_boxes[i] = self.p_boxes[i] ^ val
377 | index = index + 4
378 |
379 | # For the chaining process
380 | l, r = 0, 0
381 |
382 | # Begin chain replacing the p-boxes
383 | for i in range (0, len (self.p_boxes), 2):
384 | l, r = self.cipher (l, r, self.ENCRYPT)
385 | self.p_boxes[i] = l
386 | self.p_boxes[i + 1] = r
387 |
388 | # Chain replace the s-boxes
389 | for i in range (len (self.s_boxes)):
390 | for j in range (0, len (self.s_boxes[i]), 2):
391 | l, r = self.cipher (l, r, self.ENCRYPT)
392 | self.s_boxes[i][j] = l
393 | self.s_boxes[i][j + 1] = r
394 |
395 | def cipher (self, xl, xr, direction):
396 |
397 | if direction == self.ENCRYPT:
398 | for i in range (16):
399 | xl = xl ^ self.p_boxes[i]
400 | xr = self.__round_func (xl) ^ xr
401 | xl, xr = xr, xl
402 | xl, xr = xr, xl
403 | xr = xr ^ self.p_boxes[16]
404 | xl = xl ^ self.p_boxes[17]
405 | else:
406 | for i in range (17, 1, -1):
407 | xl = xl ^ self.p_boxes[i]
408 | xr = self.__round_func (xl) ^ xr
409 | xl, xr = xr, xl
410 | xl, xr = xr, xl
411 | xr = xr ^ self.p_boxes[1]
412 | xl = xl ^ self.p_boxes[0]
413 | return xl, xr
414 |
415 | def __round_func (self, xl):
416 | a = (xl & 0xFF000000) >> 24
417 | b = (xl & 0x00FF0000) >> 16
418 | c = (xl & 0x0000FF00) >> 8
419 | d = xl & 0x000000FF
420 |
421 | # Perform all ops as longs then and out the last 32-bits to
422 | # obtain the integer
423 | f = (long (self.s_boxes[0][a]) + long (self.s_boxes[1][b])) % self.modulus
424 | f = f ^ long (self.s_boxes[2][c])
425 | f = f + long (self.s_boxes[3][d])
426 | f = (f % self.modulus) & 0xFFFFFFFF
427 |
428 | return f
429 |
430 | def encrypt (self, data):
431 |
432 | if not len (data) == 8:
433 | raise RuntimeError, "Attempted to encrypt data of invalid block length: %s" %len (data)
434 |
435 | # Use big endianess since that's what everyone else uses
436 | xl = ord (data[3]) | (ord (data[2]) << 8) | (ord (data[1]) << 16) | (ord (data[0]) << 24)
437 | xr = ord (data[7]) | (ord (data[6]) << 8) | (ord (data[5]) << 16) | (ord (data[4]) << 24)
438 |
439 | cl, cr = self.cipher (xl, xr, self.ENCRYPT)
440 | chars = ''.join ([
441 | chr ((cl >> 24) & 0xFF), chr ((cl >> 16) & 0xFF), chr ((cl >> 8) & 0xFF), chr (cl & 0xFF),
442 | chr ((cr >> 24) & 0xFF), chr ((cr >> 16) & 0xFF), chr ((cr >> 8) & 0xFF), chr (cr & 0xFF)
443 | ])
444 | return chars
445 |
446 | def decrypt (self, data):
447 |
448 | if not len (data) == 8:
449 | raise RuntimeError, "Attempted to encrypt data of invalid block length: %s" %len (data)
450 |
451 | # Use big endianess since that's what everyone else uses
452 | cl = ord (data[3]) | (ord (data[2]) << 8) | (ord (data[1]) << 16) | (ord (data[0]) << 24)
453 | cr = ord (data[7]) | (ord (data[6]) << 8) | (ord (data[5]) << 16) | (ord (data[4]) << 24)
454 |
455 | xl, xr = self.cipher (cl, cr, self.DECRYPT)
456 | chars = ''.join ([
457 | chr ((xl >> 24) & 0xFF), chr ((xl >> 16) & 0xFF), chr ((xl >> 8) & 0xFF), chr (xl & 0xFF),
458 | chr ((xr >> 24) & 0xFF), chr ((xr >> 16) & 0xFF), chr ((xr >> 8) & 0xFF), chr (xr & 0xFF)
459 | ])
460 | return chars
461 |
462 | def blocksize (self):
463 | return 8
464 |
465 | def key_length (self):
466 | return 56
467 |
468 | def key_bits (self):
469 | return 56 * 8
470 |
471 |
472 |
--------------------------------------------------------------------------------
/pithos/pandora/fake.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
2 | ### BEGIN LICENSE
3 | # Copyright (C) 2010 Kevin Mehall
4 | #This program is free software: you can redistribute it and/or modify it
5 | #under the terms of the GNU General Public License version 3, as published
6 | #by the Free Software Foundation.
7 | #
8 | #This program is distributed in the hope that it will be useful, but
9 | #WITHOUT ANY WARRANTY; without even the implied warranties of
10 | #MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 | #PURPOSE. See the GNU General Public License for more details.
12 | #
13 | #You should have received a copy of the GNU General Public License along
14 | #with this program. If not, see .
15 | ### END LICENSE
16 |
17 | from pithos.pandora.pandora import *
18 | import gtk
19 | import logging
20 |
21 | class FakePandora(Pandora):
22 | def __init__(self):
23 | super(FakePandora, self).__init__()
24 | self.counter = 0
25 | self.show_fail_window()
26 | logging.info("Using test mode")
27 |
28 | def count(self):
29 | self.counter +=1
30 | return self.counter
31 |
32 | def show_fail_window(self):
33 | self.window = gtk.Window()
34 | self.window.set_size_request(200, 100)
35 | self.window.set_title("Pithos failure tester")
36 | self.window.set_opacity(0.7)
37 | self.auth_check = gtk.CheckButton("Authenticated")
38 | self.time_check = gtk.CheckButton("Be really slow")
39 | vbox = gtk.VBox()
40 | self.window.add(vbox)
41 | vbox.pack_start(self.auth_check)
42 | vbox.pack_start(self.time_check)
43 | self.window.show_all()
44 |
45 | def maybe_fail(self):
46 | if self.time_check.get_active():
47 | logging.info("fake: Going to sleep for 10s")
48 | time.sleep(10)
49 | if not self.auth_check.get_active():
50 | logging.info("fake: We're deauthenticated...")
51 | raise PandoraAuthTokenInvalid("Auth token invalid", "AUTH_INVALID_TOKEN")
52 |
53 | def set_authenticated(self):
54 | self.auth_check.set_active(True)
55 |
56 | def json_call(self, method, args={}, https=False, blowfish=True):
57 | time.sleep(1)
58 | self.maybe_fail()
59 |
60 | if method == 'user.getStationList':
61 | return {'stations': [
62 | {'stationId':'987', 'stationToken':'345434', 'isShared':False, 'isQuickMix':False, 'stationName':"Test Station 1"},
63 | {'stationId':'321', 'stationToken':'453544', 'isShared':False, 'isQuickMix':True, 'stationName':"Fake's QuickMix",
64 | 'quickMixStationIds':['987', '343']},
65 | {'stationId':'432', 'stationToken':'345485', 'isShared':False, 'isQuickMix':False, 'stationName':"Test Station 2"},
66 | {'stationId':'254', 'stationToken':'345415', 'isShared':False, 'isQuickMix':False, 'stationName':"Test Station 4 - Out of Order"},
67 | {'stationId':'343', 'stationToken':'345435', 'isShared':False, 'isQuickMix':False, 'stationName':"Test Station 3"},
68 | ]}
69 | elif method == 'station.getPlaylist':
70 | stationId = self.get_station_by_token(args['stationToken']).id
71 | return {'items': [self.makeFakeSong(stationId) for i in range(4)]}
72 | elif method == 'music.search':
73 | return {'artists': [
74 | {'score':90, 'musicToken':'988', 'artistName':"artistName"},
75 | ],
76 | 'songs':[
77 | {'score':80, 'musicToken':'238', 'songName':"SongName", 'artistName':"ArtistName"},
78 | ],
79 | }
80 | elif method == 'station.createStation':
81 | return {'stationId':'999', 'stationToken':'345433', 'isShared':False, 'isQuickMix':False, 'stationName':"Added Station"}
82 | elif method == 'station.addFeedback':
83 | return {'feedbackId': '1234'}
84 | elif method in ('user.setQuickMix',
85 | 'station.deleteFeedback',
86 | 'station.transformSharedStation',
87 | 'station.renameStation',
88 | 'station.deleteStation',
89 | 'user.sleepSong',
90 | 'bookmark.addSongBookmark',
91 | 'bookmark.addArtistBookmark',
92 | ):
93 | return 1
94 | else:
95 | logging.error("Invalid method %s" % method)
96 |
97 | def connect(self, user, password):
98 | self.set_authenticated()
99 | self.get_stations()
100 |
101 | def get_station_by_token(self, token):
102 | for i in self.stations:
103 | if i.idToken == token:
104 | return i
105 |
106 | def makeFakeSong(self, stationId):
107 | c = self.count()
108 | return {
109 | 'albumName':"AlbumName",
110 | 'artistName':"ArtistName",
111 | 'additionalAudioUrl':'http://kevinmehall.net/p/pithos/testfile.aac?val='+'0'*48,
112 | 'trackGain':0,
113 | 'trackToken':'5908540384',
114 | 'songRating': 1 if c%3 == 0 else 0,
115 | 'stationId': stationId,
116 | 'songName': 'Test song %i'%c,
117 | 'songDetailUrl': 'http://kevinmehall.net/p/pithos/',
118 | 'albumDetailUrl':'http://kevinmehall.net/p/pithos/',
119 | 'albumArtUrl':'http://i.imgur.com/H4Z8x.jpg',
120 | }
121 |
122 |
--------------------------------------------------------------------------------
/pithos/pandora/pandora.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
2 | ### BEGIN LICENSE
3 | # Copyright (C) 2010 Kevin Mehall
4 | # Copyright (C) 2012 Christopher Eby
5 | #This program is free software: you can redistribute it and/or modify it
6 | #under the terms of the GNU General Public License version 3, as published
7 | #by the Free Software Foundation.
8 | #
9 | #This program is distributed in the hope that it will be useful, but
10 | #WITHOUT ANY WARRANTY; without even the implied warranties of
11 | #MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12 | #PURPOSE. See the GNU General Public License for more details.
13 | #
14 | #You should have received a copy of the GNU General Public License along
15 | #with this program. If not, see .
16 | ### END LICENSE
17 |
18 | from pithos.pandora.blowfish import Blowfish
19 | import json
20 | import logging
21 | import time
22 | import urllib
23 | import urllib2
24 |
25 | # This is an implementation of the Pandora JSON API using Android partner
26 | # credentials.
27 | # See http://pan-do-ra-api.wikia.com/wiki/Json/5 for API documentation.
28 |
29 | PROTOCOL_VERSION = '5'
30 |
31 | CLIENTS = {'android-generic': {'deviceModel': 'android-generic',
32 | 'partner_username': 'android',
33 | 'partner_password': 'AC7IBG09A3DTSYM4R41UJWL07VLN8JI7',
34 | 'rpcUrl': '://tuner.pandora.com/services/json/?',
35 | 'encryptKey': '6#26FRL$ZWD',
36 | 'decryptKey': 'R=U!LH$O2B#',
37 | },
38 | 'pandora one': {'deviceModel': 'D01',
39 | 'partner_username': 'pandora one',
40 | 'partner_password': 'TVCKIBGS9AO9TSYLNNFUML0743LH82D',
41 | 'rpcUrl': '://internal-tuner.pandora.com/services/json/?',
42 | 'encryptKey': '2%3WCL*JU$MP]4',
43 | 'decryptKey': 'U#IO$RZPAB%VX2',
44 | }
45 | }
46 | DEFAULT_CLIENT = CLIENTS['android-generic']
47 |
48 | HTTP_TIMEOUT = 30
49 | USER_AGENT = 'pithos'
50 |
51 | RATE_BAN = 'ban'
52 | RATE_LOVE = 'love'
53 | RATE_NONE = None
54 |
55 | API_ERROR_API_VERSION_NOT_SUPPORTED = 11
56 | API_ERROR_COUNTRY_NOT_SUPPORTED = 12
57 | API_ERROR_INSUFFICIENT_CONNECTIVITY = 13
58 | API_ERROR_READ_ONLY_MODE = 1000
59 | API_ERROR_INVALID_AUTH_TOKEN = 1001
60 | API_ERROR_INVALID_LOGIN = 1002
61 | API_ERROR_LISTENER_NOT_AUTHORIZED = 1003
62 | API_ERROR_PARTNER_NOT_AUTHORIZED = 1010
63 |
64 | PLAYLIST_VALIDITY_TIME = 60*60*3
65 |
66 | class PandoraError(IOError):
67 | def __init__(self, message, status=None, submsg=None):
68 | self.status = status
69 | self.message = message
70 | self.submsg = submsg
71 |
72 | class PandoraAuthTokenInvalid(PandoraError): pass
73 | class PandoraNetError(PandoraError): pass
74 | class PandoraAPIVersionError(PandoraError): pass
75 | class PandoraTimeout(PandoraNetError): pass
76 |
77 |
78 | def pad(s, l):
79 | return s + "\0" * (l - len(s))
80 |
81 |
82 | class Pandora(object):
83 | def pandora_encrypt(self, s):
84 | return "".join([self.blowfish_encode.encrypt(pad(s[i:i+8], 8)).encode('hex') for i in xrange(0, len(s), 8)])
85 |
86 | def pandora_decrypt(self, s):
87 | return "".join([self.blowfish_decode.decrypt(pad(s[i:i+16].decode('hex'), 8)) for i in xrange(0, len(s), 16)]).rstrip('\x08')
88 |
89 | def json_call(self, method, args={}, https=False, blowfish=True):
90 | url_arg_strings = []
91 | if self.partnerId:
92 | url_arg_strings.append('partner_id=%s'%self.partnerId)
93 | if self.userId:
94 | url_arg_strings.append('user_id=%s'%self.userId)
95 | if self.userAuthToken:
96 | url_arg_strings.append('auth_token=%s'%urllib.quote_plus(self.userAuthToken))
97 | elif self.partnerAuthToken:
98 | url_arg_strings.append('auth_token=%s'%urllib.quote_plus(self.partnerAuthToken))
99 |
100 | url_arg_strings.append('method=%s'%method)
101 | protocol = 'https' if https else 'http'
102 | url = protocol + self.rpc_url + '&'.join(url_arg_strings)
103 |
104 | if self.time_offset:
105 | args['syncTime'] = int(time.time()+self.time_offset)
106 | if self.userAuthToken:
107 | args['userAuthToken'] = self.userAuthToken
108 | elif self.partnerAuthToken:
109 | args['partnerAuthToken'] = self.partnerAuthToken
110 | data = json.dumps(args)
111 |
112 | logging.debug(url)
113 | logging.debug(data)
114 |
115 | if blowfish:
116 | data = self.pandora_encrypt(data)
117 |
118 | try:
119 | req = urllib2.Request(url, data, {'User-agent': USER_AGENT, 'Content-type': 'text/plain'})
120 | response = self.opener.open(req, timeout=HTTP_TIMEOUT)
121 | text = response.read()
122 | except urllib2.HTTPError as e:
123 | logging.error("HTTP error: %s", e)
124 | raise PandoraNetError(str(e))
125 | except urllib2.URLError as e:
126 | logging.error("Network error: %s", e)
127 | if e.reason[0] == 'timed out':
128 | raise PandoraTimeout("Network error", submsg="Timeout")
129 | else:
130 | raise PandoraNetError("Network error", submsg=e.reason[1])
131 |
132 | logging.debug(text)
133 |
134 | tree = json.loads(text)
135 |
136 | if tree['stat'] == 'fail':
137 | code = tree['code']
138 | msg = tree['message']
139 | logging.error('fault code: ' + str(code) + ' message: ' + msg)
140 |
141 | if code == API_ERROR_INVALID_AUTH_TOKEN:
142 | raise PandoraAuthTokenInvalid(msg)
143 | elif code == API_ERROR_COUNTRY_NOT_SUPPORTED:
144 | raise PandoraError("Pandora not available", code,
145 | submsg="Pandora is not available outside the United States.")
146 | elif code == API_ERROR_API_VERSION_NOT_SUPPORTED:
147 | raise PandoraAPIVersionError(msg)
148 | elif code == API_ERROR_INSUFFICIENT_CONNECTIVITY:
149 | raise PandoraError("Out of sync", code,
150 | submsg="Correct your system's clock. If the problem persists, a Pithos update may be required")
151 | elif code == API_ERROR_READ_ONLY_MODE:
152 | raise PandoraError("Pandora maintenance", code,
153 | submsg="Pandora is in read-only mode as it is performing maintenance. Try again later.")
154 | elif code == API_ERROR_INVALID_LOGIN:
155 | raise PandoraError("Login Error", code, submsg="Invalid username or password")
156 | elif code == API_ERROR_LISTENER_NOT_AUTHORIZED:
157 | raise PandoraError("Login Error", code, submsg='User is not a Pandora One subscriber.\nPlease uncheck "Pandora One Subscriber" in preferences.')
158 | elif code == API_ERROR_PARTNER_NOT_AUTHORIZED:
159 | raise PandoraError("Login Error", code,
160 | submsg="Invalid Pandora partner keys. A Pithos update may be required.")
161 | else:
162 | raise PandoraError("Pandora returned an error", code, "%s (code %d)"%(msg, code))
163 |
164 | if 'result' in tree:
165 | return tree['result']
166 |
167 | def set_audio_quality(self, fmt):
168 | self.audio_quality = fmt
169 |
170 | def set_proxy(self, proxy):
171 | if proxy:
172 | proxy_handler = urllib2.ProxyHandler({'http': proxy})
173 | self.opener = urllib2.build_opener(proxy_handler)
174 | else:
175 | self.opener = urllib2.build_opener()
176 |
177 | def connect(self, prefs):
178 | client = DEFAULT_CLIENT
179 | if prefs['pandora_one']:
180 | client = CLIENTS['pandora one']
181 | self.partner_username = prefs.get('partner_username', client['partner_username'])
182 | self.partner_password = prefs.get('partner_password', client['partner_password'])
183 | self.device_model = prefs.get('device_model', client['deviceModel'])
184 | self.blowfish_encode = Blowfish(prefs.get('encrypt_key', client['encryptKey']))
185 | self.blowfish_decode = Blowfish(prefs.get('decrypt_key', client['decryptKey']))
186 | self.rpc_url = prefs.get('rpc_url', client['rpcUrl'])
187 |
188 | self.partnerId = self.userId = self.partnerAuthToken = self.userAuthToken = self.time_offset = None
189 |
190 | partner = self.json_call('auth.partnerLogin', {'deviceModel': self.device_model,
191 | 'username': self.partner_username,
192 | 'password': self.partner_password,
193 | 'version': PROTOCOL_VERSION},
194 | https=True, blowfish=False)
195 | self.partnerId = partner['partnerId']
196 | self.partnerAuthToken = partner['partnerAuthToken']
197 |
198 | pandora_time = int(self.pandora_decrypt(partner['syncTime'])[4:14])
199 | self.time_offset = pandora_time - time.time()
200 | logging.info("Time offset is %s", self.time_offset)
201 |
202 | user = self.json_call('auth.userLogin', {'username': prefs['username'],
203 | 'password': prefs['password'], 'loginType': 'user'},
204 | https=True)
205 | self.userId = user['userId']
206 | self.userAuthToken = user['userAuthToken']
207 |
208 | self.get_stations(self)
209 |
210 | def get_stations(self, *ignore):
211 | stations = self.json_call('user.getStationList')['stations']
212 | self.quickMixStationIds = None
213 | self.stations = [Station(self, i) for i in stations]
214 |
215 | if self.quickMixStationIds:
216 | for i in self.stations:
217 | if i.id in self.quickMixStationIds:
218 | i.useQuickMix = True
219 |
220 | def save_quick_mix(self):
221 | stationIds = []
222 | for i in self.stations:
223 | if i.useQuickMix:
224 | stationIds.append(i.id)
225 | self.json_call('user.setQuickMix', {'quickMixStationIds': stationIds})
226 |
227 | def search(self, query):
228 | results = self.json_call('music.search', {'searchText': query})
229 |
230 | l = [SearchResult('artist', i) for i in results['artists']]
231 | l += [SearchResult('song', i) for i in results['songs']]
232 | l.sort(key=lambda i: i.score, reverse=True)
233 |
234 | return l
235 |
236 | def add_station_by_music_id(self, musicid):
237 | d = self.json_call('station.createStation', {'musicToken': musicid})
238 | station = Station(self, d)
239 | self.stations.append(station)
240 | return station
241 |
242 | def get_station_by_id(self, id):
243 | for i in self.stations:
244 | if i.id == id:
245 | return i
246 |
247 | def add_feedback(self, trackToken, rating):
248 | logging.info("pandora: addFeedback")
249 | rating_bool = True if rating == RATE_LOVE else False
250 | feedback = self.json_call('station.addFeedback', {'trackToken': trackToken, 'isPositive': rating_bool})
251 | return feedback['feedbackId']
252 |
253 | def delete_feedback(self, stationToken, feedbackId):
254 | self.json_call('station.deleteFeedback', {'feedbackId': feedbackId, 'stationToken': stationToken})
255 |
256 | class Station(object):
257 | def __init__(self, pandora, d):
258 | self.pandora = pandora
259 |
260 | self.id = d['stationId']
261 | self.idToken = d['stationToken']
262 | self.isCreator = not d['isShared']
263 | self.isQuickMix = d['isQuickMix']
264 | self.name = d['stationName']
265 | self.useQuickMix = False
266 |
267 | if self.isQuickMix:
268 | self.pandora.quickMixStationIds = d.get('quickMixStationIds', [])
269 |
270 | def transformIfShared(self):
271 | if not self.isCreator:
272 | logging.info("pandora: transforming station")
273 | self.pandora.json_call('station.transformSharedStation', {'stationToken': self.idToken})
274 | self.isCreator = True
275 |
276 | def get_playlist(self):
277 | logging.info("pandora: Get Playlist")
278 | playlist = self.pandora.json_call('station.getPlaylist',
279 | {'stationToken': self.idToken}, https=True)
280 | songs = []
281 | for i in playlist['items']:
282 | if 'songName' in i: # check for ads
283 | songs.append(Song(self.pandora, i))
284 | return songs
285 |
286 | @property
287 | def info_url(self):
288 | return 'http://www.pandora.com/stations/'+self.idToken
289 |
290 | def rename(self, new_name):
291 | if new_name != self.name:
292 | self.transformIfShared()
293 | logging.info("pandora: Renaming station")
294 | self.pandora.json_call('station.renameStation', {'stationToken': self.idToken, 'stationName': new_name})
295 | self.name = new_name
296 |
297 | def delete(self):
298 | logging.info("pandora: Deleting Station")
299 | self.pandora.json_call('station.deleteStation', {'stationToken': self.idToken})
300 |
301 | class Song(object):
302 | def __init__(self, pandora, d):
303 | self.pandora = pandora
304 |
305 | self.album = d['albumName']
306 | self.artist = d['artistName']
307 | if self.pandora.audio_quality in d['audioUrlMap']:
308 | self.audioUrl = d['audioUrlMap'][self.pandora.audio_quality]['audioUrl']
309 | else:
310 | # Just pick the first one
311 | self.audioUrl = d['audioUrlMap'].values()[0]['audioUrl']
312 | self.trackToken = d['trackToken']
313 | self.rating = RATE_LOVE if d['songRating'] == 1 else RATE_NONE # banned songs won't play, so we don't care about them
314 | self.stationId = d['stationId']
315 | self.title = d['songName']
316 | self.songDetailURL = d['songDetailUrl']
317 | self.artRadio = d['albumArtUrl']
318 |
319 | self.tired=False
320 | self.message=''
321 | self.start_time = None
322 | self.finished = False
323 | self.playlist_time = time.time()
324 | self.feedbackId = None
325 |
326 | @property
327 | def station(self):
328 | return self.pandora.get_station_by_id(self.stationId)
329 |
330 | def rate(self, rating):
331 | if self.rating != rating:
332 | self.station.transformIfShared()
333 | if rating == RATE_NONE:
334 | if not self.feedbackId:
335 | # We need a feedbackId, get one by re-rating the song. We
336 | # could also get one by calling station.getStation, but
337 | # that requires transferring a lot of data (all feedback,
338 | # seeds, etc for the station).
339 | opposite = RATE_BAN if self.rating == RATE_LOVE else RATE_LOVE
340 | self.feedbackId = self.pandora.add_feedback(self.trackToken, opposite)
341 | self.pandora.delete_feedback(self.station.idToken, self.feedbackId)
342 | else:
343 | self.feedbackId = self.pandora.add_feedback(self.trackToken, rating)
344 | self.rating = rating
345 |
346 | def set_tired(self):
347 | if not self.tired:
348 | self.pandora.json_call('user.sleepSong', {'trackToken': self.trackToken})
349 | self.tired = True
350 |
351 | def bookmark(self):
352 | self.pandora.json_call('bookmark.addSongBookmark', {'trackToken': self.trackToken})
353 |
354 | def bookmark_artist(self):
355 | self.pandora.json_call('bookmark.addArtistBookmark', {'trackToken': self.trackToken})
356 |
357 | @property
358 | def rating_str(self):
359 | return self.rating
360 |
361 | def is_still_valid(self):
362 | return (time.time() - self.playlist_time) < PLAYLIST_VALIDITY_TIME
363 |
364 | class SearchResult(object):
365 | def __init__(self, resultType, d):
366 | self.resultType = resultType
367 | self.score = d['score']
368 | self.musicId = d['musicToken']
369 |
370 | if resultType == 'song':
371 | self.title = d['songName']
372 | self.artist = d['artistName']
373 | elif resultType == 'artist':
374 | self.name = d['artistName']
375 |
376 |
--------------------------------------------------------------------------------
/pithos/pithosconfig.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
2 | ### BEGIN LICENSE
3 | # Copyright (C) 2010-2012 Kevin Mehall
4 | #This program is free software: you can redistribute it and/or modify it
5 | #under the terms of the GNU General Public License version 3, as published
6 | #by the Free Software Foundation.
7 | #
8 | #This program is distributed in the hope that it will be useful, but
9 | #WITHOUT ANY WARRANTY; without even the implied warranties of
10 | #MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 | #PURPOSE. See the GNU General Public License for more details.
12 | #
13 | #You should have received a copy of the GNU General Public License along
14 | #with this program. If not, see .
15 | ### END LICENSE
16 |
17 | # where your project will head for your data (for instance, images and ui files)
18 | # by default, this is ../data, relative your trunk layout
19 | __pithos_data_directory__ = '../data/'
20 | __license__ = 'GPL-3'
21 |
22 | VERSION = '0.3.17'
23 |
24 | import os
25 |
26 | class project_path_not_found(Exception):
27 | pass
28 |
29 | valid_audio_formats = [
30 | ('highQuality', 'High'),
31 | ('mediumQuality', 'Medium'),
32 | ('lowQuality', 'Low'),
33 | ]
34 |
35 | def get_data_file(*path_segments):
36 | """Get the full path to a data file.
37 |
38 | Returns the path to a file underneath the data directory (as defined by
39 | `get_data_path`). Equivalent to os.path.join(get_data_path(),
40 | *path_segments).
41 | """
42 | return os.path.join(getdatapath(), *path_segments)
43 |
44 | def getdatapath():
45 | """Retrieve pithos data path
46 |
47 | This path is by default /../data/ in trunk
48 | and /usr/share/pithos in an installed version but this path
49 | is specified at installation time.
50 | """
51 |
52 | # get pathname absolute or relative
53 | if __pithos_data_directory__.startswith('/'):
54 | pathname = __pithos_data_directory__
55 | else:
56 | pathname = os.path.dirname(__file__) + '/' + __pithos_data_directory__
57 |
58 | abs_data_path = os.path.abspath(pathname)
59 | if os.path.exists(abs_data_path):
60 | return abs_data_path
61 | else:
62 | raise project_path_not_found
63 |
64 | if __name__=='__main__':
65 | print VERSION
66 |
--------------------------------------------------------------------------------
/pithos/plugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
2 | ### BEGIN LICENSE
3 | # Copyright (C) 2010-2012 Kevin Mehall
4 | #This program is free software: you can redistribute it and/or modify it
5 | #under the terms of the GNU General Public License version 3, as published
6 | #by the Free Software Foundation.
7 | #
8 | #This program is distributed in the hope that it will be useful, but
9 | #WITHOUT ANY WARRANTY; without even the implied warranties of
10 | #MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 | #PURPOSE. See the GNU General Public License for more details.
12 | #
13 | #You should have received a copy of the GNU General Public License along
14 | #with this program. If not, see .
15 | ### END LICENSE
16 |
17 | import logging
18 | import glob
19 | import os
20 |
21 | class PithosPlugin(object):
22 | _PITHOS_PLUGIN = True # used to find the plugin class in a module
23 | preference = None
24 | def __init__(self, name, window):
25 | self.name = name
26 | self.window = window
27 | self.prepared = False
28 | self.enabled = False
29 |
30 | def enable(self):
31 | if not self.prepared:
32 | self.error = self.on_prepare()
33 | self.prepared = True
34 | if not self.error and not self.enabled:
35 | logging.info("Enabling module %s"%(self.name))
36 | self.on_enable()
37 | self.enabled = True
38 |
39 | def disable(self):
40 | if self.enabled:
41 | logging.info("Disabling module %s"%(self.name))
42 | self.on_disable()
43 | self.enabled = False
44 |
45 | def on_prepare(self):
46 | pass
47 |
48 | def on_enable(self):
49 | pass
50 |
51 | def on_disable(self):
52 | pass
53 |
54 | class ErrorPlugin(PithosPlugin):
55 | def __init__(self, name, error):
56 | logging.error("Error loading plugin %s: %s"%(name, error))
57 | self.prepared = True
58 | self.error = error
59 | self.name = name
60 | self.enabled = False
61 |
62 | def load_plugin(name, window):
63 | try:
64 | module = __import__('pithos.plugins.'+name)
65 | module = getattr(module.plugins, name)
66 |
67 | except ImportError as e:
68 | return ErrorPlugin(name, e.message)
69 |
70 | # find the class object for the actual plugin
71 | for key, item in module.__dict__.iteritems():
72 | if hasattr(item, '_PITHOS_PLUGIN') and key != "PithosPlugin":
73 | plugin_class = item
74 | break
75 | else:
76 | return ErrorPlugin(name, "Could not find module class")
77 |
78 | return plugin_class(name, window)
79 |
80 | def load_plugins(window):
81 | plugins = window.plugins
82 | prefs = window.preferences
83 |
84 | plugins_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "plugins")
85 | discovered_plugins = [ fname.replace(".py", "") for fname in glob.glob1(plugins_dir, "*.py") if not fname.startswith("__") ]
86 |
87 | for name in discovered_plugins:
88 | if not name in plugins:
89 | plugin = plugins[name] = load_plugin(name, window)
90 | else:
91 | plugin = plugins[name]
92 |
93 | if plugin.preference and prefs.get(plugin.preference, False):
94 | plugin.enable()
95 | else:
96 | plugin.disable()
97 |
98 |
--------------------------------------------------------------------------------
/pithos/plugins/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TingPing/pithos-for-windows/02a93e76ff6b616e14f10dfc04d1532bd7d98940/pithos/plugins/__init__.py
--------------------------------------------------------------------------------
/pithos/plugins/growl.py:
--------------------------------------------------------------------------------
1 | from pithos.plugin import PithosPlugin
2 | from pithos.pithosconfig import get_data_file
3 | import logging
4 |
5 | class GrowlPlugin(PithosPlugin):
6 | preference = 'growl'
7 |
8 | def on_prepare(self):
9 | pass
10 |
11 | def on_enable(self):
12 | try: import gntp.notifier
13 | except(ImportError):
14 | logging.error('Growl Error: gntp not installed: https://github.com/kfdm/gntp')
15 | return False
16 | #temp hosting of icon
17 | self.pithosicon = 'http://puu.sh/xnMA'
18 | self.growl = gntp.notifier.GrowlNotifier(
19 | applicationName='Pithos',
20 | notifications=['Song Changed'],
21 | defaultNotifications=['Song Changed'],
22 | applicationIcon=self.pithosicon,
23 | # change for over the network notifications
24 | #hostname='localhost',
25 | #password=''
26 | )
27 | try: self.growl.register()
28 | except: logging.warning('Failed to register Growl')
29 | self.song_callback_handle = self.window.connect("song-changed", self.song_changed)
30 |
31 | def song_changed(self, window, song):
32 | try:
33 | self.growl.notify(
34 | noteType='Song Changed',
35 | title=song.title,
36 | description='by %s on %s' %(song.artist, song.album),
37 | icon=self.pithosicon,
38 | sticky=False,
39 | priority=1
40 | )
41 | except:
42 | logging.warning('Growl not running')
43 |
44 | def on_disable(self):
45 | self.window.disconnect(self.song_callback_handle)
46 |
--------------------------------------------------------------------------------
/pithos/plugins/mediakeys.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
2 | ### BEGIN LICENSE
3 | # Copyright (C) 2010-2012 Kevin Mehall
4 | #This program is free software: you can redistribute it and/or modify it
5 | #under the terms of the GNU General Public License version 3, as published
6 | #by the Free Software Foundation.
7 | #
8 | #This program is distributed in the hope that it will be useful, but
9 | #WITHOUT ANY WARRANTY; without even the implied warranties of
10 | #MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 | #PURPOSE. See the GNU General Public License for more details.
12 | #
13 | #You should have received a copy of the GNU General Public License along
14 | #with this program. If not, see .
15 | ### END LICENSE
16 |
17 | from pithos.plugin import PithosPlugin
18 | import logging
19 |
20 | class MediaKeyPlugin(PithosPlugin):
21 | preference = 'mediakeys'
22 |
23 | def kbevent(self, event):
24 | if event.KeyID == 179 or event.Key == 'Media_Play_Pause':
25 | self.window.playpause_notify()
26 | if event.KeyID == 176 or event.Key == 'Media_Next_Track':
27 | self.window.next_song()
28 | return True
29 |
30 | def on_enable(self):
31 | try: import pyHook
32 | except ImportError:
33 | logging.warning('Please install PyHook(http://sourceforge.net/projects/pyhook/files/')
34 | return False
35 | self.hookman = pyHook.HookManager()
36 | self.hookman.KeyDown = self.kbevent
37 | self.hookman.HookKeyboard()
38 |
39 | def on_disable(self):
40 | self.hookman.UnhookKeyboard()
41 |
--------------------------------------------------------------------------------
/pithos/plugins/notification_icon.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
2 | ### BEGIN LICENSE
3 | # Copyright (C) 2010-2012 Kevin Mehall
4 | #This program is free software: you can redistribute it and/or modify it
5 | #under the terms of the GNU General Public License version 3, as published
6 | #by the Free Software Foundation.
7 | #
8 | #This program is distributed in the hope that it will be useful, but
9 | #WITHOUT ANY WARRANTY; without even the implied warranties of
10 | #MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 | #PURPOSE. See the GNU General Public License for more details.
12 | #
13 | #You should have received a copy of the GNU General Public License along
14 | #with this program. If not, see .
15 | ### END LICENSE
16 |
17 | import gtk
18 | from pithos.pithosconfig import get_data_file
19 | from pithos.plugin import PithosPlugin
20 |
21 | # Check if appindicator is available on the system
22 | try:
23 | import appindicator
24 | indicator_capable = True
25 | except:
26 | indicator_capable = False
27 |
28 | class PithosNotificationIcon(PithosPlugin):
29 | preference = 'show_icon'
30 |
31 | def on_prepare(self):
32 | if indicator_capable:
33 | self.ind = appindicator.Indicator("pithos", \
34 | "pithos-mono", \
35 | appindicator.CATEGORY_APPLICATION_STATUS, \
36 | get_data_file('media'))
37 |
38 | def on_enable(self):
39 | self.visible = True
40 | self.delete_callback_handle = self.window.connect("delete-event", self.toggle_visible)
41 | self.state_callback_handle = self.window.connect("play-state-changed", self.play_state_changed)
42 | self.song_callback_handle = self.window.connect("song-changed", self.song_changed)
43 |
44 | if indicator_capable:
45 | self.ind.set_status(appindicator.STATUS_ACTIVE)
46 | else:
47 | self.statusicon = gtk.status_icon_new_from_file(get_data_file('media', 'icon.png'))
48 | self.statusicon.connect('activate', self.toggle_visible)
49 |
50 | self.build_context_menu()
51 |
52 | def build_context_menu(self):
53 | menu = gtk.Menu()
54 |
55 | def button(text, action, icon=None):
56 | if icon == 'check':
57 | item = gtk.CheckMenuItem(text)
58 | item.set_active(True)
59 | elif icon:
60 | item = gtk.ImageMenuItem(text)
61 | item.set_image(gtk.image_new_from_stock(icon, gtk.ICON_SIZE_MENU))
62 | else:
63 | item = gtk.MenuItem(text)
64 | item.connect('activate', action)
65 | item.show()
66 | menu.append(item)
67 | return item
68 |
69 | if indicator_capable:
70 | # We have to add another entry for show / hide Pithos window
71 | self.visible_check = button("Show Pithos", self._toggle_visible, 'check')
72 |
73 | self.playpausebtn = button("Pause", self.window.playpause, gtk.STOCK_MEDIA_PAUSE)
74 | button("Skip", self.window.next_song, gtk.STOCK_MEDIA_NEXT)
75 | button("Love", (lambda *i: self.window.love_song()), gtk.STOCK_ABOUT)
76 | button("Ban", (lambda *i: self.window.ban_song()), gtk.STOCK_CANCEL)
77 | button("Tired", (lambda *i: self.window.tired_song()), gtk.STOCK_JUMP_TO)
78 | button("Quit", self.window.quit, gtk.STOCK_QUIT )
79 |
80 | # connect our new menu to the statusicon or the appindicator
81 | if indicator_capable:
82 | self.ind.set_menu(menu)
83 | else:
84 | self.statusicon.connect('popup-menu', self.context_menu, menu)
85 |
86 | self.menu = menu
87 |
88 |
89 | def play_state_changed(self, window, playing):
90 | """ play or pause and rotate the text """
91 |
92 | button = self.playpausebtn
93 | if not playing:
94 | button.set_label("Play")
95 | button.set_image(gtk.image_new_from_stock(gtk.STOCK_MEDIA_PLAY, gtk.ICON_SIZE_MENU))
96 |
97 | else:
98 | button.set_label("Pause")
99 | button.set_image(gtk.image_new_from_stock(gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_MENU))
100 |
101 | if indicator_capable: # menu needs to be reset to get updated icon
102 | self.ind.set_menu(self.menu)
103 |
104 | def song_changed(self, window, song):
105 | if not indicator_capable:
106 | self.statusicon.set_tooltip("%s by %s"%(song.title, song.artist))
107 |
108 | def _toggle_visible(self, *args):
109 | if self.visible:
110 | self.window.hide()
111 | else:
112 | self.window.bring_to_top()
113 |
114 | self.visible = not self.visible
115 |
116 | def toggle_visible(self, *args):
117 | if hasattr(self, 'visible_check'):
118 | self.visible_check.set_active(not self.visible)
119 | else:
120 | self._toggle_visible()
121 |
122 | return True
123 |
124 | def context_menu(self, widget, button, time, data=None):
125 | if button == 3:
126 | if data:
127 | data.show_all()
128 | data.popup(None, None, None, 3, time)
129 |
130 | def on_disable(self):
131 | if indicator_capable:
132 | self.ind.set_status(appindicator.STATUS_PASSIVE)
133 | else:
134 | self.statusicon.set_visible(False)
135 |
136 | self.window.disconnect(self.delete_callback_handle)
137 | self.window.disconnect(self.state_callback_handle)
138 | self.window.disconnect(self.song_callback_handle)
139 |
140 | # Pithos window needs to be reconnected to on_destro()
141 | self.window.connect('delete-event',self.window.on_destroy)
142 |
143 |
--------------------------------------------------------------------------------
/pithos/plugins/scrobble.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
2 | ### BEGIN LICENSE
3 | # Copyright (C) 2010-2012 Kevin Mehall
4 | #This program is free software: you can redistribute it and/or modify it
5 | #under the terms of the GNU General Public License version 3, as published
6 | #by the Free Software Foundation.
7 | #
8 | #This program is distributed in the hope that it will be useful, but
9 | #WITHOUT ANY WARRANTY; without even the implied warranties of
10 | #MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 | #PURPOSE. See the GNU General Public License for more details.
12 | #
13 | #You should have received a copy of the GNU General Public License along
14 | #with this program. If not, see .
15 | ### END LICENSE
16 |
17 | from pithos import pylast
18 | import webbrowser
19 | import logging
20 | from pithos.gobject_worker import GObjectWorker
21 | from pithos.plugin import PithosPlugin
22 |
23 | #getting an API account: http://www.last.fm/api/account
24 | API_KEY = '997f635176130d5d6fe3a7387de601a8'
25 | API_SECRET = '3243b876f6bf880b923a3c9fb955720c'
26 |
27 | #client id, client version info: http://www.last.fm/api/submissions#1.1
28 | CLIENT_ID = 'pth'
29 | CLIENT_VERSION = '1.0'
30 |
31 | _worker = None
32 | def get_worker():
33 | # so it can be shared between the plugin and the authorizer
34 | global _worker
35 | if not _worker:
36 | _worker = GObjectWorker()
37 | return _worker
38 |
39 | class LastfmPlugin(PithosPlugin):
40 | preference='lastfm_key'
41 |
42 | def on_prepare(self):
43 | self.worker = get_worker()
44 |
45 | def on_enable(self):
46 | self.connect(self.window.preferences['lastfm_key'])
47 | self.song_ended_handle = self.window.connect('song-ended', self.song_ended)
48 | self.song_changed_handle = self.window.connect('song-changed', self.song_changed)
49 |
50 | def on_disable(self):
51 | self.window.disconnect(self.song_ended_handle)
52 | self.window.disconnect(self.song_rating_changed_handle)
53 | self.window.disconnect(self.song_changed_handle)
54 |
55 | def song_ended(self, window, song):
56 | self.scrobble(song)
57 |
58 | def connect(self, session_key):
59 | self.network = pylast.get_lastfm_network(
60 | api_key=API_KEY, api_secret=API_SECRET,
61 | session_key = session_key
62 | )
63 | self.scrobbler = self.network.get_scrobbler(CLIENT_ID, CLIENT_VERSION)
64 |
65 | def song_changed(self, window, song):
66 | self.worker.send(self.scrobbler.report_now_playing, (song.artist, song.title, song.album))
67 |
68 | def send_rating(self, song, rating):
69 | if song.rating:
70 | track = self.network.get_track(song.artist, song.title)
71 | if rating == 'love':
72 | self.worker.send(track.love)
73 | elif rating == 'ban':
74 | self.worker.send(track.ban)
75 | logging.info("Sending song rating to last.fm")
76 |
77 | def scrobble(self, song):
78 | if song.duration > 30 and (song.position > 240 or song.position > song.duration/2):
79 | logging.info("Scrobbling song")
80 | mode = pylast.SCROBBLE_MODE_PLAYED
81 | source = pylast.SCROBBLE_SOURCE_PERSONALIZED_BROADCAST
82 | self.worker.send(self.scrobbler.scrobble, (song.artist, song.title, int(song.start_time), source, mode, song.duration, song.album))
83 |
84 |
85 | class LastFmAuth:
86 | def __init__(self, d, prefname, button):
87 | self.button = button
88 | self.dict = d
89 | self.prefname = prefname
90 |
91 | self.auth_url= False
92 | self.set_button_text()
93 | self.button.connect('clicked', self.clicked)
94 |
95 | @property
96 | def enabled(self):
97 | return self.dict[self.prefname]
98 |
99 | def setkey(self, key):
100 | self.dict[self.prefname] = key
101 | self.set_button_text()
102 |
103 | def set_button_text(self):
104 | self.button.set_sensitive(True)
105 | if self.auth_url:
106 | self.button.set_label("Click once authorized on web site")
107 | elif self.enabled:
108 | self.button.set_label("Disable")
109 | else:
110 | self.button.set_label("Authorize")
111 |
112 | def clicked(self, *ignore):
113 | if self.auth_url:
114 | def err(e):
115 | logging.error(e)
116 | self.set_button_text()
117 |
118 | get_worker().send(self.sg.get_web_auth_session_key, (self.auth_url,), self.setkey, err)
119 | self.button.set_label("Checking...")
120 | self.button.set_sensitive(False)
121 | self.auth_url = False
122 |
123 | elif self.enabled:
124 | self.setkey(False)
125 | else:
126 | self.network = pylast.get_lastfm_network(api_key=API_KEY, api_secret=API_SECRET)
127 | self.sg = pylast.SessionKeyGenerator(self.network)
128 |
129 | def callback(url):
130 | self.auth_url = url
131 | self.set_button_text()
132 | webbrowser.open(self.auth_url)
133 |
134 | get_worker().send(self.sg.get_web_auth_url, (), callback)
135 | self.button.set_label("Connecting...")
136 | self.button.set_sensitive(False)
137 |
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/windows/MoveFileFolder.nsh:
--------------------------------------------------------------------------------
1 | ;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2 | ; MoveFile and MoveFolder macros
3 | ;
4 | ; Author: theblazingangel@aol.com (for the AutoPatcher project - www.autopatcher.com)
5 | ; Created: June 2007
6 | ;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
7 |
8 | ;==================
9 | ; MoveFile macro
10 | ;==================
11 |
12 | !macro MoveFile sourceFile destinationFile
13 |
14 | !define MOVEFILE_JUMP ${__LINE__}
15 |
16 | ; Check source actually exists
17 |
18 | IfFileExists "${sourceFile}" +3 0
19 | SetErrors
20 | goto done_${MOVEFILE_JUMP}
21 |
22 | ; Add message to details-view/install-log
23 |
24 | DetailPrint "Moving/renaming file: ${sourceFile} to ${destinationFile}"
25 |
26 | ; If destination does not already exists simply move file
27 |
28 | IfFileExists "${destinationFile}" +3 0
29 | rename "${sourceFile}" "${destinationFile}"
30 | goto done_${MOVEFILE_JUMP}
31 |
32 | ; If overwriting without 'ifnewer' check
33 |
34 | ${If} $switch_overwrite == 1
35 | delete "${destinationFile}"
36 | rename "${sourceFile}" "${destinationFile}"
37 | delete "${sourceFile}"
38 | goto done_${MOVEFILE_JUMP}
39 | ${EndIf}
40 |
41 | ; If destination already exists
42 |
43 | Push $R0
44 | Push $R1
45 | Push $R2
46 | push $R3
47 |
48 | GetFileTime "${sourceFile}" $R0 $R1
49 | GetFileTime "${destinationFile}" $R2 $R3
50 |
51 | IntCmp $R0 $R2 0 older_${MOVEFILE_JUMP} newer_${MOVEFILE_JUMP}
52 | IntCmp $R1 $R3 older_${MOVEFILE_JUMP} older_${MOVEFILE_JUMP} newer_${MOVEFILE_JUMP}
53 |
54 | older_${MOVEFILE_JUMP}:
55 | delete "${sourceFile}"
56 | goto time_check_done_${MOVEFILE_JUMP}
57 |
58 | newer_${MOVEFILE_JUMP}:
59 | delete "${destinationFile}"
60 | rename "${sourceFile}" "${destinationFile}"
61 | delete "${sourceFile}" ;incase above failed!
62 |
63 | time_check_done_${MOVEFILE_JUMP}:
64 |
65 | Pop $R3
66 | Pop $R2
67 | Pop $R1
68 | Pop $R0
69 |
70 | done_${MOVEFILE_JUMP}:
71 |
72 | !undef MOVEFILE_JUMP
73 |
74 | !macroend
75 |
76 | ;==================
77 | ; MoveFolder macro
78 | ;==================
79 |
80 | !macro MoveFolder source destination mask
81 |
82 | !define MOVEFOLDER_JUMP ${__LINE__}
83 |
84 | Push $R0
85 | Push $R1
86 |
87 | ; Move path parameters into registers so they can be altered if necessary
88 |
89 | StrCpy $R0 "${source}"
90 | StrCpy $R1 "${destination}"
91 |
92 | ; Sort out paths - remove final backslash if supplied
93 |
94 | Push $0
95 |
96 | ; Source
97 | StrCpy $0 "$R0" 1 -1
98 | StrCmp $0 '\' 0 +2
99 | StrCpy $R0 "$R0" -1
100 |
101 | ; Destination
102 | StrCpy $0 "$R1" 1 -1
103 | StrCmp $0 '\' 0 +2
104 | StrCpy $R1 "$R1" -1
105 |
106 | Pop $0
107 |
108 | ; Create destination dir
109 |
110 | CreateDirectory "$R1\"
111 |
112 | ; Add message to details-view/install-log
113 |
114 | DetailPrint "Moving files: $R0\${mask} to $R1\"
115 |
116 | ; Push registers used by ${Locate} onto stack
117 |
118 | Push $R6
119 | Push $R7
120 | Push $R8
121 | Push $R9
122 |
123 | ; Duplicate dir structure (to preserve empty folders and such)
124 |
125 | ${Locate} "$R0" "/L=D" ".MoveFolder_Locate_createDir"
126 |
127 | ; Locate files and move (via callback function)
128 |
129 | ${Locate} "$R0" "/L=F /M=${mask} /S= /G=1" ".MoveFolder_Locate_moveFile"
130 |
131 | ; Delete subfolders left over after move
132 |
133 | Push $R2
134 | deldir_loop_${MOVEFOLDER_JUMP}:
135 | StrCpy $R2 0
136 | ${Locate} "$R0" "/L=DE" ".MoveFolder_Locate_deleteDir"
137 | StrCmp $R2 0 0 deldir_loop_${MOVEFOLDER_JUMP}
138 | Pop $R2
139 |
140 | ; Delete empty subfolders moved - say the user just wanted to move *.apm files, they now also have a load of empty dir's from dir structure duplication!
141 |
142 | Push $R2
143 | delnewdir_loop_${MOVEFOLDER_JUMP}:
144 | StrCpy $R2 0
145 | ${Locate} "$R1" "/L=DE" ".MoveFolder_Locate_deleteDir"
146 | StrCmp $R2 0 0 delnewdir_loop_${MOVEFOLDER_JUMP}
147 | Pop $R2
148 |
149 | ; Pop registers used by ${Locate} off the stack again
150 |
151 | Pop $R9
152 | Pop $R8
153 | Pop $R7
154 | Pop $R6
155 |
156 | ; Delete source folder if empty
157 |
158 | rmdir "$R0"
159 |
160 | Pop $R1
161 | Pop $R0
162 |
163 | !undef MOVEFOLDER_JUMP
164 |
165 | !macroend
166 |
167 | ;==================
168 | ; MoveFolder macro's ${Locate} callback functions
169 | ;==================
170 |
171 | Function .MoveFolder_Locate_createDir
172 |
173 | ${If} $R6 == ""
174 | Push $R2
175 | StrLen $R2 "$R0"
176 | StrCpy $R2 $R9 '' $R2
177 | CreateDirectory "$R1$R2"
178 | Pop $R2
179 | ${EndIf}
180 |
181 | Push $R1
182 |
183 | FunctionEnd
184 |
185 | Function .MoveFolder_Locate_moveFile
186 |
187 | Push $R2
188 |
189 | ; Get path to file
190 |
191 | StrLen $R2 "$R0"
192 | StrCpy $R2 $R9 '' $R2
193 | StrCpy $R2 "$R1$R2"
194 |
195 | ; If destination does not already exists simply move file
196 |
197 | IfFileExists "$R2" +3 0
198 | rename "$R9" "$R2"
199 | goto done
200 |
201 | ; If overwriting without 'ifnewer' check
202 |
203 | ${If} $switch_overwrite == 1
204 | delete "$R2"
205 | rename "$R9" "$R2"
206 | delete "$R9"
207 | goto done
208 | ${EndIf}
209 |
210 | ; If destination already exists
211 |
212 | Push $0
213 | Push $1
214 | Push $2
215 | push $3
216 |
217 | GetFileTime "$R9" $0 $1
218 | GetFileTime "$R2" $2 $3
219 |
220 | IntCmp $0 $2 0 older newer
221 | IntCmp $1 $3 older older newer
222 |
223 | older:
224 | delete "$R9"
225 | goto time_check_done
226 |
227 | newer:
228 | delete "$R2"
229 | rename "$R9" "$R2"
230 | delete "$R9" ;incase above failed!
231 |
232 | time_check_done:
233 |
234 | Pop $3
235 | Pop $2
236 | Pop $1
237 | Pop $0
238 |
239 | done:
240 |
241 | Pop $R2
242 |
243 | Push $R1
244 |
245 | FunctionEnd
246 |
247 | Function .MoveFolder_Locate_deleteDir
248 |
249 | ${If} $R6 == ""
250 | RMDir $R9
251 | IntOp $R2 $R2 + 1
252 | ${EndIf}
253 |
254 | Push $R1
255 |
256 | FunctionEnd
--------------------------------------------------------------------------------
/windows/dependencies.nsi:
--------------------------------------------------------------------------------
1 | ;---------------------------------------------------------------------
2 | ; CUSTOM PAGE to DOWNLOAD REQUIRED DEPENDENCIES
3 | ; - Modified from ASCEND NSIS installer (http://www.ascend4.org/)
4 |
5 | Var CHECKPY
6 | Var CHECKGSTCOMSDK
7 |
8 | !macro setCheckboxChecked CB
9 | SendMessage ${CB} ${BM_SETCHECK} 0x0001 0
10 | Pop $0
11 | !macroend
12 |
13 | Function dependenciesCreate
14 |
15 | ${If} $HAVE_PYTHON == 'OK'
16 | ${AndIf} $HAVE_GSTCOMSDK == 'OK'
17 | ; do nothing in this page
18 | ${Else}
19 | nsDialogs::Create /NOUNLOAD 1018
20 | Pop $0
21 |
22 | ${NSD_CreateLabel} 0% 0 100% 48% "The following additional packages are required for Pithos to function correctly. Checked items will be downloaded and installed (some of the installers may require you to click 'next' a few times). If you already have the components installed you can unckeck them and they will not be downloaded. If any of these packages are not installed, Pithos will not work."
23 | Pop $0
24 |
25 | ${If} $HAVE_PYTHON == 'NOK'
26 | ${NSD_CreateCheckbox} 10% 48% 100% 8u "Python ${PYTHON_VERSION} (${PYTHON_FSIZE})"
27 | Pop $CHECKPY
28 | !insertmacro setCheckboxChecked $CHECKPY
29 | ${Else}
30 | ${NSD_CreateLabel} 10% 48% 100% 8u "--- Python ${PYTHON_VERSION} already installed"
31 | ${EndIf}
32 |
33 | ; GStreamer.com SDK
34 |
35 | ${If} $HAVE_GSTCOMSDK == 'NOK'
36 | ${NSD_CreateCheckbox} 10% 64% 100% 8u "GStreamer.com SDK ${GSTCOMSDK_VERSION} (${GSTCOMSDK_FSIZE})"
37 | Pop $CHECKGSTCOMSDK
38 | !insertmacro setCheckboxChecked $CHECKGSTCOMSDK
39 | ${Else}
40 | ${NSD_CreateLabel} 10% 64% 100% 8u "--- GStreamer.com SDK already installed"
41 | ${EndIf}
42 |
43 | nsDialogs::Show
44 | ${EndIf}
45 |
46 | FunctionEnd
47 |
48 | Function DependenciesLeave
49 | SendMessage $CHECKPY ${BM_GETCHECK} 0 0 $NEED_PYTHON
50 | SendMessage $CHECKGSTCOMSDK ${BM_GETCHECK} 0 0 $NEED_GSTCOMSDK
51 | FunctionEnd
52 |
--------------------------------------------------------------------------------
/windows/detect.nsi:
--------------------------------------------------------------------------------
1 | ;---------------------------------------------------------------------
2 | ; ROUTINES TO DETECT PYTHON, PYGTK, PYGOBJECT, PYCAIRO and TCL/TK.
3 | ; - Taken from ASCEND NSIS installer (http://www.ascend4.org/)
4 |
5 | ;---------------------------------------------------------------------
6 | ; Look for Python in specific directory
7 |
8 | Function DetectPython
9 |
10 | ; TODO: Really, we should be supporting more than one version here, but
11 | ; given the python support from GST only supports 2.6... nope.
12 |
13 | ; TODO: Really, assuming that the python path is a fixed location is
14 | ; a bit broken, but this installer is really geared towards non-technical
15 | ; users anyways, so the chances of them having it installed in a
16 | ; non-default location is.. low, right? *hides from bugreports*
17 |
18 | ${If} ${FileExists} "${PYTHON_PATH}\python.exe"
19 | StrCpy $HAVE_PYTHON "OK"
20 | ${Else}
21 | ;MessageBox MB_OK "No python.exe in $R6"
22 | StrCpy $HAVE_PYTHON "NOK"
23 | ${EndIf}
24 | FunctionEnd
25 |
26 | ;--------------------------------------------------------------------
27 | ; GStreamer.com SDK package detection
28 |
29 | Function DetectGstreamerComSDK
30 | ReadEnvStr $0 GSTREAMER_SDK_ROOT_X86
31 | ${IfNot} $0 == ""
32 | StrCpy $HAVE_GSTCOMSDK "OK"
33 | ${Else}
34 | StrCpy $HAVE_GSTCOMSDK "NOK"
35 | ${EndIf}
36 |
37 | ; TODO: What if they don't select the correct options?
38 | FunctionEnd
39 |
40 |
--------------------------------------------------------------------------------
/windows/download.nsi:
--------------------------------------------------------------------------------
1 | ;---------------------------------------------------------------------
2 | ; - Taken from ASCEND NSIS installer (http://www.ascend4.org/)
3 |
4 | Var DAI_RET
5 | Var DAI_MSG
6 | Var DAI_TMPFILE
7 | Var DAI_REMOVE
8 |
9 | !macro downloadAndInstall DAI_NAME DAI_URL DAI_FN DAI_CMD
10 | Push $0
11 | Push $1
12 |
13 | StrCpy $DAI_RET ""
14 | StrCpy $DAI_REMOVE ""
15 |
16 | ${If} ${FileExists} "${DAI_FN}"
17 | DetailPrint "Found local file ${DAI_FN}..."
18 | ${If} ${Cmd} `MessageBox MB_ICONQUESTION|MB_YESNO "File ${DAI_FN} was found in the current directory, so it may not be necessary to download it now.$\n$\nWould you like to run this local copy of the installer?" IDYES `
19 | StrCpy $DAI_RET "success"
20 | StrCpy $DAI_TMPFILE "${DAI_FN}"
21 | ${EndIf}
22 | ${EndIf}
23 |
24 | ${If} $DAI_RET != "success"
25 | DetailPrint "Downloading file ${DAI_FN}..."
26 | DetailPrint "URL: ${DAI_URL}"
27 | StrCpy $DAI_TMPFILE "$TEMP\${DAI_FN}"
28 |
29 | ; Download files using the INETC plugin for NSIS, available from
30 | ; http://nsis.sourceforge.net/Inetc_plug-in
31 | inetc::get /CAPTION "${DAI_FN}" /USERAGENT "Mozilla/5.0 NSIS_Inetc (Pithos Installer)" "${DAI_URL}" "$DAI_TMPFILE" /END
32 | Pop $DAI_RET ; return value = exit code, "OK" means OK
33 |
34 | ${DoWhile} $DAI_RET != "OK"
35 | ${If} $DAI_RET == "cancel"
36 | StrCpy $DAI_MSG "cancelled"
37 | ${Else}
38 | StrCpy $DAI_MSG "failed (return '$DAI_RET')"
39 | ${EndIf}
40 |
41 | DetailPrint "Download of ${DAI_FN} $DAI_MSG."
42 | ${IfNot} ${Cmd} `MessageBox MB_ICONEXCLAMATION|MB_YESNO "${DAI_NAME} download $DAI_MSG. URL was:$\n$\n${DAI_URL}$\n$\nDo you wish to re-attempt the download?" IDYES `
43 | ; response was no
44 | ;MessageBox MB_OK "File ${DAI_NAME} will not be installed..."
45 | Pop $1
46 | Pop $0
47 | Push 1 ; error code
48 | Return
49 | ${EndIf}
50 |
51 | ;MessageBox MB_OK "Will re-attempt download of ${DAI_NAME}"
52 | ${If} ${FileExists} "$DAI_TMPFILE"
53 | Delete "$DAI_TMPFILE"
54 | ${EndIf}
55 | ${Loop}
56 |
57 | StrCpy $DAI_REMOVE "1"
58 | ${EndIf}
59 |
60 |
61 | ;MessageBox MB_OK "Installing ${DAI_NAME}...$\n$\nCommand: ${DAI_CMD}"
62 | DetailPrint "Installing ${DAI_NAME} (${DAI_FN})"
63 | ExecWait "${DAI_CMD}" $0
64 | DetailPrint "Installer return code = $0"
65 | ${If} $0 != "0"
66 | MessageBox MB_ICONEXCLAMATION|MB_OK "${DAI_NAME} installer returned a non-zero error code '$0'"
67 | Pop $1
68 | Pop $0
69 | Push 1 ; error code
70 | ${Else}
71 | Pop $1
72 | Pop $0
73 | Push 0 ; error code
74 | ${EndIf}
75 |
76 | ${If} $DAI_REMOVE != ""
77 | ;MessageBox MB_OK "Deleting $DAI_TMPFILE..."
78 | Delete "$DAI_TMPFILE"
79 | ${EndIf}
80 |
81 | !macroend
--------------------------------------------------------------------------------
/windows/install.nsi:
--------------------------------------------------------------------------------
1 | ;Pithos for Windows installer script
2 | ;Modified by TingPing
3 | ;Based on Exaile Windows installer script
4 | ;Modified by Dustin Spicuzza
5 | ;Based on the Quod Libet / Ex Falso Windows installer script
6 | ;Modified by Steven Robertson
7 | ;Based on the NSIS Modern User Interface Start Menu Folder Example Script
8 | ;Written by Joost Verburg
9 |
10 | ;compression
11 | SetCompressor /SOLID LZMA
12 |
13 | !define MULTIUSER_EXECUTIONLEVEL Highest
14 | !define MULTIUSER_MUI
15 | !define MULTIUSER_INSTALLMODE_COMMANDLINE
16 |
17 | !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pithos"
18 | !define INSTDIR_KEY "Software\Pithos"
19 | !define INSTDIR_SUBKEY "InstDir"
20 |
21 | ;--------------------------------
22 | ;Include Modern UI and other libs
23 |
24 | !include "MUI2.nsh"
25 | !include "LogicLib.nsh"
26 |
27 | ;--------------------------------
28 | ;General
29 |
30 | ;Name and file
31 | Name "Pithos"
32 | OutFile "pithos_installer.exe"
33 |
34 | ;Default installation folder
35 | InstallDir "$PROGRAMFILES\Pithos"
36 |
37 | ;Get installation folder from registry if available
38 | ;InstallDirRegKey HKCU "${INSTDIR_KEY}" ""
39 | ;doesn't work with multi user -> see onInit..
40 |
41 | ;Request application privileges for Windows Vista+
42 | RequestExecutionLevel admin
43 |
44 | ;--------------------------------
45 | ;Variables
46 |
47 | Var StartMenuFolder
48 | Var instdir_temp
49 |
50 | Var HAVE_PYTHON
51 | Var HAVE_GSTCOMSDK
52 |
53 | Var NEED_PYTHON
54 | Var NEED_GSTCOMSDK
55 |
56 |
57 | ;--------------------------------
58 | ;Interface Settings
59 |
60 | !define MUI_ABORTWARNING
61 | !define MUI_ICON "..\data\icons\pithos-small.ico"
62 |
63 | ;--------------------------------
64 | ;Pages
65 |
66 | !insertmacro MUI_PAGE_LICENSE "..\LICENSE"
67 | !insertmacro MUI_PAGE_DIRECTORY
68 |
69 | Page custom dependenciesCreate dependenciesLeave
70 |
71 | ;Start Menu Folder Page Configuration
72 | !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU"
73 | !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\Pithos"
74 | !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
75 |
76 | !insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder
77 |
78 | !insertmacro MUI_PAGE_INSTFILES
79 |
80 | !insertmacro MUI_UNPAGE_CONFIRM
81 | !insertmacro MUI_UNPAGE_INSTFILES
82 |
83 | ;------------------------------------------------------------
84 | ; DOWNLOAD AND INSTALL DEPENDENCIES FIRST
85 | ;!define TEST_URL ""
86 |
87 | ; Use the official python.org Python packages
88 | !define PYTHON_VERSION "2.7"
89 | !define PYTHON_FULL_VERSION "2.7.3"
90 | !define PYTHON_PATH "C:\Python27"
91 | !define PYTHON_FN "python-${PYTHON_FULL_VERSION}.msi"
92 | !define PYTHON_FSIZE "15MB"
93 | !define PYTHON_URL "http://python.org/ftp/python/${PYTHON_FULL_VERSION}/${PYTHON_FN}"
94 | ;!define PYTHON_URL "${TEST_URL}/${PYTHON_FN}"
95 | !define PYTHON_CMD "msiexec /i $DAI_TMPFILE /passive ALLUSERS=1"
96 |
97 | ; Use the GStreamer.com SDK
98 | !define GSTCOMSDK_VERSION "2012.9"
99 | !define GSTCOMSDK_FN "gstreamer-sdk-x86-${GSTCOMSDK_VERSION}.msi"
100 | !define GSTCOMSDK_FSIZE "97MB"
101 | !define GSTCOMSDK_URL "http://www.freedesktop.org/software/gstreamer-sdk/data/packages/windows/x86/${GSTCOMSDK_FN}"
102 | ;!define GSTCOMSDK_URL "${TEST_URL}/${GSTCOMSDK_FN}"
103 | !define GSTCOMSDK_FEATURES "_gstreamer_core,_gstreamer_system,_gstreamer_playback,_gstreamer_codecs,_gstreamer_networking,_gstreamer_python,_gtk__2.0,_gtk__2.0_python,_gstreamer_codecs_restricted,_gstreamer_networking_restricted"
104 | !define GSTCOMSDK_CMD "msiexec /i $DAI_TMPFILE /passive ALLUSERS=1 ADDLOCAL=${GSTCOMSDK_FEATURES}"
105 |
106 | !include "download.nsi"
107 |
108 | Section "-python"
109 | ${If} $NEED_PYTHON == '1'
110 | DetailPrint "--- DOWNLOAD PYTHON ---"
111 | !insertmacro downloadAndInstall "Python" "${PYTHON_URL}" "${PYTHON_FN}" "${PYTHON_CMD}"
112 | Call DetectPython
113 | ${If} $HAVE_PYTHON == 'NOK'
114 | MessageBox MB_OK "Python installation appears to have failed. You may need to retry manually."
115 | ${EndIf}
116 | ${EndIf}
117 | SectionEnd
118 |
119 | Section "-gstcomsdk"
120 | ${If} $NEED_GSTCOMSDK == '1'
121 | DetailPrint "--- DOWNLOAD GSTREAMER.COM SDK ---"
122 | !insertmacro downloadAndInstall "GStreamer.com SDK" "${GSTCOMSDK_URL}" "${GSTCOMSDK_FN}" "${GSTCOMSDK_CMD}"
123 | Pop $0
124 | ${If} $0 != "0"
125 | MessageBox MB_OK "GStreamer.com SDK installation appears to have failed. You may need to retry manually."
126 | ${EndIf}
127 | ${EndIf}
128 |
129 | SectionEnd
130 |
131 | ;------------------------------------------------------------
132 | ; Install Pithos last
133 |
134 | Section "-Pithos" SecPithos
135 |
136 | SetOutPath "$INSTDIR"
137 |
138 | File /r "..\data"
139 | File /r "..\pithos"
140 | File /r "..\pithos.pyw"
141 | File /r "..\pithos.bat"
142 |
143 | ;Store installation folder
144 | WriteRegStr SHCTX "${INSTDIR_KEY}" "${INSTDIR_SUBKEY}" $INSTDIR
145 |
146 | ;Multi user uninstaller stuff
147 | ;WriteRegStr SHCTX "${UNINST_KEY}" \
148 | ;"DisplayName" "Pithos - Pandora desktop client."
149 | ;WriteRegStr SHCTX "${UNINST_KEY}" "DisplayIcon" "$\"$INSTDIR\data\icons\pithos-small.ico$\""
150 | ;WriteRegStr SHCTX "${UNINST_KEY}" "UninstallString" \
151 | ;"$\"$INSTDIR\uninstall.exe$\" /$MultiUser.InstallMode"
152 | ;WriteRegStr SHCTX "${UNINST_KEY}" "QuietUninstallString" \
153 | ;"$\"$INSTDIR\uninstall.exe$\" /$MultiUser.InstallMode /S"
154 |
155 | ;Create uninstaller
156 | WriteUninstaller "$INSTDIR\uninstall.exe"
157 |
158 | !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
159 |
160 | ;Create shortcuts
161 | CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
162 | CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Pithos.lnk" "$INSTDIR\pithos.bat" "" "$INSTDIR\data\icons\pithos-large.ico"
163 |
164 | !insertmacro MUI_STARTMENU_WRITE_END
165 |
166 | SectionEnd
167 |
168 |
169 | !include "dependencies.nsi"
170 |
171 | !include "detect.nsi"
172 |
173 | ;--------------------------------
174 | ;Uninstaller Section
175 |
176 | Section "Uninstall"
177 |
178 | RMDir /r "$INSTDIR"
179 |
180 | Delete "$INSTDIR\uninstall.exe"
181 |
182 | !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder
183 |
184 | Delete "$SMPROGRAMS\$StartMenuFolder\Pithos.lnk"
185 | RMDir "$SMPROGRAMS\$StartMenuFolder"
186 |
187 | DeleteRegKey SHCTX "${UNINST_KEY}"
188 | DeleteRegKey SHCTX "${INSTDIR_KEY}"
189 |
190 | SectionEnd
191 |
192 |
--------------------------------------------------------------------------------
/windows/redist.txt:
--------------------------------------------------------------------------------
1 | -- for plugins (mediakeys, growl) --
2 | http://sourceforge.net/projects/pyhook/files/
3 | http://sourceforge.net/projects/pywin32/files/pywin32/
4 | https://github.com/kfdm/gntp/
5 |
--------------------------------------------------------------------------------