Merge lp://staging/~rockstar/entertainer/package-structure-apocalypse into lp://staging/entertainer/future

Proposed by Paul Hummer
Status: Merged
Approved by: Paul Hummer
Approved revision: 382
Merged at revision: not available
Proposed branch: lp://staging/~rockstar/entertainer/package-structure-apocalypse
Merge into: lp://staging/entertainer/future
Diff against target: None lines
To merge this branch: bzr merge lp://staging/~rockstar/entertainer/package-structure-apocalypse
Reviewer Review Type Date Requested Status
Samuel Buffet (community) Approve
Matt Layman Approve
Review via email: mp+6249@code.staging.launchpad.net

Commit message

Entertainer's package structure has been re-organized

To post a comment you must log in.
Revision history for this message
Paul Hummer (rockstar) wrote :

This branch is the apocalyptical package structure changes. entertainerlib.backend will disappear as soon as the new indexer lands. There are all sorts of changes here. The only real code change was the complete removal of entertainerlib.utils.cd_utils, because there was a single line single function, which I easily removed. Otherwise, I consolidated a lot of packages into single modules, and flattened the tree a bunch.

This specifically needs to land in future, because it's unnecessary when there is still a backend/frontend. Frontend was renamed to client, and will become more of a client in later branches. entertainerlib.utils was removed, and its contents shifted around.

This is probably going to cause a lot of conflicts, which is another big reason why this needs to land in future. The future branch is there solely to be liberal in the changes that need to be made, and, well, these changes are quite liberal. Tests, lint, and running entertainer all indicate everything is good, but the jury is still out, and will require A LOT of changes.

Revision history for this message
Samuel Buffet (samuel-buffet) wrote :

Hi Paul,

Good to see Entertainer becoming a client/server application.

I have some points to discuss with you before I go on deeper in the review. Most of them are related with the tree structure you've chosen.

Under entertainerlib we have :

backend
client
db
glade
gui
network
test

My questions are.

Why db isn't under backend? and why gui is not under client?

I think it's more readable and more logic because gui which contains widgets/screens ... are all used only by the *client* and *db* is used by the server (I guess).

You write that you're about to remove entertainerlib.backend but I don't understand right now why you don't rename it in entertainerlib.server? Does that mean that the server's code will take place elsewhere?

That's the first set of questions I have taking a first look to this branch.

Samuel-

Note :

Entertainer is broken if the notification icon is activated. See below the traceback :

(/mnt/raid/Programmes/branches_entertainer/package-structure-apocalypse/entertainer:31123): libglade-WARNING **: could not find glade file '/mnt/raid/Programmes/branches_entertainer/package-structure-apocalypse/entertainerlib/gui/glade/system_tray_icon_menu.glade'
Traceback (most recent call last):
  File "/mnt/raid/Programmes/branches_entertainer/package-structure-apocalypse/entertainer", line 12, in <module>
    main()
  File "/mnt/raid/Programmes/branches_entertainer/package-structure-apocalypse/entertainerlib/client/__init__.py", line 33, in main
    client_client = Client()
  File "/mnt/raid/Programmes/branches_entertainer/package-structure-apocalypse/entertainerlib/client/client.py", line 51, in __init__
    self.quit_client, self.toggle_interface_visibility)
  File "/mnt/raid/Programmes/branches_entertainer/package-structure-apocalypse/entertainerlib/gui/system_tray_icon.py", line 42, in __init__
    os.path.join(self.GLADE_DIR, "system_tray_icon_menu.glade"))
RuntimeError: could not create GladeXML object

Revision history for this message
Matt Layman (mblayman) wrote :

> Why db isn't under backend? and why gui is not under client?
>
> I think it's more readable and more logic because gui which contains
> widgets/screens ... are all used only by the *client* and *db* is used by the
> server (I guess).

I can't speak for db, but moving gui out of frontend was my request. I know that the frontend is currently the only thing that uses the gui module, but there is no true reason that gui needs to be a sub-module of frontend (it could theoretically be totally independent of the frontend). Moving gui out of frontend flattens the depth of the tree which I think is a good thing here. I have a feeling Paul would say the same thing about the db and the backend.

> Note :
> Entertainer is broken if the notification icon is activated. See below the
> traceback :

I agree that this needs to be fixed before the branch lands.

Revision history for this message
Matt Layman (mblayman) wrote :

Paul,

Here are all my comments that I recorded as I went through the diff:

What was the rationale behind putting all the dialog classes into a single file? I would suggest that we at least call it dialogs if there are going to be multiple, but it doesn't make too much sense to me to put them all in the same file, especially if our dialogs might only grow more capabilities over time (like TV stuff).

entertainerlib/backend/core/message_type_priority.py
 * FRONTEND_OPENED and FRONTEND_CLOSED should probably change to CLIENT_*. The comments with those lines don't make sense because frontend was substituted with client.

entertainerlib/client/__init__.py
 * Initial comment isn't valid.
 * client_client is weird.

entertainerlib/client/client.py
 * import order
 * class doc string has Frontend
 * logger name isn't correct, would client.Client

entertainerlib/client/media_player.py
 * import order

entertainerlib/client/medialibrary/music.py
 * import order

entertainerlib/client/medialibrary/videos.py
 * import order

entertainerlib/dialog.py
 * import order

entertainerlib/gui/screens/artist.py
 * import order

entertainerlib/gui/screens/disc.py
 * import order

entertainerlib/gui/screens/feed.py
 * import order

entertainerlib/gui/screens/feed_entry.py
 * import order

entertainerlib/gui/screens/main.py
 * import order

entertainerlib/gui/screens/music.py
 * import order

entertainerlib/gui/screens/rss.py
 * import order

entertainerlib/gui/system_tray_icon.py
 * Switch from frontend to client revealed some code that must is broken. set_client_visible has no implementation.

entertainerlib/gui/transitions/factory.py
 * import order

entertainerlib/gui/transitions/slide.py
 * import order

entertainerlib/gui/user_interface.py
 * import order
 * Uses UserEvent.QUIT_FRONTEND. That event should change to QUIT_CLIENT.

entertainerlib/gui/widgets/image_menu.py
 * import order

entertainerlib/gui/widgets/list_indicator.py
 * import order

entertainerlib/gui/widgets/tab_group.py
 * import order

entertainerlib/gui/widgets/text_menu.py
 * import order

entertainerlib/gui/widgets/texture.py
 * logger name doesn't need to refer to client anymore

entertainerlib/tests/test_logger.py
 * import order

entertainerlib/tests/test_theme.py
 * import order

entertainerlib/thumbnailer.py
 * import order

entertainerlib/utils/cd_utils.py
 * Removed, so you can link to bug 313815

setup.py
 * There is no longer a client/glade directory so 'client/glade/*' shouldn't be needed anymore.

Revision history for this message
Paul Hummer (rockstar) wrote :

On Thu, 07 May 2009 19:59:33 -0000, Samuel Buffet <email address hidden>
> My questions are.
>
> Why db isn't under backend? and why gui is not under client?

db is a core part of entertainer. I tried to make thinngs more easily
accessible, and once I get the network stuff in, it won't be used by just one
package.

> You write that you're about to remove entertainerlib.backend but I don't
> understand right now why you don't rename it in entertainerlib.server? Does
> that mean that the server's code will take place elsewhere?

Because there won't be just one entertainer server. There will be with 0.5,
but the end goal is to provide more options. I figure something like
entertainerlib.network.local.Server and entertainerlib.network.dlna.Server,
etc.

Now you'll see why db didn't stay in the backend.

Also, most of the backend code is pure crap now, being replaced, and will be
completely removed. THAT's why I didn't move it to a new location. :)

>> Note :
>
> Entertainer is broken if the notification icon is activated. See below the
> traceback :
>
> (/mnt/raid/Programmes/branches_entertainer/package-structure-apocalypse/entertainer:31123):
> libglade-WARNING **: could not find glade file
> '/mnt/raid/Programmes/branches_entertainer/package-structure-apocalypse/entertainerlib/gui/glade/system_tray_icon_menu.glade'
> Traceback (most recent call last): File
> "/mnt/raid/Programmes/branches_entertainer/package-structure-apocalypse/entertainer",
> line 12, in <module> main() File
> "/mnt/raid/Programmes/branches_entertainer/package-structure-apocalypse/entertainerlib/client/__init__.py",
> line 33, in main client_client = Client() File
> "/mnt/raid/Programmes/branches_entertainer/package-structure-apocalypse/entertainerlib/client/client.py",
> line 51, in __init__ self.quit_client, self.toggle_interface_visibility)
> File
> "/mnt/raid/Programmes/branches_entertainer/package-structure-apocalypse/entertainerlib/gui/system_tray_icon.py",
> line 42, in __init__ os.path.join(self.GLADE_DIR,
> "system_tray_icon_menu.glade")) RuntimeError: could not create GladeXML
> object
>

I don't know yet what to with the system tray icon. No one uses it, it
doesn't really fit in with what Entertainer is (a media center, not a desktop
application) and I currently don't have any plans to support it in the local
server.

--
Paul Hummer
http://theironlion.net
1024/862FF08F C921 E962 58F8 5547 6723 0E8C 1C4D 8AC5 862F F08F

Revision history for this message
Paul Hummer (rockstar) wrote :
Download full text (8.0 KiB)

> What was the rationale behind putting all the dialog classes into a single
> file? I would suggest that we at least call it dialogs if there are going
> to be multiple, but it doesn't make too much sense to me to put them all in
> the same file, especially if our dialogs might only grow more capabilities
> over time (like TV stuff).

They depend on similar things. As I was working on everything else, it only
made sense to consolidate those dialogs. I think the next logical step would
be to create an EntertainerDialog, and extend the dialogs that way. That code
is an utter mess. I think, for the time being, it should stay as the same
module, and get it cleaned up. If (and this is a big IF) we see a need to make
a package out of this, we can be confident that the code is being maintained.

> entertainerlib/backend/core/message_type_priority.py
> * FRONTEND_OPENED and FRONTEND_CLOSED should probably change to CLIENT_*.
> The comments with those lines don't make sense because frontend was
> substituted with client.

Nope, I'm not touching that message passing crap. It never worked right, and
will just get deleted when the server is implemented.

>
> entertainerlib/client/__init__.py
> * Initial comment isn't valid.
> * client_client is weird.

Yeah, I thought that too. client.client_client vs client.client, either way,
it's a bit repetitive. I was going to see if you wanted to root around in the
client code and see what you'd need.

>
> entertainerlib/client/client.py
> * import order
> * class doc string has Frontend
> * logger name isn't correct, would client.Client

I've made the changes to everything but import order. The import order will be
re-arranged before this lands in trunk, but the imports are going to be hell to
merge from trunk as it is. I'd rather do a second branch with the import order
stuff. This is the beauty of having this HIGHLY experimental branch. I think
this is an acceptable solution.

> entertainerlib/gui/system_tray_icon.py
> * Switch from frontend to client revealed some code that must is broken.
> set_client_visible has no implementation.

I don't plan on fixing this in this branch. If it's broken, it's broken. It
may not even be in the next version of Entertainer.

> entertainerlib/gui/user_interface.py
> * import order
> * Uses UserEvent.QUIT_FRONTEND. That event should change to QUIT_CLIENT.

I changed this to just QUIT. These events are only used by the client anyway,
so QUIT_CLIENT is rather redundant.

> entertainerlib/gui/widgets/texture.py
> * logger name doesn't need to refer to client anymore

Fixed.

> entertainerlib/utils/cd_utils.py
> * Removed, so you can link to bug 313815

Sweet, thanks. I figured there was a bug, but I was too lazy to go find it.
:)

>
> setup.py
> * There is no longer a client/glade directory so 'client/glade/*'
> shouldn't be needed anymore.

Fixed.

Here's the incremental diff:

=== modified file 'entertainerlib/client/__init__.py'
--- entertainerlib/client/__init__.py 2009-05-05 04:43:57 +0000
+++ entertainerlib/client/__init__.py 2009-05-09 03:14:44 +0000
@@ -1,8 +1,8 @@
-'''Frontend gui to entertainer'''
+'''Client code for Entertainer.'''
 # py...

Read more...

382. By Paul Hummer

Responded to Matt's review

Revision history for this message
Samuel Buffet (samuel-buffet) wrote :

Hi Paul,

I'm going to approve this branch as I can see on it the seeds of Entertainer's future.

> Also, most of the backend code is pure crap now, being replaced, and will be
> completely removed. THAT's why I didn't move it to a new location. :)

Okay, with those explanations.

> I don't know yet what to with the system tray icon. No one uses it, it
> doesn't really fit in with what Entertainer is (a media center, not a desktop
> application) and I currently don't have any plans to support it in the local
> server.

Well, I do use it. Also, I think this is not good at all to have *broken* code.
So it turns out to me that either we have to fix this or we remove all the code
related to this notification icon. But, as we're not really sure of the future of
this feature and as fixing this should be rather trivial, I'd prefer the fix
solution.

> They depend on similar things. As I was working on everything else, it only
> made sense to consolidate those dialogs. I think the next logical step would
> be to create an EntertainerDialog, and extend the dialogs that way. That code
> is an utter mess. I think, for the time being, it should stay as the same
> module, and get it cleaned up. If (and this is a big IF) we see a need to
> make
> a package out of this, we can be confident that the code is being maintained.

I'm not sure it's a good idea. I tempted to say "+1 with Matt".
Even if the *one Class per file* rule is nonsense sometime but here ...??

>=== added file 'entertainerlib/download.py'
>--- entertainerlib/download.py 1970-01-01 00:00:00 +0000
>+++ entertainerlib/download.py 2009-05-07 19:37:31 +0000
>@@ -0,0 +1,453 @@
>+'''Downloader classes.'''
>+
>+__licence__ = "GPLv2"
>+__copyright__ = "2009 Entertainer Developers"
>+

I like the idea to group our downloaders. What about weather downloader? Is it
planned in the future?

> self.window.connect('destroy', self.destroy_callback)
>@@ -53,7 +53,7 @@
> except gobject.GError:
> # Must not be installed from a package, get icon from the branch
> file_dir = os.path.dirname(__file__)
>- icon_path = os.path.join(file_dir, '..', '..', '..', 'icons',
>+ icon_path = os.path.join(file_dir, '..', '..', 'icons',
> 'hicolor', '48x48', 'apps', 'entertainer.png')
> icon = gtk.gdk.pixbuf_new_from_file(icon_path)
> self.window.set_icon(icon)

(just me thinking loudly) => If we want to improve our flexibility with folder
tree modifications, we have to find a way to avoid those '..', '..'.
What about adding more xxx_dir (root_dir, data_dir, etc). Those could be calculated
attributes of our config object.

Thanks for the great work Paul. Entertainer will rock with the future client/server
architecture and a robust and powerfull indexer.

Samuel-

Revision history for this message
Matt Layman (mblayman) wrote :

After looking over the conversations in this thread, I've concluded that I have to think differently about my normal standards and realize that this isn't trunk. I do wonder how we can capture the problems that start to arise as we merge branches onto future. Some problems will go away when future gets closer to merging into trunk, but what if we just forget about some of these things and merge them onto trunk by accident. That's my one reservation about approving this branch.

However, I do like the bulk of this work and since it is such a disruptive change, I would rather it land sooner than later. Therefore, I'm going to approve in spite of my concerns.

review: Approve
Revision history for this message
Samuel Buffet (samuel-buffet) wrote :

Go Paul! Go!

This is really something we need and like Matt said, this is not trunk. So you have a STRONG approval from me on this work. I can add that I'm very happy with the goal of this branch.

Cheers,
Samuel-

review: Approve
383. By Paul Hummer

Merged from entertainer-future

384. By Paul Hummer

Added copyright stuff to the new files

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'entertainer'
2--- entertainer 2008-10-25 16:41:22 +0000
3+++ entertainer 2009-05-06 03:37:34 +0000
4@@ -1,12 +1,12 @@
5 #!/usr/bin/env python
6-'''Main frontend executable'''
7+'''Main client executable'''
8
9 __licence__ = "GPLv2"
10 __copyright__ = "2007, Lauri Taimila"
11 __author__ = "Lauri Taimila <lauri@taimila.com>"
12 __version__ = "0.2"
13
14-from entertainerlib.frontend import main
15+from entertainerlib.client import main
16
17 if __name__ == '__main__':
18 main()
19
20=== modified file 'entertainer-client'
21--- entertainer-client 2009-04-04 05:30:04 +0000
22+++ entertainer-client 2009-05-06 03:37:34 +0000
23@@ -2,7 +2,7 @@
24 '''Test client for Entertainer's server.
25
26 This code will go away when the client code is integrated into what is now the
27-frontend.
28+client.
29 '''
30
31 from entertainerlib.network import client_main
32
33=== modified file 'entertainer-content-manager'
34--- entertainer-content-manager 2008-12-19 04:06:18 +0000
35+++ entertainer-content-manager 2009-05-06 03:37:34 +0000
36@@ -3,11 +3,10 @@
37
38 import gtk
39
40-from entertainerlib.frontend.translation_setup import TranslationSetup
41+from entertainerlib.client.translation_setup import TranslationSetup
42 TranslationSetup()
43
44-from entertainerlib.utils.content_management_dialog import (
45- ContentManagementDialog)
46+from entertainerlib.dialog import ContentManagementDialog
47
48
49 ContentManagementDialog(True)
50
51=== modified file 'entertainer-preferences'
52--- entertainer-preferences 2008-12-19 04:06:18 +0000
53+++ entertainer-preferences 2009-05-06 03:37:34 +0000
54@@ -3,10 +3,10 @@
55
56 import gtk
57
58-from entertainerlib.frontend.translation_setup import TranslationSetup
59+from entertainerlib.client.translation_setup import TranslationSetup
60 TranslationSetup()
61
62-from entertainerlib.utils.preferences_dialog import PreferencesDialog
63+from entertainerlib.dialog import PreferencesDialog
64
65
66 PreferencesDialog(True)
67
68=== modified file 'entertainerlib/backend/backend_server.py'
69--- entertainerlib/backend/backend_server.py 2009-02-08 07:01:25 +0000
70+++ entertainerlib/backend/backend_server.py 2009-05-05 04:43:57 +0000
71@@ -8,8 +8,8 @@
72
73 import gobject
74
75-from entertainerlib.utils.configuration import Configuration
76-from entertainerlib.utils.logger import Logger
77+from entertainerlib.configuration import Configuration
78+from entertainerlib.logger import Logger
79
80 # Entertainer backend core
81 from entertainerlib.backend.core.message import Message
82
83=== modified file 'entertainerlib/backend/components/feeds/feed_fetcher.py'
84--- entertainerlib/backend/components/feeds/feed_fetcher.py 2009-02-07 21:43:35 +0000
85+++ entertainerlib/backend/components/feeds/feed_fetcher.py 2009-05-05 04:43:57 +0000
86@@ -10,8 +10,8 @@
87 from datetime import datetime
88 from pysqlite2 import dbapi2 as sqlite
89
90-from entertainerlib.utils.configuration import Configuration
91-from entertainerlib.utils.logger import Logger
92+from entertainerlib.configuration import Configuration
93+from entertainerlib.logger import Logger
94
95 # Messaging system
96 from entertainerlib.backend.core.message import Message
97
98=== modified file 'entertainerlib/backend/components/feeds/feed_manager.py'
99--- entertainerlib/backend/components/feeds/feed_manager.py 2008-08-15 23:52:30 +0000
100+++ entertainerlib/backend/components/feeds/feed_manager.py 2009-05-05 04:43:57 +0000
101@@ -8,8 +8,8 @@
102 from pysqlite2 import dbapi2 as sqlite
103 from entertainerlib.backend.components.feeds.feed_fetcher import FeedFetcher
104
105-from entertainerlib.utils.configuration import Configuration
106-from entertainerlib.utils.logger import Logger
107+from entertainerlib.configuration import Configuration
108+from entertainerlib.logger import Logger
109
110 # Messaging system
111 from entertainerlib.backend.core.message_type_priority import MessageType
112
113=== renamed file 'entertainerlib/utils/feed_utils.py' => 'entertainerlib/backend/components/feeds/feed_utils.py'
114--- entertainerlib/utils/feed_utils.py 2009-02-08 07:43:25 +0000
115+++ entertainerlib/backend/components/feeds/feed_utils.py 2009-05-06 04:00:53 +0000
116@@ -11,7 +11,7 @@
117 import gtk
118 import gtk.glade
119
120-from entertainerlib.utils.configuration import Configuration
121+from entertainerlib.configuration import Configuration
122
123
124 class FeedEntryParser:
125
126=== modified file 'entertainerlib/backend/components/mediacache/image_cache.py'
127--- entertainerlib/backend/components/mediacache/image_cache.py 2009-02-19 14:30:24 +0000
128+++ entertainerlib/backend/components/mediacache/image_cache.py 2009-05-05 04:43:57 +0000
129@@ -11,8 +11,8 @@
130 from pysqlite2 import dbapi2 as sqlite
131
132 from entertainerlib.thumbnailer import ImageThumbnailer
133-from entertainerlib.utils.configuration import Configuration
134-from entertainerlib.utils.logger import Logger
135+from entertainerlib.configuration import Configuration
136+from entertainerlib.logger import Logger
137
138 from entertainerlib.backend.components.mediacache.cache import Cache
139
140
141=== modified file 'entertainerlib/backend/components/mediacache/media_cache_manager.py'
142--- entertainerlib/backend/components/mediacache/media_cache_manager.py 2009-01-06 23:51:38 +0000
143+++ entertainerlib/backend/components/mediacache/media_cache_manager.py 2009-05-05 04:43:57 +0000
144@@ -4,8 +4,8 @@
145 __copyright__ = "2007, Lauri Taimila"
146 __author__ = "Lauri Taimila <lauri@taimila.com>"
147
148-from entertainerlib.utils.configuration import Configuration
149-from entertainerlib.utils.logger import Logger
150+from entertainerlib.configuration import Configuration
151+from entertainerlib.logger import Logger
152
153 from entertainerlib.backend.core.message_type_priority import MessageType
154 from entertainerlib.backend.core.message_handler import MessageHandler
155@@ -16,7 +16,7 @@
156 from entertainerlib.backend.components.mediacache.video_cache import VideoCache
157
158 class MediaCacheManager(MessageHandler):
159- """Makes sure that frontend has all the data available."""
160+ """Makes sure that client has all the data available."""
161
162 def __init__(self):
163 """
164
165=== modified file 'entertainerlib/backend/components/mediacache/music_cache.py'
166--- entertainerlib/backend/components/mediacache/music_cache.py 2009-04-24 20:41:49 +0000
167+++ entertainerlib/backend/components/mediacache/music_cache.py 2009-05-06 04:46:51 +0000
168@@ -12,11 +12,11 @@
169 import ogg.vorbis
170 from pysqlite2 import dbapi2 as sqlite
171
172-from entertainerlib.utils.albumart_downloader import AlbumArtDownloader
173-from entertainerlib.utils.configuration import Configuration
174-from entertainerlib.utils.logger import Logger
175-
176 from entertainerlib.backend.components.mediacache.cache import Cache
177+from entertainerlib.configuration import Configuration
178+from entertainerlib.download import AlbumArtDownloader
179+from entertainerlib.logger import Logger
180+
181
182 class MusicCache(Cache):
183 """
184
185=== modified file 'entertainerlib/backend/components/mediacache/video_cache.py'
186--- entertainerlib/backend/components/mediacache/video_cache.py 2009-02-19 14:30:24 +0000
187+++ entertainerlib/backend/components/mediacache/video_cache.py 2009-05-05 04:43:57 +0000
188@@ -9,8 +9,8 @@
189 from pysqlite2 import dbapi2 as sqlite
190
191 from entertainerlib.thumbnailer import VideoThumbnailer
192-from entertainerlib.utils.configuration import Configuration
193-from entertainerlib.utils.logger import Logger
194+from entertainerlib.configuration import Configuration
195+from entertainerlib.logger import Logger
196
197 from entertainerlib.backend.components.mediacache.cache import Cache
198 from entertainerlib.backend.components.mediacache.video_metadata_search import (
199
200=== modified file 'entertainerlib/backend/components/mediacache/video_metadata_search.py'
201--- entertainerlib/backend/components/mediacache/video_metadata_search.py 2009-02-07 22:12:25 +0000
202+++ entertainerlib/backend/components/mediacache/video_metadata_search.py 2009-05-05 04:24:32 +0000
203@@ -12,8 +12,8 @@
204 import threading
205 from pysqlite2 import dbapi2 as sqlite
206
207-from entertainerlib.utils.logger import Logger
208-from entertainerlib.utils.configuration import Configuration
209+from entertainerlib.logger import Logger
210+from entertainerlib.configuration import Configuration
211
212 class VideoMetadataSearch(threading.Thread):
213 """
214
215=== modified file 'entertainerlib/backend/core/client_connection.py'
216--- entertainerlib/backend/core/client_connection.py 2009-02-07 21:43:35 +0000
217+++ entertainerlib/backend/core/client_connection.py 2009-05-05 04:24:32 +0000
218@@ -11,7 +11,7 @@
219 # Messaging system
220 from entertainerlib.backend.core.message_handler import MessageHandler
221
222-from entertainerlib.utils.logger import Logger
223+from entertainerlib.logger import Logger
224
225 class ClientConnection(threading.Thread, MessageHandler):
226 """
227
228=== modified file 'entertainerlib/backend/core/connection_server.py'
229--- entertainerlib/backend/core/connection_server.py 2009-02-06 08:13:48 +0000
230+++ entertainerlib/backend/core/connection_server.py 2009-05-05 04:24:32 +0000
231@@ -10,7 +10,7 @@
232
233 from entertainerlib.backend.core.client_connection import ClientConnection
234
235-from entertainerlib.utils.logger import Logger
236+from entertainerlib.logger import Logger
237
238 class ConnectionServer(threading.Thread):
239 """
240
241=== modified file 'entertainerlib/backend/core/message_bus.py'
242--- entertainerlib/backend/core/message_bus.py 2009-02-06 08:13:48 +0000
243+++ entertainerlib/backend/core/message_bus.py 2009-05-05 04:24:32 +0000
244@@ -5,7 +5,7 @@
245 from entertainerlib.backend.core.message import Message
246 from entertainerlib.backend.core.message_handler import MessageHandler
247 from entertainerlib.backend.core.message_type_priority import MessageType
248-from entertainerlib.utils.logger import Logger
249+from entertainerlib.logger import Logger
250
251 __licence__ = "GPLv2"
252 __copyright__ = "2007, Lauri Taimila"
253
254=== modified file 'entertainerlib/backend/core/message_bus_proxy.py'
255--- entertainerlib/backend/core/message_bus_proxy.py 2009-01-05 03:25:03 +0000
256+++ entertainerlib/backend/core/message_bus_proxy.py 2009-05-05 04:11:48 +0000
257@@ -9,7 +9,7 @@
258 import threading
259 from cStringIO import StringIO
260
261-from entertainerlib.utils.configuration import Configuration
262+from entertainerlib.configuration import Configuration
263
264 class MessageBusProxy(threading.Thread):
265 """
266
267=== modified file 'entertainerlib/backend/core/message_type_priority.py'
268--- entertainerlib/backend/core/message_type_priority.py 2009-01-31 15:46:36 +0000
269+++ entertainerlib/backend/core/message_type_priority.py 2009-04-30 01:11:22 +0000
270@@ -33,10 +33,10 @@
271 # Indicates that Content Management UI has been used to update contents.
272 CONTENT_CONF_UPDATED = 1
273
274- # Indicates that frontend has been opened.
275+ # Indicates that client has been opened.
276 FRONTEND_OPENED = 2
277
278- # Indicates that frontend has been closed.
279+ # Indicates that client has been closed.
280 FRONTEND_CLOSED = 3
281
282 # Indicates that Feed cache has been updated.
283
284=== renamed directory 'entertainerlib/frontend' => 'entertainerlib/client'
285=== modified file 'entertainerlib/client/__init__.py'
286--- entertainerlib/frontend/__init__.py 2009-03-08 03:16:25 +0000
287+++ entertainerlib/client/__init__.py 2009-05-05 04:43:57 +0000
288@@ -5,7 +5,7 @@
289 '''Frontend runner'''
290
291 # Import statements are inside the function so that they aren't imported
292- # every time something from the frontend is imported
293+ # every time something from the client is imported
294
295 # cluttergtk must be imported before the first import of clutter so it
296 # must be imported even though pylint complains about it not being used.
297@@ -14,12 +14,12 @@
298 import gobject
299 import gtk
300
301- from entertainerlib.frontend.translation_setup import TranslationSetup
302+ from entertainerlib.client.translation_setup import TranslationSetup
303 TranslationSetup()
304
305 from entertainerlib.backend.backend_server import BackendServer
306- from entertainerlib.utils.configuration import Configuration
307- from entertainerlib.frontend.frontend_client import FrontendClient
308+ from entertainerlib.configuration import Configuration
309+ from entertainerlib.client.client import Client
310
311 gobject.threads_init()
312 gtk.gdk.threads_init()
313@@ -30,7 +30,7 @@
314 print "Entertainer backend starting..."
315 BackendServer()
316
317- frontend_client = FrontendClient()
318- frontend_client.start()
319+ client_client = Client()
320+ client_client.start()
321
322
323
324=== renamed file 'entertainerlib/frontend/frontend_client.py' => 'entertainerlib/client/client.py'
325--- entertainerlib/frontend/frontend_client.py 2009-02-10 00:59:03 +0000
326+++ entertainerlib/client/client.py 2009-05-05 04:43:57 +0000
327@@ -9,34 +9,34 @@
328
329 import gtk
330
331-from entertainerlib.frontend.backend_connection import BackendConnection
332-from entertainerlib.frontend.gui.user_interface import UserInterface
333-from entertainerlib.frontend.medialibrary.feeds import FeedLibrary
334-from entertainerlib.frontend.medialibrary.music import MusicLibrary
335-from entertainerlib.frontend.medialibrary.images import ImageLibrary
336-from entertainerlib.frontend.medialibrary.videos import VideoLibrary
337-from entertainerlib.utils.configuration import Configuration
338-from entertainerlib.utils.logger import Logger
339-from entertainerlib.utils.system_tray_icon import SystemTrayIcon
340+from entertainerlib.client.backend_connection import BackendConnection
341+from entertainerlib.gui.user_interface import UserInterface
342+from entertainerlib.client.medialibrary.feeds import FeedLibrary
343+from entertainerlib.client.medialibrary.music import MusicLibrary
344+from entertainerlib.client.medialibrary.images import ImageLibrary
345+from entertainerlib.client.medialibrary.videos import VideoLibrary
346+from entertainerlib.configuration import Configuration
347+from entertainerlib.logger import Logger
348+from entertainerlib.gui.system_tray_icon import SystemTrayIcon
349
350-class FrontendClient:
351+class Client:
352 '''
353- Entertainer frontend
354+ Entertainer client
355
356- This is a frontend application of the Entertainer. Frontend is a GUI part
357- that user sees on the screen. This class is a core of the frontend. Frontend
358+ This is a client application of the Entertainer. Frontend is a GUI part
359+ that user sees on the screen. This class is a core of the client. Frontend
360 connects to the backend's messagebus at startup.
361 '''
362
363 def __init__(self):
364 '''
365- Create a new frontend.
366+ Create a new client.
367
368- This initializes all frontend stuff like media librarys, remote
369+ This initializes all client stuff like media librarys, remote
370 control receiver and GUI. After this we just wait user actions.
371 '''
372 config = Configuration()
373- self.logger = Logger().getLogger('frontend.FrontendClient')
374+ self.logger = Logger().getLogger('client.FrontendClient')
375 self.backend_connection = self.initialize_backend_connection()
376 feed_library = FeedLibrary(self.backend_connection)
377 music_library = MusicLibrary(self.backend_connection)
378@@ -44,11 +44,11 @@
379 video_library = VideoLibrary(self.backend_connection)
380 self.ui = UserInterface(
381 feed_library, image_library, music_library, video_library,
382- self.quit_frontend)
383+ self.quit_client)
384
385 if config.tray_icon_enabled():
386 SystemTrayIcon(
387- self.quit_frontend, self.toggle_interface_visibility)
388+ self.quit_client, self.toggle_interface_visibility)
389
390 def start(self):
391 '''Start the necessary main loop.'''
392@@ -65,8 +65,8 @@
393
394 return backend_connection
395
396- def quit_frontend(self):
397- '''Clean up the connection to the backend then close the frontend.'''
398+ def quit_client(self):
399+ '''Clean up the connection to the backend then close the client.'''
400 self.backend_connection.close_connection()
401
402 gtk.main_quit()
403
404=== modified file 'entertainerlib/client/media_player.py'
405--- entertainerlib/frontend/media_player.py 2009-02-08 08:44:46 +0000
406+++ entertainerlib/client/media_player.py 2009-05-05 04:24:32 +0000
407@@ -11,9 +11,9 @@
408 import clutter
409 import gst
410
411-from entertainerlib.frontend.gui.widgets.texture import Texture
412-from entertainerlib.frontend.medialibrary.playable import Playable
413-from entertainerlib.utils.logger import Logger
414+from entertainerlib.gui.widgets.texture import Texture
415+from entertainerlib.client.medialibrary.playable import Playable
416+from entertainerlib.logger import Logger
417
418 class MediaPlayer(object):
419 """
420@@ -59,7 +59,7 @@
421 self.repeat = False # Repeat mode
422 self.playing = False # Is media player currently playing
423
424- self.logger = Logger().getLogger('frontend.MediaPlayer')
425+ self.logger = Logger().getLogger('client.MediaPlayer')
426
427 self.video_texture = cluttergst.VideoTexture()
428 self.playbin = self.video_texture.get_playbin()
429
430=== modified file 'entertainerlib/client/medialibrary/feeds.py'
431--- entertainerlib/frontend/medialibrary/feeds.py 2009-01-26 18:28:26 +0000
432+++ entertainerlib/client/medialibrary/feeds.py 2009-05-05 04:11:48 +0000
433@@ -7,7 +7,7 @@
434
435 from pysqlite2 import dbapi2 as sqlite
436
437-from entertainerlib.utils.configuration import Configuration
438+from entertainerlib.configuration import Configuration
439
440 class FeedLibrary(object):
441 """
442
443=== modified file 'entertainerlib/client/medialibrary/images.py'
444--- entertainerlib/frontend/medialibrary/images.py 2009-01-31 17:57:51 +0000
445+++ entertainerlib/client/medialibrary/images.py 2009-05-05 04:11:48 +0000
446@@ -7,7 +7,7 @@
447 import os
448 from pysqlite2 import dbapi2 as sqlite
449
450-from entertainerlib.utils.configuration import Configuration
451+from entertainerlib.configuration import Configuration
452
453 class ImageLibrary:
454 """
455
456=== modified file 'entertainerlib/client/medialibrary/music.py'
457--- entertainerlib/frontend/medialibrary/music.py 2009-03-15 16:01:39 +0000
458+++ entertainerlib/client/medialibrary/music.py 2009-05-06 04:46:51 +0000
459@@ -8,10 +8,10 @@
460 import CDDB, DiscID
461 from pysqlite2 import dbapi2 as sqlite
462
463-from entertainerlib.utils.configuration import Configuration
464+from entertainerlib.configuration import Configuration
465
466-from entertainerlib.frontend.medialibrary.playable import Playable
467-from entertainerlib.utils.lyrics_downloader import LyricsDownloader
468+from entertainerlib.client.medialibrary.playable import Playable
469+from entertainerlib.download import LyricsDownloader
470
471
472 class MusicLibraryException(Exception):
473
474=== modified file 'entertainerlib/client/medialibrary/videos.py'
475--- entertainerlib/frontend/medialibrary/videos.py 2009-02-01 19:15:02 +0000
476+++ entertainerlib/client/medialibrary/videos.py 2009-05-05 04:24:32 +0000
477@@ -8,10 +8,10 @@
478 import os
479 from pysqlite2 import dbapi2 as sqlite
480
481-from entertainerlib.utils.configuration import Configuration
482-from entertainerlib.utils.logger import Logger
483+from entertainerlib.configuration import Configuration
484+from entertainerlib.logger import Logger
485
486-from entertainerlib.frontend.medialibrary.playable import Playable
487+from entertainerlib.client.medialibrary.playable import Playable
488
489 class VideoLibrary:
490 """
491@@ -137,7 +137,7 @@
492
493 #Setting default values
494 self.logger = Logger().getLogger(
495- 'frontend.medialibrary.videos.VideoItem')
496+ 'client.medialibrary.videos.VideoItem')
497 self.__title = ""
498 self.__filename = ""
499 self.__length = 0
500
501=== renamed file 'entertainerlib/utils/configuration.py' => 'entertainerlib/configuration.py'
502--- entertainerlib/utils/configuration.py 2009-03-08 21:29:32 +0000
503+++ entertainerlib/configuration.py 2009-05-06 05:37:53 +0000
504@@ -14,10 +14,10 @@
505 from entertainerlib.backend.core.message_type_priority import MessageType
506 from entertainerlib.backend.core.message_handler import MessageHandler
507
508-from entertainerlib.utils.theme import Theme
509+from entertainerlib.gui.theme import Theme
510
511 SOURCE_CONFIG = {
512- 'branch' : os.path.abspath(os.path.dirname(__file__) + '/../../cfg'),
513+ 'branch' : os.path.abspath(os.path.dirname(__file__) + '/../cfg'),
514 # Hardcoded path for a package install
515 'package' : "/usr/share/entertainer/cfg"
516 }
517@@ -332,9 +332,9 @@
518 location = ''
519 return location
520
521- def display_weather_in_frontend(self):
522+ def display_weather_in_client(self):
523 """
524- Should we display weather in frontend
525+ Should we display weather in client
526 @return: Boolean
527 """
528 try:
529@@ -344,9 +344,9 @@
530 return False
531 return result
532
533- def display_cd_eject_in_frontend(self):
534+ def display_cd_eject_in_client(self):
535 """
536- Should we display the cd eject button in frontend
537+ Should we display the cd eject button in client
538 @return: Boolean
539 @author: Joshua Scotton
540 """
541
542=== renamed directory 'entertainerlib/backend/core/db' => 'entertainerlib/db'
543=== added file 'entertainerlib/dialog.py'
544--- entertainerlib/dialog.py 1970-01-01 00:00:00 +0000
545+++ entertainerlib/dialog.py 2009-05-06 05:10:14 +0000
546@@ -0,0 +1,1374 @@
547+'''Dialogs for Entertainer.'''
548+# pylint: disable-msg=C0302
549+
550+__licence__ = "GPLv2"
551+__copyright__ = '2009 Entertainer Developers, 2008 Joshua Scotton'
552+
553+import os
554+import shutil
555+import socket
556+import sys
557+import tarfile
558+
559+import gtk
560+import gtk.glade
561+
562+from entertainerlib.backend.core.message import Message
563+from entertainerlib.backend.core.message_bus_proxy import MessageBusProxy
564+from entertainerlib.backend.core.message_type_priority import MessageType
565+from entertainerlib.configuration import Configuration
566+from entertainerlib.logger import Logger
567+from entertainerlib.gui.theme import Theme
568+from entertainerlib.backend.components.feeds.feed_utils import (OPMLParser,
569+ FeedConfigTools)
570+from entertainerlib.weather import Weather
571+
572+
573+class ContentManagementDialog:
574+ """
575+ This is a content management tool for Entertainer media center application.
576+ """
577+
578+ # Temporary storage for entered URL
579+ url = ""
580+ GLADE_DIR = os.path.join(os.path.dirname(__file__), "glade")
581+
582+ def __init__(self, stand_alone):
583+ """
584+ Initialize content management dialog
585+ @param stand_alone: Boolean, Is this dialog running as a stand alone
586+ process
587+ """
588+ self.stand_alone = stand_alone
589+ self.config = Configuration()
590+ self.weather = Weather()
591+
592+ # Load glade UI
593+ self.gladefile = os.path.join(self.GLADE_DIR,
594+ "entertainer-content-management.glade")
595+ self.widgets = gtk.glade.XML(self.gladefile)
596+
597+ # Get content management dialog and bind signal callbacks
598+ self.dialog = self.widgets.get_widget("ContentManagementDialog")
599+ if (self.dialog):
600+ callback_dic = {
601+ "on_button_open_list_clicked" :
602+ self.on_button_open_list_clicked,
603+ "on_close_button_clicked" : self.on_close_button_clicked,
604+ "on_button_remove_videos_clicked" :
605+ self.on_button_remove_videos_clicked,
606+ "on_button_add_videos_clicked" :
607+ self.on_button_add_videos_clicked,
608+ "on_button_edit_videos_clicked" :
609+ self.on_button_edit_videos_clicked,
610+ "on_checkbutton_video_metadata_toggled" :
611+ self.on_checkbutton_video_metadata_toggled,
612+ "on_button_add_music_clicked" :
613+ self.on_button_add_music_clicked,
614+ "on_button_remove_music_clicked" :
615+ self.on_button_remove_music_clicked,
616+ "on_button_edit_music_clicked" :
617+ self.on_button_edit_music_clicked,
618+ "on_lyrics_checkbox_toggled" : self.on_lyrics_checkbox_toggled,
619+ "on_art_checkbox_toggled" : self.on_art_checkbox_toggled,
620+ "on_button_add_images_clicked" :
621+ self.on_button_add_images_clicked,
622+ "on_button_remove_images_clicked" :
623+ self.on_button_remove_images_clicked,
624+ "on_button_edit_images_clicked" :
625+ self.on_button_edit_images_clicked,
626+ "on_button_add_feed_clicked" :
627+ self.on_button_add_feed_clicked,
628+ "on_button_remove_feed_clicked" :
629+ self.on_button_remove_feed_clicked,
630+ "on_button_edit_feed_clicked" :
631+ self.on_button_edit_feed_clicked,
632+ "on_fetch_interval_spinbutton_value_changed" :
633+ self.on_fetch_interval_spinbutton_value_changed,
634+ "on_ContentManagementDialog_destroy" : self.on_dialog_closed,
635+ "on_url_dialog_delete_event" : self.on_url_dialog_delete_event,
636+ "on_url_dialog_ok_button_clicked" :
637+ self.on_url_dialog_ok_button_clicked,
638+ "on_url_dialog_cancel_button_clicked" :
639+ self.on_url_dialog_cancel_button_clicked,
640+ "on_button_video_rebuild_clicked" :
641+ self.on_button_video_rebuild_clicked,
642+ "on_button_music_rebuild_clicked" :
643+ self.on_button_music_rebuild_clicked,
644+ "on_button_image_rebuild_clicked" :
645+ self.on_button_image_rebuild_clicked,
646+ "on_button_feed_rebuild_clicked" :
647+ self.on_button_feed_rebuild_clicked,
648+ "on_button_add_weather_clicked" :
649+ self.on_button_add_weather_clicked,
650+ "on_button_remove_weather_clicked" :
651+ self.on_button_remove_weather_clicked,
652+ "on_weather_display_checkbox_toggled" :
653+ self.on_weather_display_checkbox_toggled,
654+ "on_location_find_button_clicked" :
655+ self.on_location_find_button_clicked,
656+ "on_location_cancel_button_clicked" :
657+ self.on_location_cancel_button_clicked,
658+ "on_location_add_button_clicked" :
659+ self.on_location_add_button_clicked,
660+ "on_location_entry_activate" : self.on_location_entry_activate}
661+ self.widgets.signal_autoconnect(callback_dic)
662+
663+ # Initialize dialog widgets with correct values and show dialog
664+ self.init_dialog_values_from_configure_file()
665+ self.dialog.resize(500, 300)
666+ self.dialog.show()
667+
668+ # Initialize location list in search dialog
669+ result_list = self.widgets.get_widget("location_results_treeview")
670+ store = gtk.ListStore(str)
671+ result_list.set_model(store)
672+ cell_renderer = gtk.CellRendererText()
673+ column = gtk.TreeViewColumn(_("Location"), cell_renderer, text=0)
674+ result_list.append_column(column)
675+
676+# Signal handlers
677+
678+ def on_dialog_closed(self, widget):
679+ """Callback function for dialog's close button"""
680+ try:
681+ proxy = MessageBusProxy(client_name = "Content Management GUI")
682+ proxy.connectToMessageBus()
683+ proxy.sendMessage(Message(MessageType.CONTENT_CONF_UPDATED))
684+ proxy.disconnectFromMessageBus()
685+ except socket.error:
686+ error = gtk.MessageDialog(
687+ None, gtk.DIALOG_MODAL,
688+ gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _(
689+ "Entertainer backend is not running. "
690+ "Cache cannot be rebuilt."
691+ ))
692+ error.run()
693+ error.destroy()
694+
695+
696+ if(self.stand_alone):
697+ self.dialog.hide()
698+ self.dialog.destroy()
699+ gtk.main_quit()
700+ else:
701+ self.dialog.hide()
702+ self.dialog.destroy()
703+
704+ def on_close_button_clicked(self, widget):
705+ """Callback function for dialog's close button"""
706+ if(self.stand_alone):
707+ self.dialog.hide()
708+ self.dialog.destroy()
709+ gtk.main_quit()
710+ else:
711+ self.dialog.hide()
712+ self.dialog.destroy()
713+
714+ def on_button_add_videos_clicked(self, widget):
715+ """Opens add URL dialog. """
716+ widget = self.widgets.get_widget("treeview_videos")
717+ model = widget.get_model()
718+ # Open "Select folder" dialog
719+ dialog = gtk.FileChooserDialog(_("Select video folder"), None,
720+ gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
721+ (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,
722+ gtk.STOCK_OPEN,gtk.RESPONSE_OK),
723+ None)
724+ status = dialog.run()
725+ # If folder was selected we add it to model and update config file
726+ if(status == gtk.RESPONSE_OK):
727+ self.add_to_model_and_config(dialog.get_current_folder(), model,
728+ self.video_folders, "Videos")
729+ dialog.destroy()
730+
731+ def on_button_remove_videos_clicked(self, widget):
732+ """Remove currently selected folder from video folders"""
733+ widget = self.widgets.get_widget("treeview_videos")
734+ model = widget.get_model()
735+ selection = widget.get_selection().get_selected()
736+ if selection[1] == None:
737+ return
738+ rm_folder = model.get_value(selection[1], 0)
739+ self.video_folders.remove(rm_folder)
740+ str_folders = ";".join(self.video_folders)
741+ self.config.write_content_value("Videos", "folders", str_folders)
742+ model.remove(selection[1])
743+
744+ def on_button_edit_videos_clicked(self, widget):
745+ """Edit currently selected folder"""
746+ widget = self.widgets.get_widget("treeview_videos")
747+ url_dialog = self.widgets.get_widget("url_dialog")
748+ url_entry = self.widgets.get_widget("url_entry")
749+ model = widget.get_model()
750+ selection = widget.get_selection().get_selected()
751+ if selection[1] == None:
752+ return
753+ folder = model.get_value(selection[1], 0)
754+ url_entry.set_text(folder)
755+ url_dialog.set_title(_("Edit URL"))
756+ status = url_dialog.run()
757+ if status == gtk.RESPONSE_OK and os.path.exists(self.url):
758+ # Update list model
759+ model.set_value(selection[1], 0, self.url)
760+ # Update configure file
761+ pos = self.video_folders.index(folder)
762+ self.video_folders.remove(folder)
763+ self.video_folders.insert(pos, self.url)
764+ str_folders = ";".join(self.video_folders)
765+ self.config.write_content_value("Videos", "folders",
766+ str_folders)
767+
768+ def on_checkbutton_video_metadata_toggled(self, widget):
769+ """
770+ Download video file metadata from internet
771+ @param widget: GTK-Widget
772+ """
773+ self.config.write_content_value("Videos", "download_metadata",
774+ widget.get_active())
775+
776+ def on_button_add_music_clicked(self, widget):
777+ """
778+ Opens add URL dialog
779+ @param widget: GTK-Widget
780+ """
781+ widget = self.widgets.get_widget("treeview_music")
782+ model = widget.get_model()
783+ # Open "Select folder" dialog
784+ dialog = gtk.FileChooserDialog(_("Select music folder"), None,
785+ gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
786+ (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
787+ gtk.RESPONSE_OK),
788+ None)
789+ status = dialog.run()
790+ # If folder was selected we add it to model and update config file
791+ if(status == gtk.RESPONSE_OK):
792+ self.add_to_model_and_config(dialog.get_current_folder(), model,
793+ self.music_folders, "Music")
794+ dialog.destroy()
795+
796+ def on_button_remove_music_clicked(self, widget):
797+ """Remove currently selected folder from music folders"""
798+ widget = self.widgets.get_widget("treeview_music")
799+ model = widget.get_model()
800+ selection = widget.get_selection().get_selected()
801+ if selection[1] == None:
802+ return
803+ rm_folder = model.get_value(selection[1], 0)
804+ self.music_folders.remove(rm_folder)
805+ str_folders = ";".join(self.music_folders)
806+ self.config.write_content_value("Music", "folders", str_folders)
807+ model.remove(selection[1])
808+
809+ def on_button_edit_music_clicked(self, widget):
810+ """Edit currently selected music folder"""
811+ widget = self.widgets.get_widget("treeview_music")
812+ url_dialog = self.widgets.get_widget("url_dialog")
813+ url_entry = self.widgets.get_widget("url_entry")
814+ model = widget.get_model()
815+ selection = widget.get_selection().get_selected()
816+ if selection[1] == None:
817+ return
818+ folder = model.get_value(selection[1], 0)
819+ url_entry.set_text(folder)
820+ url_dialog.set_title(_("Edit URL"))
821+ status = url_dialog.run()
822+ if status == gtk.RESPONSE_OK and os.path.exists(self.url):
823+ # Update list model
824+ model.set_value(selection[1], 0, self.url)
825+ # Update configure file
826+ pos = self.music_folders.index(folder)
827+ self.music_folders.remove(folder)
828+ self.music_folders.insert(pos, self.url)
829+ str_folders = ";".join(self.music_folders)
830+ self.config.write_content_value("Music", "folders", str_folders)
831+
832+ def on_button_add_images_clicked(self, widget):
833+ """Opens add URL dialog. """
834+ widget = self.widgets.get_widget("treeview_images")
835+ model = widget.get_model()
836+ # Open "Select folder" dialog
837+ dialog = gtk.FileChooserDialog(_("Select image folder"), None,
838+ gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
839+ (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
840+ gtk.RESPONSE_OK),
841+ None)
842+ status = dialog.run()
843+ # If folder was selected we add it to model and update config file
844+ if(status == gtk.RESPONSE_OK):
845+ self.add_to_model_and_config(dialog.get_current_folder(), model,
846+ self.image_folders, "Images")
847+ dialog.destroy()
848+
849+ def on_button_remove_images_clicked(self, widget):
850+ """Remove currently selected folder from images folders"""
851+ widget = self.widgets.get_widget("treeview_images")
852+ model = widget.get_model()
853+ selection = widget.get_selection().get_selected()
854+ if selection[1] == None:
855+ return
856+ rm_folder = model.get_value(selection[1], 0)
857+ self.image_folders.remove(rm_folder)
858+ str_folders = ";".join(self.image_folders)
859+ self.config.write_content_value("Images", "folders", str_folders)
860+ model.remove(selection[1])
861+
862+ def on_button_edit_images_clicked(self, widget):
863+ """Edit currently selected music folder"""
864+ widget = self.widgets.get_widget("treeview_images")
865+ url_dialog = self.widgets.get_widget("url_dialog")
866+ url_entry = self.widgets.get_widget("url_entry")
867+ model = widget.get_model()
868+ selection = widget.get_selection().get_selected()
869+ if selection[1] == None:
870+ return
871+ folder = model.get_value(selection[1], 0)
872+ url_entry.set_text(folder)
873+ url_dialog.set_title(_("Edit URL"))
874+ status = url_dialog.run()
875+ if status == gtk.RESPONSE_OK and os.path.exists(self.url):
876+ # Update list model
877+ model.set_value(selection[1], 0, self.url)
878+ # Update configure file
879+ pos = self.image_folders.index(folder)
880+ self.image_folders.remove(folder)
881+ self.image_folders.insert(pos, self.url)
882+ str_folders = ";".join(self.image_folders)
883+ self.config.write_content_value("Images", "folders",
884+ str_folders)
885+
886+ def on_button_add_feed_clicked(self, widget):
887+ """Opens add feed dialog. """
888+ widget = self.widgets.get_widget("treeview_feeds")
889+ url_dialog = self.widgets.get_widget("url_dialog")
890+ model = widget.get_model()
891+ # Open dialog
892+ url_dialog.set_title(_("Add RSS-feed"))
893+ status = url_dialog.run()
894+ # If folder was selected we add it to model and update config file
895+ if(status == gtk.RESPONSE_OK):
896+ model.append([self.url])
897+ self.feeds.append(self.url)
898+ str_folders = ";".join(self.feeds)
899+ self.config.write_content_value("RSS", "feeds", str_folders)
900+
901+ def on_button_remove_feed_clicked(self, widget):
902+ """Remove currently selected reed from RSS-feeds"""
903+ widget = self.widgets.get_widget("treeview_feeds")
904+ model = widget.get_model()
905+ selection = widget.get_selection().get_selected()
906+ if selection[1] == None:
907+ return
908+ rm_folder = model.get_value(selection[1], 0)
909+ self.feeds.remove(rm_folder)
910+ str_folders = ";".join(self.feeds)
911+ self.config.write_content_value("RSS", "feeds", str_folders)
912+ model.remove(selection[1])
913+
914+ def on_button_edit_feed_clicked(self, widget):
915+ """Edit currently selected feed"""
916+ widget = self.widgets.get_widget("treeview_feeds")
917+ url_dialog = self.widgets.get_widget("url_dialog")
918+ url_entry = self.widgets.get_widget("url_entry")
919+ model = widget.get_model()
920+ selection = widget.get_selection().get_selected()
921+ if selection[1] == None:
922+ return
923+ feed = model.get_value(selection[1], 0)
924+ url_entry.set_text(feed)
925+ url_dialog.set_title(_("Edit feed"))
926+ status = url_dialog.run()
927+ if status == gtk.RESPONSE_OK:
928+ # Update list model
929+ model.set_value(selection[1], 0, self.url)
930+ # Update configure file
931+ pos = self.feeds.index(feed)
932+ self.feeds.remove(feed)
933+ self.feeds.insert(pos, self.url)
934+ str_feeds = ";".join(self.feeds)
935+ self.config.write_content_value("RSS", "feeds", str_feeds)
936+
937+ def on_button_open_list_clicked(self, widget):
938+ """Opens the open feed source dialog"""
939+ open_dialog = OpenFeedSourceDialog(
940+ self.widgets.get_widget("treeview_feeds"), self.feeds)
941+ open_dialog.dialog.connect("destroy", open.on_closeButton_clicked)
942+ open_dialog.dialog.hide()
943+ open_dialog.dialog.destroy()
944+
945+ def on_fetch_interval_spinbutton_value_changed(self, widget):
946+ self.config.write_content_value("RSS", "fetch_interval",
947+ widget.get_value_as_int())
948+
949+ def on_lyrics_checkbox_toggled(self, widget):
950+ self.config.write_content_value("Music", "download_lyrics",
951+ widget.get_active())
952+
953+ def on_art_checkbox_toggled(self, widget):
954+ self.config.write_content_value("Music", "download_album_art",
955+ widget.get_active())
956+
957+ def on_url_dialog_ok_button_clicked(self, widget):
958+ """URL dialog OK button pressed. Sets self.url"""
959+ url_dialog = self.widgets.get_widget("url_dialog")
960+ url_entry = self.widgets.get_widget("url_entry")
961+ url_dialog.hide()
962+ self.url = url_entry.get_text()
963+ url_entry.set_text("")
964+ url_dialog.response(gtk.RESPONSE_OK)
965+
966+ def on_url_dialog_cancel_button_clicked(self, widget):
967+ """URL dialog cancelled. Hides dialog"""
968+ url_dialog = self.widgets.get_widget("url_dialog")
969+ url_entry = self.widgets.get_widget("url_entry")
970+ url_dialog.hide()
971+ url_entry.set_text("")
972+ url_dialog.response(gtk.RESPONSE_CANCEL)
973+
974+ def on_url_dialog_delete_event(self, widget, data):
975+ """Dialog's X clicked. Hides dialog"""
976+ url_dialog = self.widgets.get_widget("url_dialog")
977+ url_entry = self.widgets.get_widget("url_entry")
978+ url_dialog.hide()
979+ url_entry.set_text("")
980+ url_dialog.response(gtk.RESPONSE_CANCEL)
981+ return True
982+
983+ def on_button_add_weather_clicked(self, widget):
984+ """
985+ Open location search dialog
986+ @param widget: GTK-Widget
987+ """
988+ location_dialog = self.widgets.get_widget("weather_search_dialog")
989+ location_dialog.set_title(_("Add location"))
990+
991+ # Clear results
992+ result_list = self.widgets.get_widget("location_results_treeview")
993+ model = result_list.get_model()
994+ model.clear()
995+
996+ status = location_dialog.run()
997+ if(status == gtk.RESPONSE_OK):
998+ print "Added"
999+
1000+ def on_button_remove_weather_clicked(self, widget):
1001+ """
1002+ Remove currently selected weather location from the location list
1003+ @param widget: GTK-Widget
1004+ """
1005+ widget = self.widgets.get_widget("treeview_locations")
1006+ model = widget.get_model()
1007+ self.weather_locations = []
1008+ str_folders = ""
1009+ self.config.write_content_value("Weather", "location", str_folders)
1010+ model.clear()
1011+
1012+ def on_weather_display_checkbox_toggled(self, widget):
1013+ """
1014+ Checkbox that defines should we use weather conditions
1015+ @param widget: GTK-Widget
1016+ """
1017+ self.config.write_content_value("Weather", "display_in_menu",
1018+ widget.get_active())
1019+ if widget.get_active():
1020+ self.widgets.get_widget("button_add_weather").set_sensitive(True)
1021+ self.widgets.get_widget(
1022+ "button_remove_weather").set_sensitive(True)
1023+ self.widgets.get_widget("treeview_locations").set_sensitive(True)
1024+ else:
1025+ self.widgets.get_widget("button_add_weather").set_sensitive(False)
1026+ self.widgets.get_widget(
1027+ "button_remove_weather").set_sensitive(False)
1028+ self.widgets.get_widget("treeview_locations").set_sensitive(False)
1029+
1030+ def on_location_find_button_clicked(self, widget):
1031+ """
1032+ Find location by search string
1033+ @param widget: GTK-Widget
1034+ """
1035+ add_button = self.widgets.get_widget("location_add_button")
1036+ search_term = self.widgets.get_widget("location_entry").get_text()
1037+ result_list = self.widgets.get_widget("location_results_treeview")
1038+ model = result_list.get_model()
1039+ model.clear()
1040+ if search_term != "":
1041+ self.weather.set_location(search_term)
1042+ self.weather.refresh()
1043+ results = self.weather.get_forecasts()
1044+ if len(results) > 0:
1045+ add_button.set_sensitive(True)
1046+ model.append([search_term])
1047+ result_list.set_cursor(0)
1048+ else:
1049+ model.clear()
1050+ model.append([_("Location Not Found!")])
1051+ add_button.set_sensitive(False)
1052+
1053+ def on_location_cancel_button_clicked(self, widget):
1054+ """
1055+ Close location search dialog without taking any actions.0
1056+ @param widget: GTK-Widget
1057+ """
1058+ location_dialog = self.widgets.get_widget("weather_search_dialog")
1059+ location_entry = self.widgets.get_widget("location_entry")
1060+ location_dialog.hide()
1061+ location_entry.set_text("")
1062+ location_dialog.response(gtk.RESPONSE_CANCEL)
1063+
1064+ def on_location_add_button_clicked(self, widget):
1065+ """
1066+ Add selected location to location list and close search dialog
1067+ @param widget: GTK-Widget
1068+ """
1069+ self.weather_locations = []
1070+ result_list = self.widgets.get_widget("location_results_treeview")
1071+ model = result_list.get_model()
1072+ selection = result_list.get_selection().get_selected()
1073+ if selection[1] == None:
1074+ return
1075+ location_string = model.get_value(selection[1], 0)
1076+
1077+ location_list = self.widgets.get_widget("treeview_locations")
1078+ loc_model = location_list.get_model()
1079+ loc_model.clear()
1080+ loc_model.append([location_string])
1081+
1082+ self.weather_locations.append(location_string)
1083+ str_locations = ";".join(self.weather_locations)
1084+ self.config.write_content_value("Weather", "location", str_locations)
1085+
1086+ location_dialog = self.widgets.get_widget("weather_search_dialog")
1087+ location_entry = self.widgets.get_widget("location_entry")
1088+ location_dialog.hide()
1089+ location_entry.set_text("")
1090+ location_dialog.response(gtk.RESPONSE_CANCEL)
1091+
1092+ def on_location_entry_activate(self, widget):
1093+ """
1094+ User hit enter on location entry to start search
1095+ @param widget: GTK-Widget
1096+ """
1097+ self.on_location_find_button_clicked(widget)
1098+
1099+ def on_button_video_rebuild_clicked(self, widget):
1100+ """
1101+ Rebuild video cache requested
1102+ @param widget: GTK-Widget
1103+ """
1104+ try:
1105+ proxy = MessageBusProxy(client_name = "Content Management GUI")
1106+ proxy.connectToMessageBus()
1107+ proxy.sendMessage(Message(MessageType.REBUILD_VIDEO_CACHE))
1108+ proxy.disconnectFromMessageBus()
1109+ except socket.error:
1110+ error = gtk.MessageDialog(
1111+ None, gtk.DIALOG_MODAL,
1112+ gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _(
1113+ "Entertainer backend is not running. "
1114+ "Cache cannot be rebuilt."
1115+ ))
1116+ error.run()
1117+ error.destroy()
1118+
1119+ def on_button_music_rebuild_clicked(self, widget):
1120+ """
1121+ Rebuild music cache requested
1122+ @param widget: GTK-Widget
1123+ """
1124+ try:
1125+ proxy = MessageBusProxy(client_name = "Content Management GUI")
1126+ proxy.connectToMessageBus()
1127+ proxy.sendMessage(Message(MessageType.REBUILD_MUSIC_CACHE))
1128+ proxy.disconnectFromMessageBus()
1129+ except socket.error:
1130+ error = gtk.MessageDialog(
1131+ None, gtk.DIALOG_MODAL,
1132+ gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _(
1133+ "Entertainer backend is not running. "
1134+ "Cache cannot be rebuilt."
1135+ ))
1136+ error.run()
1137+ error.destroy()
1138+
1139+ def on_button_image_rebuild_clicked(self, widget):
1140+ """
1141+ Rebuild image cache requested
1142+ @param widget: GTK-Widget
1143+ """
1144+ try:
1145+ proxy = MessageBusProxy(client_name = "Content Management GUI")
1146+ proxy.connectToMessageBus()
1147+ proxy.sendMessage(Message(MessageType.REBUILD_IMAGE_CACHE))
1148+ proxy.disconnectFromMessageBus()
1149+ except socket.error:
1150+ error = gtk.MessageDialog(
1151+ None, gtk.DIALOG_MODAL,
1152+ gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _(
1153+ "Entertainer backend is not running. "
1154+ "Cache cannot be rebuilt."
1155+ ))
1156+ error.run()
1157+ error.destroy()
1158+
1159+ def on_button_feed_rebuild_clicked(self, widget):
1160+ """
1161+ Rebuild feed cache requested
1162+ @param widget: GTK-Widget
1163+ """
1164+ #We need the user to confirm the rebuild feed cache request
1165+ dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_WARNING,
1166+ gtk.BUTTONS_OK_CANCEL,
1167+ _("This will completely remove any feed entries in the cache!"))
1168+ status = dialog.run()
1169+ #If user has ok'd the request send the message to the message bus
1170+ if(status == gtk.RESPONSE_OK):
1171+ try:
1172+ proxy = MessageBusProxy(client_name = "Content Management GUI")
1173+ proxy.connectToMessageBus()
1174+ proxy.sendMessage(Message(MessageType.REBUILD_FEED_CACHE))
1175+ proxy.disconnectFromMessageBus()
1176+ except socket.error:
1177+ error = gtk.MessageDialog(
1178+ None, gtk.DIALOG_MODAL,
1179+ gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _(
1180+ "Entertainer backend is not running. "
1181+ "Cache cannot be rebuilt."
1182+ ))
1183+ error.run()
1184+ error.destroy()
1185+
1186+ dialog.destroy()
1187+
1188+ def init_dialog_values_from_configure_file(self):
1189+ """Read configuration and set dialog widget values with read values.
1190+ """
1191+ # == Videos ==
1192+ videolist_widget = self.widgets.get_widget("treeview_videos")
1193+ videostore = gtk.ListStore(str)
1194+
1195+ cell_renderer = gtk.CellRendererText()
1196+ column = gtk.TreeViewColumn(_("Folders"), cell_renderer, text=0)
1197+ videolist_widget.append_column(column)
1198+
1199+ self.video_folders = self.config.get_video_folders()
1200+
1201+ # Fill model with folders read from config file
1202+ self.init_model(videostore, self.video_folders)
1203+
1204+ videolist_widget.set_model(videostore)
1205+
1206+ # Checkboxes
1207+ metadata_checkbox = self.widgets.get_widget("video_metadata_checkbox")
1208+ md_val = self.config.download_video_metadata()
1209+ metadata_checkbox.set_active(md_val)
1210+
1211+ # == Music ==
1212+ musiclist_widget = self.widgets.get_widget("treeview_music")
1213+ music_model = gtk.ListStore(str)
1214+
1215+ music_cell = gtk.CellRendererText()
1216+ music_column = gtk.TreeViewColumn(_("Folders"), music_cell, text=0)
1217+ musiclist_widget.append_column(music_column)
1218+
1219+ self.music_folders = self.config.get_music_folders()
1220+
1221+ # Fill model with folders read from config file
1222+ self.init_model(music_model, self.music_folders)
1223+
1224+ musiclist_widget.set_model(music_model)
1225+
1226+ # Checkboxes
1227+ art_checkbox = self.widgets.get_widget("art_checkbox")
1228+ art_val = self.config.download_album_art()
1229+ art_checkbox.set_active(art_val)
1230+
1231+ lyrics_checkbox = self.widgets.get_widget("lyrics_checkbox")
1232+ lyrics_val = self.config.download_lyrics()
1233+ lyrics_checkbox.set_active(lyrics_val)
1234+
1235+ # == Images ==
1236+ imagelist_widget = self.widgets.get_widget("treeview_images")
1237+ images_model = gtk.ListStore(str)
1238+
1239+ img_cell = gtk.CellRendererText()
1240+ img_column = gtk.TreeViewColumn(_("Folders"), img_cell, text=0)
1241+ imagelist_widget.append_column(img_column)
1242+
1243+ self.image_folders = self.config.get_image_folders()
1244+
1245+ # Fill model with folders read from config file
1246+ self.init_model(images_model, self.image_folders)
1247+
1248+ imagelist_widget.set_model(images_model)
1249+
1250+ # == RSS-feeds ==
1251+ feedlist_widget = self.widgets.get_widget("treeview_feeds")
1252+ feed_model = gtk.ListStore(str)
1253+
1254+ rss_cell = gtk.CellRendererText()
1255+ rss_column = gtk.TreeViewColumn(_("Feeds"), rss_cell, text=0)
1256+ feedlist_widget.append_column(rss_column)
1257+
1258+ self.feeds = self.config.get_feeds()
1259+
1260+ # Fill model with folders read from config file
1261+ for i in range(len(self.feeds)):
1262+ feed_model.insert(i, [self.feeds[i]])
1263+
1264+ feedlist_widget.set_model(feed_model)
1265+
1266+ # Interval spinner
1267+ interval_spinner = self.widgets.get_widget("fetch_interval_spinbutton")
1268+ interval_val = self.config.get_feed_fetch_interval()
1269+ if interval_val < 15:
1270+ interval_val = 15
1271+ elif interval_val > 900:
1272+ interval_val = 900
1273+ interval_spinner.set_value(interval_val)
1274+
1275+ # == Weather location ==
1276+ locationlist_widget = self.widgets.get_widget("treeview_locations")
1277+ location_model = gtk.ListStore(str)
1278+
1279+ loc_cell = gtk.CellRendererText()
1280+ location_column = gtk.TreeViewColumn(_("Location"), loc_cell, text=0)
1281+ locationlist_widget.append_column(location_column)
1282+
1283+ self.weather_location = self.config.get_weather_location()
1284+
1285+ # Fill model with location read from config file
1286+ location_model.insert(0, [self.weather_location])
1287+
1288+ locationlist_widget.set_model(location_model)
1289+
1290+ # Checkboxes
1291+ weather_display_checkbox = self.widgets.get_widget(
1292+ "weather_display_checkbox")
1293+ display_val = self.config.display_weather_in_client()
1294+ weather_display_checkbox.set_active(display_val)
1295+ if not display_val:
1296+ self.widgets.get_widget("button_add_weather").set_sensitive(False)
1297+ self.widgets.get_widget("button_remove_weather").set_sensitive(
1298+ False)
1299+ self.widgets.get_widget("treeview_locations").set_sensitive(False)
1300+
1301+ def add_to_model_and_config(self, selected_folder, model, folders, kind):
1302+ """
1303+ Add selected_folder to the model and the folders list while updating
1304+ the configuration item section specified by type
1305+ """
1306+ if not selected_folder in folders:
1307+ model.append([selected_folder])
1308+
1309+ if(folders == None):
1310+ folders = [selected_folder]
1311+ else:
1312+ folders.append(selected_folder)
1313+
1314+ if "" in folders:
1315+ folders.remove("")
1316+ str_folders = ";".join(folders)
1317+ self.config.write_content_value(kind, "folders", str_folders)
1318+
1319+ def init_model(self, model, items):
1320+ """Fill model with items from supplied list"""
1321+ for i in range(len(items)):
1322+ if not str(items[i]).strip() == "":
1323+ model.insert(i, [items[i]])
1324+
1325+
1326+class LogViewer:
1327+ """
1328+ Implements dialog that allows user to see logged events.
1329+
1330+ This dialog is used to check Entertainer logfiles. It reads all data from
1331+ selected file and saves rows to self.log_rows. Then it filters unwanted
1332+ rows away by calling self.filterMessages(). This method adds rows to
1333+ ListStore, which is the model of TreeView object.
1334+
1335+ Combobox and refresh -button actions read files again
1336+ Checkbox actions just filter current rows again
1337+ """
1338+
1339+ GLADE_DIR = os.path.join(os.path.dirname(__file__), "glade")
1340+
1341+ # Is this dialog running as a stand alone process
1342+ __STAND_ALONE = None
1343+
1344+ widgets = None
1345+ dialog = None
1346+ log_store = None
1347+ log_rows = []
1348+
1349+ gladefile = os.path.join(GLADE_DIR, "log_dialog.glade")
1350+
1351+ def __init__(self, stand_alone):
1352+ self.logfile_entertainer = Configuration().ENTERTAINER_LOG
1353+ self.logger = Logger().getLogger('utils.log_viewer')
1354+
1355+ self.__STAND_ALONE = stand_alone
1356+ try:
1357+ self.widgets = gtk.glade.XML(self.gladefile)
1358+ except RuntimeError:
1359+ self.logger.critical("Couldn't open glade file: " + self.gladefile)
1360+ sys.exit(1)
1361+ callback_dic = {
1362+ "on_close_log_button_clicked" : self.on_close_log_button_clicked,
1363+ "on_log_refresh_button_clicked" : self.update_log_rows,
1364+ "on_checkbutton_debug_toggled" : self.filter_messages,
1365+ "on_checkbutton_critical_toggled" : self.filter_messages,
1366+ "on_checkbutton_error_toggled" : self.filter_messages,
1367+ "on_checkbutton_warning_toggled" : self.filter_messages,
1368+ "on_checkbutton_info_toggled" : self.filter_messages }
1369+
1370+ self.widgets.signal_autoconnect(callback_dic)
1371+
1372+ # Create log treeview
1373+ treeview = self.widgets.get_widget("treeview_log")
1374+ cell_renderer1 = gtk.CellRendererText()
1375+ cell_renderer2 = gtk.CellRendererText()
1376+ cell_renderer3 = gtk.CellRendererText()
1377+ cell_renderer4 = gtk.CellRendererText()
1378+
1379+ column1 = gtk.TreeViewColumn("Date")
1380+ column1.pack_start(cell_renderer1, True)
1381+ column1.set_attributes(cell_renderer1, text = 0)
1382+
1383+ column2 = gtk.TreeViewColumn("Time")
1384+ column2.pack_start(cell_renderer2, True)
1385+ column2.set_attributes(cell_renderer2, text = 1)
1386+
1387+ column3 = gtk.TreeViewColumn("Type")
1388+ column3.pack_start(cell_renderer3, True)
1389+ column3.set_attributes(cell_renderer3, text = 2)
1390+
1391+ column4 = gtk.TreeViewColumn("Message")
1392+ column4.pack_end(cell_renderer4, True)
1393+ column4.set_attributes(cell_renderer4, text = 3)
1394+
1395+ treeview.append_column(column1)
1396+ treeview.append_column(column2)
1397+ treeview.append_column(column3)
1398+ treeview.append_column(column4)
1399+ treeview.set_headers_visible(True)
1400+
1401+ # Set model to view and read data from logfile
1402+ self.log_store = gtk.ListStore(str, str, str, str)
1403+ treeview.set_model(self.log_store)
1404+ self.update_log_rows()
1405+
1406+ # Show Log viewer dialog
1407+ self.dialog = self.widgets.get_widget("LogDialog")
1408+ self.dialog.resize(750, 500)
1409+ self.dialog.connect("destroy", self.on_close_log_button_clicked)
1410+ self.dialog.show()
1411+
1412+ def update_log_rows(self, widget=None):
1413+ """Read logfile and udpate treeview"""
1414+ self.log_rows[:] = []
1415+
1416+ try:
1417+ for line in open(self.logfile_entertainer, 'r'):
1418+ try:
1419+ line_table = line.split()
1420+ message = ' '.join(line_table[3:])
1421+ row = line_table[:3] + [message]
1422+ parsed_row = parse_row(row)
1423+ self.log_rows.append(parsed_row)
1424+ except IndexError:
1425+ print "Cannot parse log line: ", line
1426+ except IOError:
1427+ print "Cannot find logfile: ", self.logfile_entertainer
1428+
1429+ # Reverse so that the latest message is at top
1430+ self.log_rows.reverse()
1431+ # Filter unwated message types
1432+ self.filter_messages()
1433+
1434+ def filter_messages(self, widget = None):
1435+ """Checks which message types should be displayed on treeview"""
1436+ if self.log_store:
1437+ self.log_store.clear()
1438+
1439+ debug = self.widgets.get_widget("checkbutton_debug").get_active()
1440+ critical = self.widgets.get_widget("checkbutton_critical").get_active()
1441+ error = self.widgets.get_widget("checkbutton_error").get_active()
1442+ warning = self.widgets.get_widget("checkbutton_warning").get_active()
1443+ info = self.widgets.get_widget("checkbutton_info").get_active()
1444+
1445+ for element in self.log_rows:
1446+ if element[2] == "DEBUG" and debug:
1447+ self.log_store.append(element)
1448+ elif element[2] == "CRITICAL" and critical:
1449+ self.log_store.append(element)
1450+ elif element[2] == "ERROR" and error:
1451+ self.log_store.append(element)
1452+ elif element[2] == "WARNING" and warning:
1453+ self.log_store.append(element)
1454+ elif element[2] == "INFO" and info:
1455+ self.log_store.append(element)
1456+
1457+ # Signal handlers
1458+ def on_close_log_button_clicked(self, widget):
1459+ """
1460+ If running as a stand alone process, quit.
1461+ Otherwise only destroy dialog.
1462+ """
1463+ self.dialog.hide()
1464+ self.dialog.destroy()
1465+ if(self.__STAND_ALONE):
1466+ gtk.main_quit()
1467+
1468+
1469+def parse_row(row):
1470+ """
1471+ This parses the input list into a list suitable for the logviewer
1472+ @author Joshua Scotton
1473+ @param row The input list [Date, Time, Class, Type + Description]
1474+ """
1475+ if row[3][:5] == "DEBUG":
1476+ return [row[0], row[1], "DEBUG",
1477+ row[2] + ": " + row[3][5:]]
1478+ elif row[3][:8] == "CRITICAL":
1479+ return [row[0], row[1], "CRITICAL",
1480+ row[2] + ": " + row[3][8:]]
1481+ elif row[3][:5] == "ERROR":
1482+ return [row[0], row[1], "ERROR",
1483+ row[2] + ": " + row[3][5:]]
1484+ elif row[3][:7] == "WARNING":
1485+ return [row[0], row[1], "WARNING",
1486+ row[2] + ": " + row[3][7:]]
1487+ elif row[3][:4] == "INFO":
1488+ return [row[0], row[1], "INFO",
1489+ row[2] + ": " + row[3][4:]]
1490+
1491+
1492+class OpenFeedSourceDialog:
1493+ '''Feed source reader dialog'''
1494+
1495+ GLADE_DIR = os.path.join(os.path.dirname(__file__), "glade")
1496+
1497+ widgets = None
1498+ dialog = None
1499+ tree_widget = None
1500+ feeds = None
1501+ url = None
1502+
1503+ def __init__(self, the_widget, the_feeds):
1504+ """initialises the gtk window and displays it"""
1505+
1506+ #feeds is a pointer to a list of feeds from the config file
1507+ self.feeds = the_feeds
1508+
1509+ #needed so we can add feeds to the feed list widget
1510+ self.tree_widget = the_widget
1511+
1512+ # Load glade UI
1513+ self.gladefile = os.path.join(self.GLADE_DIR,
1514+ "open_feed_source_dialog.glade")
1515+ self.widgets = gtk.glade.XML(self.gladefile)
1516+
1517+ # Get content management dialog and bind signal callbacks
1518+ self.dialog = self.widgets.get_widget("open_source_dialog")
1519+ if (self.dialog):
1520+ callback_dic = {
1521+ "on_fileOpen_clicked" : self.on_fileOpen_clicked,
1522+ "on_lifereaButton_clicked" : self.on_lifereaButton_clicked,
1523+ "on_enterURL_clicked" : self.on_enterURL_clicked,
1524+ "on_closeButton_clicked" : self.on_closeButton_clicked,
1525+ "on_url_dialog_ok_button_clicked" :
1526+ self.on_url_dialog_ok_button_clicked,
1527+ "on_url_dialog_cancel_button_clicked" :
1528+ self.on_url_dialog_cancel_button_clicked,
1529+ "on_url_dialog_delete_event" : self.on_url_dialog_delete_event
1530+ }
1531+
1532+ self.widgets.signal_autoconnect(callback_dic)
1533+
1534+ # Initilize dialog widgets with correct values and show dialog
1535+ self.dialog.resize(300, 200)
1536+ self.dialog.run()
1537+
1538+ def on_fileOpen_clicked(self, widget):
1539+ """Opens add file dialog and then adds all feeds in the opml file
1540+ selected """
1541+
1542+ #get the model for the feed list widget so we can add the new feeds
1543+ model = self.tree_widget.get_model()
1544+
1545+ #create select file dialog
1546+ dialog = gtk.FileChooserDialog(_("Select OPML file"), None,
1547+ gtk.FILE_CHOOSER_ACTION_OPEN,
1548+ (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
1549+ gtk.RESPONSE_OK))
1550+
1551+ #set dialog up to filter for only opml files
1552+ file_filter = gtk.FileFilter()
1553+ file_filter.set_name(_("OPML files"))
1554+ file_filter.add_pattern("*.opml")
1555+ dialog.add_filter(file_filter)
1556+
1557+ #set dialog up to allow multiple selections
1558+ dialog.set_select_multiple(True)
1559+
1560+ #run the dialog
1561+ status = dialog.run()
1562+
1563+ # if file was selected, get list of feeds from it and add to
1564+ #model and update config file
1565+ if(status == gtk.RESPONSE_OK):
1566+ FeedConfigTools().add_file_feeds_to_widget(
1567+ dialog.get_filenames(), model, self.feeds)
1568+ dialog.destroy()
1569+
1570+ def on_lifereaButton_clicked(self, widget):
1571+ """Adds any liferea feeds it finds to the feed widget and config file
1572+ """
1573+ #get the model
1574+ model = self.tree_widget.get_model()
1575+ #get the liferea feeds and then send everything to the
1576+ #add_file_feeds_to_widget method
1577+ FeedConfigTools().add_file_feeds_to_widget(
1578+ [OPMLParser().get_liferea_opml()], model, self.feeds)
1579+
1580+ def on_enterURL_clicked(self, widget):
1581+ """gets a opml file link from a user and adds any feeds it finds to the
1582+ feed widget and config file"""
1583+ url_dialog = self.widgets.get_widget("url_dialog")
1584+ model = self.tree_widget.get_model()
1585+ # Open dialog
1586+ url_dialog.set_title(_("Add OPML File"))
1587+ status = url_dialog.run()
1588+ # If folder was selected we add it to model and update config file
1589+ if(status == gtk.RESPONSE_OK):
1590+ FeedConfigTools().add_file_feeds_to_widget([self.url],
1591+ model, self.feeds)
1592+
1593+ def on_closeButton_clicked(self, widget):
1594+ self.dialog.hide()
1595+ self.dialog.destroy()
1596+
1597+ def on_url_dialog_ok_button_clicked(self, widget):
1598+ """URL dialog OK button pressed. Sets self.url"""
1599+ url_dialog = self.widgets.get_widget("url_dialog")
1600+ url_entry = self.widgets.get_widget("url_entry")
1601+ url_dialog.hide()
1602+ self.url = url_entry.get_text()
1603+ url_entry.set_text("")
1604+ url_dialog.response(gtk.RESPONSE_OK)
1605+
1606+ def on_url_dialog_cancel_button_clicked(self, widget):
1607+ """URL dialog cancelled. Hides dialog"""
1608+ url_dialog = self.widgets.get_widget("url_dialog")
1609+ url_entry = self.widgets.get_widget("url_entry")
1610+ url_dialog.hide()
1611+ url_entry.set_text("")
1612+ url_dialog.response(gtk.RESPONSE_CANCEL)
1613+
1614+ def on_url_dialog_delete_event(self, widget, data):
1615+ """Dialog's X clicked. Hides dialog"""
1616+ url_dialog = self.widgets.get_widget("url_dialog")
1617+ url_entry = self.widgets.get_widget("url_entry")
1618+ url_dialog.hide()
1619+ url_entry.set_text("")
1620+ url_dialog.response(gtk.RESPONSE_CANCEL)
1621+ return True
1622+
1623+
1624+class PreferencesDialog:
1625+ """This is a preferences editing tool for Entertainer media center."""
1626+
1627+ # Glade file location
1628+ GLADE_DIR = os.path.join(os.path.dirname(__file__), "glade")
1629+
1630+ def __init__(self, stand_alone):
1631+ """
1632+ Initialize Preferenced dialog
1633+ @param stand_alone: Is this dialog stand alone process or part of
1634+ client
1635+ """
1636+ # Is this dialog running as a stand alone process
1637+ self.STAND_ALONE = stand_alone
1638+ self.themes = []
1639+ self.config = Configuration()
1640+
1641+ # Load glade UI
1642+ self.gladefile = os.path.join(self.GLADE_DIR,
1643+ "entertainer-preferences.glade")
1644+ self.widgets = gtk.glade.XML(self.gladefile)
1645+
1646+ # Get preferences dialog and bind signal callbacks
1647+ self.pref_dialog = self.widgets.get_widget("PreferencesDialog")
1648+ if (self.pref_dialog):
1649+ callback_dic = {
1650+ "on_close_button_clicked" : self.on_close_button_clicked,
1651+ "on_PreferencesDialog_delete_event" : self.on_dialog_closed,
1652+ "on_theme_list_cursor_changed" :
1653+ self.on_theme_list_cursor_changed,
1654+ "on_checkbutton_autostart_toggled" :
1655+ self.on_checkbutton_autostart_toggled,
1656+ "on_checkbutton_fullscreen_toggled" :
1657+ self.on_checkbutton_fullscreen_toggled,
1658+ "on_checkbutton_systray_icon_toggled" :
1659+ self.on_checkbutton_systray_icon_toggled,
1660+ "on_effect_checkbox_toggled" : self.on_effect_checkbox_toggled,
1661+ "on_effect_combobox_changed" : self.on_effect_combobox_changed,
1662+ "on_add_button_clicked" : self.on_add_button_clicked,
1663+ "on_remove_button_clicked" : self.on_remove_button_clicked,
1664+ "on_spinbutton_slideshow_step_value_changed":
1665+ self.on_spinbutton_slideshow_step_value_changed
1666+ }
1667+ self.widgets.signal_autoconnect(callback_dic)
1668+
1669+ # Initilize dialog widgets with correct values and show dialog
1670+ self.init_dialog_values_from_configure_file()
1671+ #self.pref_dialog.resize(500,400)
1672+ self.pref_dialog.show()
1673+
1674+ def load_themes(self):
1675+ """Load themes"""
1676+ themes = os.listdir(os.path.join(self.config.get_cfg_dir(), 'themes'))
1677+ for element in themes:
1678+ theme = os.path.join(self.config.get_cfg_dir(), 'themes', element)
1679+ if os.path.isdir(theme):
1680+ self.themes.append(element)
1681+
1682+ def on_add_button_clicked(self, widget):
1683+ """Add theme button clicked"""
1684+ themelist = self.widgets.get_widget("theme_list")
1685+ model = themelist.get_model()
1686+ # Open "Select folder" dialog
1687+ dialog = gtk.FileChooserDialog(_("Select theme package file"),
1688+ None, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL,
1689+ gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK), None)
1690+ file_filter = gtk.FileFilter()
1691+ file_filter.set_name(_("Theme package (tar.gz)"))
1692+ file_filter.add_pattern("*.tar.gz")
1693+ dialog.add_filter(file_filter)
1694+ status = dialog.run()
1695+
1696+ # If theme was selected with file chooser
1697+ if(status == gtk.RESPONSE_OK):
1698+ package = dialog.get_filename()
1699+ tar = tarfile.open(package, 'r:gz') # Open tar.gz package
1700+
1701+ # Make sure that package contains configuration file (is theme)
1702+ content = tar.getnames()
1703+ theme_name = None
1704+ is_theme = False
1705+ for element in content:
1706+ if element[-10:] == "theme.conf":
1707+ theme_name = element[:-11]
1708+ is_theme = True
1709+
1710+ # Install them
1711+ if is_theme:
1712+ tar.extractall(os.path.join(self.config.get_cfg_dir(),
1713+ 'themes'))
1714+ model.insert(len(model), [theme_name])
1715+ else:
1716+ error = gtk.MessageDialog(None, gtk.DIALOG_MODAL,
1717+ gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Invalid theme file!"))
1718+ error.run()
1719+ error.destroy()
1720+
1721+ dialog.destroy()
1722+
1723+ def on_remove_button_clicked(self, widget):
1724+ """Remove theme button clicked"""
1725+ # Get currently selected theme
1726+ themelist = self.widgets.get_widget("theme_list")
1727+ model = themelist.get_model()
1728+ selection = themelist.get_selection().get_selected()
1729+ name = model.get_value(selection[1], 0)
1730+
1731+ confirm = gtk.MessageDialog(None,
1732+ gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
1733+ _("Are you sure you want to delete\ntheme: %(name)s") % \
1734+ {'name': name})
1735+ status = confirm.run()
1736+ confirm.destroy()
1737+ if(status == gtk.RESPONSE_YES):
1738+ themedir = os.path.join(self.config.get_cfg_dir(), 'themes', name)
1739+ shutil.rmtree(themedir)
1740+ model.remove(selection[1])
1741+ themelist.set_cursor(0)
1742+ self.themes.remove(name)
1743+
1744+ def on_theme_list_cursor_changed(self, widget):
1745+ """Executed when theme is changed in theme list. Update preview."""
1746+ # Get currently selected theme
1747+ themelist = self.widgets.get_widget("theme_list")
1748+ model = themelist.get_model()
1749+ selection = themelist.get_selection().get_selected()
1750+ name = model.get_value(selection[1], 0)
1751+ themedir = os.path.join(self.config.get_cfg_dir(), 'themes', name)
1752+ theme = Theme(theme_path=themedir)
1753+
1754+ # Update preview
1755+ image = self.widgets.get_widget("theme_image")
1756+ image.set_from_file(os.path.join(themedir, "thumbnail.png"))
1757+ name = self.widgets.get_widget("name_label")
1758+ name.set_text(theme.getName())
1759+ author = self.widgets.get_widget("author_label")
1760+ author.set_text(theme.getAuthor())
1761+ licence = self.widgets.get_widget("licence_label")
1762+ licence.set_text(theme.getLicence())
1763+ copyright_label = self.widgets.get_widget("copyright_label")
1764+ copyright_label.set_text(theme.getCopyright())
1765+ comment = self.widgets.get_widget("comment_label")
1766+ comment.set_text(theme.getComment())
1767+
1768+ self.config.write_preference_value("General", "theme", name.get_text())
1769+
1770+ def on_checkbutton_autostart_toggled(self, widget):
1771+ """Server Autostart checkbox toggled"""
1772+ self.config.write_preference_value("General", "start_server_auto",
1773+ widget.get_active())
1774+
1775+ def on_checkbutton_fullscreen_toggled(self, widget):
1776+ """Start in fullscreen checkbox toggled"""
1777+ self.config.write_preference_value("General", "start_in_fullscreen",
1778+ widget.get_active())
1779+
1780+ def on_checkbutton_systray_icon_toggled(self, widget):
1781+ """System Tray Icon checkbox toggled"""
1782+ self.config.write_preference_value("General", "display_icon",
1783+ widget.get_active())
1784+
1785+ def on_effect_checkbox_toggled(self, widget):
1786+ """Effect checkbox toggled"""
1787+ combobox = self.widgets.get_widget("effect_combobox")
1788+ combobox.set_sensitive(widget.get_active())
1789+ self.config.write_preference_value("General", "show_effects",
1790+ widget.get_active())
1791+
1792+ def on_effect_combobox_changed(self, widget):
1793+ """User changed effect for screen transitions"""
1794+ text = widget.get_active_text()
1795+ if text == _("No effect"):
1796+ english_text = "No effect"
1797+ if text == _("Crossfade"):
1798+ english_text = "Crossfade"
1799+ if text == _("Zoom and fade"):
1800+ english_text = "Zoom and fade"
1801+ if text == _("Slide"):
1802+ english_text = "Slide"
1803+ self.config.write_preference_value("General", "transition_effect",
1804+ english_text)
1805+
1806+ def on_spinbutton_slideshow_step_value_changed(self, widget):
1807+ """Activation of slideshow effects"""
1808+ self.config.write_preference_value("Photographs", "slideshow_step",
1809+ int(widget.get_value()))
1810+
1811+ def on_dialog_closed(self, widget, after):
1812+ """Callback function for dialog's close button"""
1813+ self.close()
1814+
1815+ def on_close_button_clicked(self, widget):
1816+ """Callback function for dialog's close button"""
1817+ self.close()
1818+
1819+ def close(self):
1820+ """Close Dialog"""
1821+ try:
1822+ proxy = MessageBusProxy(client_name = "Preferences GUI")
1823+ proxy.connectToMessageBus()
1824+ proxy.sendMessage(Message(MessageType.PREFERENCES_CONF_UPDATED))
1825+ proxy.disconnectFromMessageBus()
1826+ except socket.error:
1827+ error = gtk.MessageDialog(
1828+ None, gtk.DIALOG_MODAL,
1829+ gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, _(
1830+ "Entertainer backend is not running. "
1831+ "Config updates will happen on backend restart."
1832+ ))
1833+ error.run()
1834+ error.destroy()
1835+
1836+
1837+ if(self.STAND_ALONE):
1838+ self.pref_dialog.hide()
1839+ self.pref_dialog.destroy()
1840+ gtk.main_quit()
1841+ else:
1842+ self.pref_dialog.hide()
1843+ self.pref_dialog.destroy()
1844+
1845+ def init_dialog_values_from_configure_file(self):
1846+ """Read configure file and set dialog widget values with read values.
1847+ """
1848+ self.load_themes()
1849+ current_theme = self.config.get_theme_name()
1850+
1851+ themelist_widget = self.widgets.get_widget("theme_list")
1852+ model = gtk.ListStore(str)
1853+
1854+ cell_renderer = gtk.CellRendererText()
1855+ column = gtk.TreeViewColumn("Themes", cell_renderer, text=0)
1856+ themelist_widget.append_column(column)
1857+
1858+ # Fill model with installed themes
1859+ for i in range(len(self.themes)):
1860+ model.insert(i, [self.themes[i]])
1861+
1862+ themelist_widget.set_model(model)
1863+
1864+ # Set current theme selected in theme list
1865+ index = model.get_iter_first()
1866+ unselected = True
1867+ index_counter = 0
1868+ while(unselected):
1869+ name = model.get_value(index, 0)
1870+ if name == current_theme:
1871+ unselected = False
1872+ themelist_widget.set_cursor(index_counter)
1873+ index = model.iter_next(index)
1874+ index_counter += 1
1875+
1876+ # Checkboxes
1877+ checkbutton_systray_icon = self.widgets.get_widget(
1878+ "checkbutton_systray_icon")
1879+ if self.config.tray_icon_enabled():
1880+ checkbutton_systray_icon.set_active(True)
1881+ else:
1882+ checkbutton_systray_icon.set_active(False)
1883+
1884+ checkbutton_autostart = self.widgets.get_widget("checkbutton_autostart")
1885+ if self.config.start_auto_server():
1886+ checkbutton_autostart.set_active(True)
1887+ else:
1888+ checkbutton_autostart.set_active(False)
1889+
1890+ checkbutton_fullscreen = self.widgets.get_widget(
1891+ "checkbutton_fullscreen")
1892+ if self.config.start_in_fullscreen():
1893+ checkbutton_fullscreen.set_active(True)
1894+ else:
1895+ checkbutton_fullscreen.set_active(False)
1896+
1897+ effect_checkbox = self.widgets.get_widget("effect_checkbox")
1898+ effect_combobox = self.widgets.get_widget("effect_combobox")
1899+ if self.config.show_effects():
1900+ effect_checkbox.set_active(True)
1901+ effect_combobox.set_sensitive(True)
1902+ else:
1903+ effect_checkbox.set_active(False)
1904+ effect_combobox.set_sensitive(False)
1905+
1906+ spinbutton_slideshow_step = self.widgets.get_widget(
1907+ "spinbutton_slideshow_step")
1908+ spinbutton_slideshow_step.set_value(self.config.get_slideshow_step())
1909+
1910+ # Set Effect Combobox value (Text values are set in glade file)
1911+ effect = self.config.transition_effect()
1912+ if effect == "No effect":
1913+ effect_combobox.set_active(0)
1914+ if effect == "Crossfade":
1915+ effect_combobox.set_active(1)
1916+ if effect == "Zoom and fade":
1917+ effect_combobox.set_active(2)
1918+ if effect == "Slide":
1919+ effect_combobox.set_active(3)
1920+
1921
1922=== added file 'entertainerlib/download.py'
1923--- entertainerlib/download.py 1970-01-01 00:00:00 +0000
1924+++ entertainerlib/download.py 2009-05-06 04:46:51 +0000
1925@@ -0,0 +1,453 @@
1926+'''Downloader classes.'''
1927+
1928+__licence__ = "GPLv2"
1929+__copyright__ = "2009 Entertainer Developers"
1930+
1931+import locale
1932+import os
1933+import re
1934+import socket
1935+import threading
1936+import urllib
1937+from xml.dom import minidom
1938+
1939+import gobject
1940+
1941+# Amazon licence for Entertainer
1942+LICENSE_KEY = "1YCWYZ0SPPAJ27YZZ482"
1943+DEFAULT_LOCALE = "en_US"
1944+ASSOCIATE = "webservices-20"
1945+
1946+# We are not allowed to batch more than 2 requests at once
1947+# http://docs.amazonwebservices.com/AWSEcommerceService/4-0/
1948+# PgCombiningOperations.html
1949+MAX_BATCH_JOBS = 2
1950+
1951+
1952+class AlbumArtDownloader(threading.Thread):
1953+ """
1954+ Search and download album art from the internet.
1955+
1956+ This class is heavily based on Rhythmbox - AlbumArt plugin's class
1957+ 'AmazonCoverArtSearch'. That plugin is released under GPLv2 (or higher)
1958+ and it's copyrights belong to Gareth Murphy and Martin Szulecki.
1959+
1960+ See more: http://www.gnome.org/projects/rhythmbox/
1961+
1962+ If you want better cover search, please contribute to Rhyhtmbox project.
1963+ """
1964+
1965+ def __init__(self, album, artist, art_file_path, callback = None):
1966+ """
1967+ Initialize album art downloader
1968+ @param album: Album title
1969+ @param artist: Artist name
1970+ @param art_file_path: Path where albumart is saved
1971+ @param callback: Callback function that is called after search if set
1972+ """
1973+ threading.Thread.__init__(self)
1974+ self.setName("AlbumArt Downloader")
1975+ self.callback_function = callback # Callback function
1976+ self.album = album # Album title
1977+ self.artist = artist # Artist name
1978+ # Album art files are in this directory
1979+ self.path = art_file_path
1980+ (self.tld, self.encoding) = self.__get_locale ()
1981+
1982+ def run(self):
1983+ """Start searching and downloading albumart."""
1984+ self.search()
1985+
1986+ def __get_locale (self):
1987+ '''Get locale information from user\'s machine'''
1988+ # "JP is the only locale that correctly takes UTF8 input.
1989+ # All other locales use LATIN1."
1990+ # http://developer.amazonwebservices.com/connect/
1991+ # entry.jspa?externalID=1295&categoryID=117
1992+ supported_locales = {
1993+ "en_US" : ("com", "latin1"),
1994+ "en_GB" : ("co.uk", "latin1"),
1995+ "de" : ("de", "latin1"),
1996+ "ja" : ("jp", "utf8")
1997+ }
1998+
1999+ lc_id = DEFAULT_LOCALE
2000+ default = locale.getdefaultlocale ()[0]
2001+ if default:
2002+ if supported_locales.has_key (default):
2003+ lc_id = default
2004+ else:
2005+ lang = default.split("_")[0]
2006+ if supported_locales.has_key (lang):
2007+ lc_id = lang
2008+
2009+ return supported_locales[lc_id]
2010+
2011+ def __valid_match (self, item):
2012+ '''Determine if item matches tag criteria'''
2013+ return (hasattr (item, "LargeImage") or hasattr (item, "MediumImage")) \
2014+ and hasattr (item, "ItemAttributes")
2015+
2016+ def __tidy_up_string (self, str_input):
2017+ """
2018+ Tidy up string. Remove spaces, convert to lowercase and replace chars.
2019+ """
2020+ # Lowercase
2021+ str_input = str_input.lower ()
2022+ # Strip
2023+ str_input = str_input.strip ()
2024+
2025+ # TODO: Convert accented to unaccented
2026+ str_input = str_input.replace (" - ", " ")
2027+ str_input = str_input.replace (": ", " ")
2028+ str_input = str_input.replace (" & ", " and ")
2029+
2030+ return str_input
2031+
2032+ def search(self):
2033+ """Search album art from Amazon"""
2034+ self.searching = True
2035+ self.cancel = False
2036+ self.keywords = []
2037+
2038+ st_artist = self.artist or u'Unknown'
2039+ st_album = self.album or u'Unknown'
2040+
2041+ if st_artist == st_album == u'Unknown':
2042+ self.on_search_completed (None)
2043+ return
2044+
2045+ # Tidy up
2046+
2047+ # Replace quote characters
2048+ # don't replace single quote: could be important punctuation
2049+ for char in ["\""]:
2050+ st_artist = st_artist.replace (char, '')
2051+ st_album = st_album.replace (char, '')
2052+
2053+
2054+ self.st_album = st_album
2055+ self.st_artist = st_artist
2056+
2057+ # Remove variants of Disc/CD [1-9] from album title before search
2058+ for exp in ["\([Dd]isc *[1-9]+\)", "\([Cc][Dd] *[1-9]+\)"]:
2059+ p = re.compile (exp)
2060+ st_album = p.sub ('', st_album)
2061+
2062+ st_album_no_vol = st_album
2063+ for exp in ["\(*[Vv]ol.*[1-9]+\)*"]:
2064+ p = re.compile (exp)
2065+ st_album_no_vol = p.sub ('', st_album_no_vol)
2066+
2067+ self.st_album_no_vol = st_album_no_vol
2068+
2069+ # Save current search's entry properties
2070+ self.search_album = st_album
2071+ self.search_artist = st_artist
2072+ self.search_album_no_vol = st_album_no_vol
2073+
2074+ # TODO: Improve to decrease wrong cover downloads, maybe add severity?
2075+ # Assemble list of search keywords (and thus search queries)
2076+ if st_album == u'Unknown':
2077+ self.keywords.append ("%s Best of" % (st_artist))
2078+ self.keywords.append ("%s Greatest Hits" % (st_artist))
2079+ self.keywords.append ("%s Essential" % (st_artist))
2080+ self.keywords.append ("%s Collection" % (st_artist))
2081+ self.keywords.append ("%s" % (st_artist))
2082+ elif st_artist == u'Unknown':
2083+ self.keywords.append ("%s" % (st_album))
2084+ if st_album_no_vol != st_artist:
2085+ self.keywords.append ("%s" % (st_album_no_vol))
2086+ self.keywords.append ("Various %s" % (st_album))
2087+ else:
2088+ if st_album != st_artist:
2089+ self.keywords.append ("%s %s" % (st_artist, st_album))
2090+ if st_album_no_vol != st_album:
2091+ self.keywords.append ("%s %s" %
2092+ (st_artist, st_album_no_vol))
2093+ self.keywords.append ("Various %s" % (st_album))
2094+ self.keywords.append ("%s" % (st_artist))
2095+
2096+ # Initiate asynchronous search
2097+ self.search_next ()
2098+
2099+ def search_next(self):
2100+ """Search again, because the last one didn't find any covers."""
2101+ if len (self.keywords) == 0:
2102+ # No keywords left to search -> no results
2103+ self.on_search_completed (None)
2104+ return False
2105+
2106+ self.searching = True
2107+
2108+ url = "http://ecs.amazonaws." + self.tld + "/onca/xml" \
2109+ "?Service=AWSECommerceService" \
2110+ "&AWSAccessKeyId=" + LICENSE_KEY + \
2111+ "&AssociateTag=" + ASSOCIATE + \
2112+ "&ResponseGroup=Images,ItemAttributes" \
2113+ "&Operation=ItemSearch" \
2114+ "&ItemSearch.Shared.SearchIndex=Music"
2115+
2116+ job = 1
2117+ while job <= MAX_BATCH_JOBS and len (self.keywords) > 0:
2118+ keyword = self.keywords.pop (0)
2119+ keyword = keyword.encode (self.encoding, "ignore")
2120+ keyword = keyword.strip ()
2121+ keyword = urllib.quote (keyword)
2122+ url += "&ItemSearch.%d.Keywords=%s" % (job, keyword)
2123+ job += 1
2124+
2125+ # Retrieve search for keyword
2126+ temp = urllib.urlopen(url)
2127+ search_results = temp.read()
2128+ self.on_search_response(search_results)
2129+ return True
2130+
2131+ def __unmarshal(self, element):
2132+ rc = object()
2133+ child_elements = [e for e in element.childNodes if isinstance (e,
2134+ minidom.Element)]
2135+ if child_elements:
2136+ for child in child_elements:
2137+ key = child.tagName
2138+ if hasattr (rc, key):
2139+ if not isinstance (getattr (rc, key), list):
2140+ setattr (rc, key, [getattr (rc, key)])
2141+ getattr (rc, key).append (self.__unmarshal (child))
2142+ # get_best_match_urls() wants a list, even if there is only
2143+ # one item/artist
2144+ elif child.tagName in ("Items", "Item", "Artist"):
2145+ setattr (rc, key, [self.__unmarshal(child)])
2146+ else:
2147+ setattr (rc, key, self.__unmarshal(child))
2148+ else:
2149+ rc = "".join ([e.data for e in element.childNodes if isinstance (e,
2150+ minidom.Text)])
2151+ return rc
2152+
2153+ def on_search_response (self, result_data):
2154+ '''Check search results
2155+
2156+ If results are not good, we search again with the next keyword.
2157+ '''
2158+ if result_data is None:
2159+ self.search_next()
2160+ return
2161+
2162+ try:
2163+ xmldoc = minidom.parseString(result_data)
2164+ except (TypeError, AttributeError):
2165+ self.search_next()
2166+ return
2167+
2168+ data = self.__unmarshal (xmldoc)
2169+ if not hasattr (data, "ItemSearchResponse") or \
2170+ not hasattr (data.ItemSearchResponse, "Items"):
2171+ # Something went wrong ...
2172+ self.search_next ()
2173+ else:
2174+ # We got some search results
2175+ self.on_search_results (data.ItemSearchResponse.Items)
2176+
2177+ def on_search_results (self, results):
2178+ '''Results were found, now we need to take action.'''
2179+ self.on_search_completed (results)
2180+
2181+ def on_search_completed (self, result):
2182+ """
2183+ Search completed and results found.
2184+
2185+ Download large album art image from the first result and save it to
2186+ the disk. This function diverges greatly from the rhythmbox
2187+ implementation in order to avoid their loader and CoverArtDatabase
2188+ """
2189+ self.searching = False
2190+ image_urls = self.get_best_match_urls(result)
2191+ if len(image_urls) == 0:
2192+ return
2193+ image_url = image_urls[0]
2194+ image_file = urllib.urlopen(image_url)
2195+ # base64 encode artist and album so there can be a '/' in the artist
2196+ # or album
2197+ artist_album = self.artist + " - " + self.album
2198+ artist_album = artist_album.encode("base64")
2199+
2200+ dest = open(os.path.join(self.path, artist_album + ".jpg"),'w')
2201+ dest.write(image_file.read())
2202+ dest.close()
2203+
2204+ if self.callback_function is not None:
2205+ self.callback_function(self.artist, self.album)
2206+
2207+ def get_best_match_urls (self, search_results):
2208+ """Return tuple of URL's to large and medium cover of the best match"""
2209+ # Default to "no match", our results must match our criteria
2210+
2211+ # This code comes from Rhythmbox so we can't control the use of 'filter'
2212+ # pylint: disable-msg=W0141
2213+
2214+ if not search_results:
2215+ return []
2216+
2217+ best_match = None
2218+
2219+ for result in search_results:
2220+ if not hasattr (result, "Item"):
2221+ # Search was unsuccessful, try next batch job
2222+ continue
2223+
2224+ items = filter(self.__valid_match, result.Item)
2225+ if self.search_album != u'Unknown':
2226+ album_check = self.__tidy_up_string (self.search_album)
2227+ for item in items:
2228+ if not hasattr (item.ItemAttributes, "Title"):
2229+ continue
2230+
2231+ album = self.__tidy_up_string (item.ItemAttributes.Title)
2232+ if album == album_check:
2233+ # Found exact album, can not get better than that
2234+ best_match = item
2235+ break
2236+ # If we already found a best_match, just keep checking for
2237+ # exact one. Check the results for both an album name that
2238+ # contains the name we're searching for, and an album name
2239+ # that's a substring of the name we're searching for
2240+ elif (best_match is None) and \
2241+ (album.find (album_check) != -1 or
2242+ album_check.find (album) != -1):
2243+ best_match = item
2244+
2245+ # If we still have no definite hit, use first result where artist
2246+ # matches
2247+ if (self.search_album == u'Unknown' and \
2248+ self.search_artist != u'Unknown'):
2249+ artist_check = self.__tidy_up_string (self.search_artist)
2250+ if best_match is None:
2251+ # Check if artist appears in the Artists list
2252+ hit = False
2253+ for item in items:
2254+ if not hasattr (item.ItemAttributes, "Artist"):
2255+ continue
2256+
2257+ for artist in item.ItemAttributes.Artist:
2258+ artist = self.__tidy_up_string (artist)
2259+ if artist.find (artist_check) != -1:
2260+ best_match = item
2261+ hit = True
2262+ break
2263+ if hit:
2264+ break
2265+
2266+ urls = [getattr (best_match, size).URL for size in ("LargeImage",
2267+ "MediumImage")
2268+ if hasattr (best_match, size)]
2269+ if urls:
2270+ return urls
2271+
2272+ # No search was successful
2273+ return []
2274+
2275+
2276+class LyricsDownloader(object):
2277+ """
2278+ Search and download song lyrics from the internet.
2279+ Update music cache if lyrics found.
2280+ """
2281+
2282+ # The permanent user ID from Lyricsfly
2283+ # NOTICE: This is the personal user ID for Entertainer, if you want to
2284+ # experiment with the API from lyricsfly you can get an ID here =>
2285+ # http://lyricsfly.com/api/, don't use this one as abuse of our key may
2286+ # invalidate it.
2287+ _LYRICSFLY_KEY = 'YzIxOTM4M2NkNGQ4MmRmODEtZW50ZXJ0YWluZXItcHJvamVjdC5jb20='
2288+
2289+ def __init__(self, title, artist, callback):
2290+ """
2291+ Initialize lyrics downloader.
2292+ @param title: Title of the track
2293+ @param artist: Artist of the track
2294+ @param callback: Callback function which is called when search is over.
2295+ lyrics are given as a parameter to this callback function.
2296+ """
2297+ self.title = title.lower()
2298+ self.artist = artist.lower()
2299+ self.callback = callback
2300+
2301+ def search(self):
2302+ """
2303+ Search lyrics and download if found. Search is done asynchronously.
2304+ This method returns immediately and set callback is called when search
2305+ is over.
2306+ """
2307+ gobject.timeout_add(2000, self._async_search)
2308+
2309+ def _async_search(self):
2310+ """
2311+ Search lyrics and download if found
2312+ """
2313+ lyrics = ""
2314+ self._clean_up_artist_title()
2315+ lyrics_xml = self._get_lyrics_xml()
2316+ lyrics = self._parse_lyrics_xml(lyrics_xml)
2317+ self.callback(lyrics)
2318+
2319+ def _clean_up_artist_title(self):
2320+ """
2321+ Clean up the artist and title.
2322+ """
2323+ # Clean spaces
2324+ self.title = self.title.strip()
2325+ self.artist = self.artist.strip()
2326+
2327+ # Convert title and artist to use in url, special symbols have to be
2328+ # replaced by a '%' not '%xx'
2329+ # TODO: Find out what the special symbols are (', &, ...)
2330+ # not letters, digits, spaces and ()$^*=:;|#@}{][!,.-_\
2331+ self.artist = urllib.quote(self.artist.encode('utf-8'),
2332+ "'&()$^*=:;|#@}{][!,\\")
2333+ self.title = urllib.quote(self.title.encode('utf-8'),
2334+ "'&()$^*=:;|#@}{][!,\\")
2335+
2336+ self.artist = self.artist.replace("'", "%").replace("&", "%")
2337+ self.title = self.title.replace("'", "%").replace("&", "%")
2338+
2339+ def _get_lyrics_xml(self):
2340+ """
2341+ Download the lyrics XML-file.
2342+ """
2343+ lyrics_xml = None
2344+
2345+ # timeout in seconds
2346+ timeout = 5
2347+ socket.setdefaulttimeout(timeout)
2348+
2349+ url = "http://lyricsfly.com/api/api.php?i=%s&a=%s&t=%s" \
2350+ % (self._LYRICSFLY_KEY.decode('base64'), self.artist, self.title)
2351+ temp = urllib.urlopen(url)
2352+ lyrics_xml = temp.read()
2353+
2354+ return lyrics_xml
2355+
2356+ def _parse_lyrics_xml(self, lyrics_xml):
2357+ """Parse lyrics XML and return lyrics string"""
2358+ xmldoc = minidom.parseString(lyrics_xml).documentElement
2359+
2360+ # Get the lyric from the XML file
2361+ try:
2362+ lyrics = xmldoc.getElementsByTagName('tx')[0].firstChild.nodeValue
2363+ except IndexError:
2364+ return ''
2365+
2366+ # Clean spaces and enters
2367+ lyrics = lyrics.strip().replace('\n', '')
2368+ lyrics = lyrics.replace('[br]', '\n')
2369+
2370+ # Add the artist and title to the top of the lyric
2371+ lyrics = xmldoc.getElementsByTagName('ar')[0].firstChild.nodeValue + \
2372+ ' - ' + xmldoc.getElementsByTagName('tt')[0].firstChild.nodeValue + \
2373+ '\n\n' + lyrics
2374+
2375+ xmldoc.unlink()
2376+
2377+ return lyrics
2378+
2379
2380=== renamed directory 'entertainerlib/utils/glade' => 'entertainerlib/glade'
2381=== modified file 'entertainerlib/glade/system_tray_icon_menu.glade'
2382--- entertainerlib/utils/glade/system_tray_icon_menu.glade 2008-08-16 04:28:47 +0000
2383+++ entertainerlib/glade/system_tray_icon_menu.glade 2009-04-30 01:11:22 +0000
2384@@ -6,12 +6,12 @@
2385 <property name="visible">True</property>
2386 <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
2387 <child>
2388- <widget class="GtkImageMenuItem" id="menuitem_frontend">
2389+ <widget class="GtkImageMenuItem" id="menuitem_client">
2390 <property name="visible">True</property>
2391 <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
2392 <property name="label" translatable="yes">Open media center GUI</property>
2393 <property name="use_underline">True</property>
2394- <signal name="activate" handler="on_menuitem_frontend_activate"/>
2395+ <signal name="activate" handler="on_menuitem_client_activate"/>
2396 <child internal-child="image">
2397 <widget class="GtkImage" id="menu-item-image3">
2398 <property name="visible">True</property>
2399
2400=== renamed directory 'entertainerlib/frontend/gui' => 'entertainerlib/gui'
2401=== modified file 'entertainerlib/gui/screens/album.py'
2402--- entertainerlib/frontend/gui/screens/album.py 2009-04-27 03:32:30 +0000
2403+++ entertainerlib/gui/screens/album.py 2009-05-05 03:31:34 +0000
2404@@ -8,13 +8,13 @@
2405 import clutter
2406 import pango
2407
2408-from entertainerlib.frontend.gui.screens.screen import Screen
2409-from entertainerlib.frontend.gui.widgets.eyecandy_texture import (
2410+from entertainerlib.gui.screens.screen import Screen
2411+from entertainerlib.gui.widgets.eyecandy_texture import (
2412 EyeCandyTexture)
2413-from entertainerlib.frontend.gui.widgets.label import Label
2414-from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
2415-from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
2416-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
2417+from entertainerlib.gui.widgets.label import Label
2418+from entertainerlib.gui.widgets.list_indicator import ListIndicator
2419+from entertainerlib.gui.widgets.text_menu import TextMenu
2420+from entertainerlib.gui.widgets.text_menu_item import TextMenuItem
2421
2422 class Album(Screen):
2423 '''Screen that allows user to browse and play tracks of the music album.'''
2424
2425=== modified file 'entertainerlib/gui/screens/artist.py'
2426--- entertainerlib/frontend/gui/screens/artist.py 2009-04-27 03:32:30 +0000
2427+++ entertainerlib/gui/screens/artist.py 2009-05-05 03:31:34 +0000
2428@@ -6,11 +6,11 @@
2429
2430 import pango
2431
2432-from entertainerlib.frontend.gui.screens.screen import Screen
2433-from entertainerlib.frontend.gui.tabs.albums_tab import AlbumsTab
2434-from entertainerlib.frontend.gui.tabs.tracks_tab import TracksTab
2435-from entertainerlib.frontend.gui.widgets.label import Label
2436-from entertainerlib.frontend.medialibrary.playlist import Playlist
2437+from entertainerlib.gui.screens.screen import Screen
2438+from entertainerlib.gui.tabs.albums_tab import AlbumsTab
2439+from entertainerlib.gui.tabs.tracks_tab import TracksTab
2440+from entertainerlib.gui.widgets.label import Label
2441+from entertainerlib.client.medialibrary.playlist import Playlist
2442
2443 class Artist(Screen):
2444 '''Screen that allows user to browse music by artist.'''
2445
2446=== modified file 'entertainerlib/gui/screens/audio_play.py'
2447--- entertainerlib/frontend/gui/screens/audio_play.py 2009-04-27 03:32:30 +0000
2448+++ entertainerlib/gui/screens/audio_play.py 2009-05-05 03:31:34 +0000
2449@@ -9,10 +9,10 @@
2450 import gobject
2451 import clutter
2452
2453-from entertainerlib.frontend.gui.screens.screen import Screen
2454-from entertainerlib.frontend.gui.tabs.lyrics_tab import LyricsTab
2455-from entertainerlib.frontend.gui.tabs.playing_tab import PlayingTab
2456-from entertainerlib.frontend.gui.widgets.eyecandy_texture import (
2457+from entertainerlib.gui.screens.screen import Screen
2458+from entertainerlib.gui.tabs.lyrics_tab import LyricsTab
2459+from entertainerlib.gui.tabs.playing_tab import PlayingTab
2460+from entertainerlib.gui.widgets.eyecandy_texture import (
2461 EyeCandyTexture)
2462
2463 class AudioPlay(Screen):
2464
2465=== modified file 'entertainerlib/gui/screens/disc.py'
2466--- entertainerlib/frontend/gui/screens/disc.py 2009-04-27 03:32:30 +0000
2467+++ entertainerlib/gui/screens/disc.py 2009-05-06 04:46:51 +0000
2468@@ -12,18 +12,18 @@
2469 import pango
2470 import clutter
2471
2472-from entertainerlib.frontend.gui.screens.screen import Screen
2473-from entertainerlib.frontend.gui.widgets.eyecandy_texture import (
2474+from entertainerlib.gui.screens.screen import Screen
2475+from entertainerlib.gui.widgets.eyecandy_texture import (
2476 EyeCandyTexture)
2477-from entertainerlib.frontend.gui.widgets.label import Label
2478-from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
2479-from entertainerlib.frontend.gui.widgets.loading_animation import (
2480+from entertainerlib.gui.widgets.label import Label
2481+from entertainerlib.gui.widgets.list_indicator import ListIndicator
2482+from entertainerlib.gui.widgets.loading_animation import (
2483 LoadingAnimation)
2484-from entertainerlib.frontend.gui.widgets.texture import Texture
2485-from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
2486-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
2487-from entertainerlib.frontend.medialibrary.playlist import Playlist
2488-from entertainerlib.utils.albumart_downloader import AlbumArtDownloader
2489+from entertainerlib.gui.widgets.texture import Texture
2490+from entertainerlib.gui.widgets.text_menu import TextMenu
2491+from entertainerlib.gui.widgets.text_menu_item import TextMenuItem
2492+from entertainerlib.client.medialibrary.playlist import Playlist
2493+from entertainerlib.download import AlbumArtDownloader
2494
2495 class Disc(Screen):
2496 '''Screen allows user to play tracks from the current Audio CD.'''
2497
2498=== modified file 'entertainerlib/gui/screens/factory.py'
2499--- entertainerlib/frontend/gui/screens/factory.py 2009-02-07 17:12:21 +0000
2500+++ entertainerlib/gui/screens/factory.py 2009-05-05 03:31:34 +0000
2501@@ -4,25 +4,25 @@
2502 __copyright__ = '2009, Matt Layman'
2503 __author__ = 'Matt Layman <laymansterms.dev@gmail.com>'
2504
2505-from entertainerlib.frontend.gui.screens.artist import Artist
2506-from entertainerlib.frontend.gui.screens.album import Album
2507-from entertainerlib.frontend.gui.screens.audio_play import AudioPlay
2508-from entertainerlib.frontend.gui.screens.disc import Disc
2509-from entertainerlib.frontend.gui.screens.feed import Feed
2510-from entertainerlib.frontend.gui.screens.feed_entry import FeedEntry
2511-from entertainerlib.frontend.gui.screens.main import Main
2512-from entertainerlib.frontend.gui.screens.movie import Movie
2513-from entertainerlib.frontend.gui.screens.music import Music
2514-from entertainerlib.frontend.gui.screens.photo import Photo
2515-from entertainerlib.frontend.gui.screens.photo_albums import PhotoAlbums
2516-from entertainerlib.frontend.gui.screens.photographs import Photographs
2517-from entertainerlib.frontend.gui.screens.question import Question
2518-from entertainerlib.frontend.gui.screens.rss import Rss
2519-from entertainerlib.frontend.gui.screens.tv_episodes import TvEpisodes
2520-from entertainerlib.frontend.gui.screens.tv_series import TvSeries
2521-from entertainerlib.frontend.gui.screens.video import Video
2522-from entertainerlib.frontend.gui.screens.video_osd import VideoOSD
2523-from entertainerlib.frontend.gui.screens.weather import WeatherScreen
2524+from entertainerlib.gui.screens.artist import Artist
2525+from entertainerlib.gui.screens.album import Album
2526+from entertainerlib.gui.screens.audio_play import AudioPlay
2527+from entertainerlib.gui.screens.disc import Disc
2528+from entertainerlib.gui.screens.feed import Feed
2529+from entertainerlib.gui.screens.feed_entry import FeedEntry
2530+from entertainerlib.gui.screens.main import Main
2531+from entertainerlib.gui.screens.movie import Movie
2532+from entertainerlib.gui.screens.music import Music
2533+from entertainerlib.gui.screens.photo import Photo
2534+from entertainerlib.gui.screens.photo_albums import PhotoAlbums
2535+from entertainerlib.gui.screens.photographs import Photographs
2536+from entertainerlib.gui.screens.question import Question
2537+from entertainerlib.gui.screens.rss import Rss
2538+from entertainerlib.gui.screens.tv_episodes import TvEpisodes
2539+from entertainerlib.gui.screens.tv_series import TvSeries
2540+from entertainerlib.gui.screens.video import Video
2541+from entertainerlib.gui.screens.video_osd import VideoOSD
2542+from entertainerlib.gui.screens.weather import WeatherScreen
2543
2544 class ScreenFactory(object):
2545 '''Generate a screen based on the type provided.'''
2546
2547=== modified file 'entertainerlib/gui/screens/feed.py'
2548--- entertainerlib/frontend/gui/screens/feed.py 2009-04-27 03:32:30 +0000
2549+++ entertainerlib/gui/screens/feed.py 2009-05-06 04:00:53 +0000
2550@@ -6,13 +6,13 @@
2551
2552 import pango
2553
2554-from entertainerlib.frontend.gui.screens.screen import Screen
2555-from entertainerlib.frontend.gui.widgets.label import Label
2556-from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
2557-from entertainerlib.frontend.gui.widgets.texture import Texture
2558-from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
2559-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
2560-from entertainerlib.utils.feed_utils import FeedEntryParser
2561+from entertainerlib.gui.screens.screen import Screen
2562+from entertainerlib.gui.widgets.label import Label
2563+from entertainerlib.gui.widgets.list_indicator import ListIndicator
2564+from entertainerlib.gui.widgets.texture import Texture
2565+from entertainerlib.gui.widgets.text_menu import TextMenu
2566+from entertainerlib.gui.widgets.text_menu_item import TextMenuItem
2567+from entertainerlib.backend.components.feeds.feed_utils import FeedEntryParser
2568
2569 class Feed(Screen):
2570 '''Screen is displayed when headlines are accessed for a specific feed.'''
2571
2572=== modified file 'entertainerlib/gui/screens/feed_entry.py'
2573--- entertainerlib/frontend/gui/screens/feed_entry.py 2009-04-27 03:32:30 +0000
2574+++ entertainerlib/gui/screens/feed_entry.py 2009-05-06 04:00:53 +0000
2575@@ -6,12 +6,12 @@
2576
2577 import pango
2578
2579-from entertainerlib.frontend.gui.screens.screen import Screen
2580-from entertainerlib.frontend.gui.user_event import UserEvent
2581-from entertainerlib.frontend.gui.widgets.label import Label
2582-from entertainerlib.frontend.gui.widgets.scroll_area import ScrollArea
2583-from entertainerlib.frontend.gui.widgets.texture import Texture
2584-from entertainerlib.utils.feed_utils import FeedEntryParser
2585+from entertainerlib.gui.screens.screen import Screen
2586+from entertainerlib.gui.user_event import UserEvent
2587+from entertainerlib.gui.widgets.label import Label
2588+from entertainerlib.gui.widgets.scroll_area import ScrollArea
2589+from entertainerlib.gui.widgets.texture import Texture
2590+from entertainerlib.backend.components.feeds.feed_utils import FeedEntryParser
2591
2592 class FeedEntry(Screen):
2593 '''Screen displays one feed entry.'''
2594
2595=== modified file 'entertainerlib/gui/screens/main.py'
2596--- entertainerlib/frontend/gui/screens/main.py 2009-04-27 03:32:30 +0000
2597+++ entertainerlib/gui/screens/main.py 2009-05-06 04:15:18 +0000
2598@@ -4,17 +4,18 @@
2599 __copyright__ = "2007, Lauri Taimila"
2600 __author__ = "Lauri Taimila <lauri@taimila.com>"
2601
2602+import os
2603+
2604 import clutter
2605
2606-from entertainerlib.frontend.gui.screens.screen import Screen
2607-from entertainerlib.frontend.gui.widgets.clock_label import ClockLabel
2608-from entertainerlib.frontend.gui.widgets.label import Label
2609-from entertainerlib.frontend.gui.widgets.scroll_menu import ScrollMenu
2610-from entertainerlib.frontend.gui.widgets.texture import Texture
2611-from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
2612-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
2613-from entertainerlib.utils.cd_utils import eject_cd
2614-from entertainerlib.utils.feed_utils import FeedEntryParser
2615+from entertainerlib.gui.screens.screen import Screen
2616+from entertainerlib.gui.widgets.clock_label import ClockLabel
2617+from entertainerlib.gui.widgets.label import Label
2618+from entertainerlib.gui.widgets.scroll_menu import ScrollMenu
2619+from entertainerlib.gui.widgets.texture import Texture
2620+from entertainerlib.gui.widgets.text_menu import TextMenu
2621+from entertainerlib.gui.widgets.text_menu_item import TextMenuItem
2622+from entertainerlib.backend.components.feeds.feed_utils import FeedEntryParser
2623
2624 class Main(Screen):
2625 '''Screen displayed when frontend is opened and provides main navigation.'''
2626@@ -102,11 +103,11 @@
2627 item5 = Label(size, color, 0, 0, _("Headlines"), "rss")
2628 self.menu.add(item5)
2629
2630- if self.config.display_weather_in_frontend():
2631+ if self.config.display_weather_in_client():
2632 item6 = Label(size, color, 0, 0, _("Weather"), "weather")
2633 self.menu.add(item6)
2634
2635- if self.config.display_cd_eject_in_frontend():
2636+ if self.config.display_cd_eject_in_client():
2637 item7 = Label(size, color, 0, 0, _("Eject CD"), "eject_cd")
2638 self.menu.add(item7)
2639
2640@@ -310,7 +311,7 @@
2641 elif item.get_name() == "weather":
2642 self.callback("weather")
2643 elif item.get_name() == "eject_cd":
2644- eject_cd()
2645+ os.system('eject')
2646 elif item.get_name() == "photo":
2647 self.callback("photo_albums")
2648 elif item.get_name() == "rss":
2649
2650=== modified file 'entertainerlib/gui/screens/movie.py'
2651--- entertainerlib/frontend/gui/screens/movie.py 2009-04-27 03:32:30 +0000
2652+++ entertainerlib/gui/screens/movie.py 2009-05-05 03:31:34 +0000
2653@@ -8,15 +8,15 @@
2654 import gtk
2655 import pango
2656
2657-from entertainerlib.frontend.gui.screens.screen import Screen
2658-from entertainerlib.frontend.gui.user_event import UserEvent
2659-from entertainerlib.frontend.gui.widgets.eyecandy_texture import (
2660+from entertainerlib.gui.screens.screen import Screen
2661+from entertainerlib.gui.user_event import UserEvent
2662+from entertainerlib.gui.widgets.eyecandy_texture import (
2663 EyeCandyTexture)
2664-from entertainerlib.frontend.gui.widgets.label import Label
2665-from entertainerlib.frontend.gui.widgets.scroll_area import ScrollArea
2666-from entertainerlib.frontend.gui.widgets.texture import Texture
2667-from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
2668-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
2669+from entertainerlib.gui.widgets.label import Label
2670+from entertainerlib.gui.widgets.scroll_area import ScrollArea
2671+from entertainerlib.gui.widgets.texture import Texture
2672+from entertainerlib.gui.widgets.text_menu import TextMenu
2673+from entertainerlib.gui.widgets.text_menu_item import TextMenuItem
2674
2675 class Movie(Screen):
2676 '''Screen contains information of one movie.'''
2677
2678=== modified file 'entertainerlib/gui/screens/music.py'
2679--- entertainerlib/frontend/gui/screens/music.py 2009-04-27 03:32:30 +0000
2680+++ entertainerlib/gui/screens/music.py 2009-05-05 03:31:34 +0000
2681@@ -4,12 +4,12 @@
2682 __copyright__ = "2007, Lauri Taimila"
2683 __author__ = "Lauri Taimila <lauri@taimila.com>"
2684
2685-from entertainerlib.frontend.gui.screens.screen import Screen
2686-from entertainerlib.frontend.gui.tabs.albums_tab import AlbumsTab
2687-from entertainerlib.frontend.gui.tabs.artists_tab import ArtistsTab
2688-from entertainerlib.frontend.gui.widgets.label import Label
2689-from entertainerlib.frontend.gui.widgets.texture import Texture
2690-from entertainerlib.frontend.medialibrary.playlist import Playlist
2691+from entertainerlib.gui.screens.screen import Screen
2692+from entertainerlib.gui.tabs.albums_tab import AlbumsTab
2693+from entertainerlib.gui.tabs.artists_tab import ArtistsTab
2694+from entertainerlib.gui.widgets.label import Label
2695+from entertainerlib.gui.widgets.texture import Texture
2696+from entertainerlib.client.medialibrary.playlist import Playlist
2697
2698 class Music(Screen):
2699 '''Screen that allows user to browse music library content.'''
2700
2701=== modified file 'entertainerlib/gui/screens/photo.py'
2702--- entertainerlib/frontend/gui/screens/photo.py 2009-04-27 03:32:30 +0000
2703+++ entertainerlib/gui/screens/photo.py 2009-05-05 03:31:34 +0000
2704@@ -7,9 +7,9 @@
2705 import clutter
2706 import gobject
2707
2708-from entertainerlib.frontend.gui.screens.screen import Screen
2709-from entertainerlib.frontend.gui.widgets.label import Label
2710-from entertainerlib.frontend.gui.widgets.texture import Texture
2711+from entertainerlib.gui.screens.screen import Screen
2712+from entertainerlib.gui.widgets.label import Label
2713+from entertainerlib.gui.widgets.texture import Texture
2714
2715 class Photo(Screen):
2716 '''Screen displays photograph in fullscreen and allows user to zoom in.'''
2717
2718=== modified file 'entertainerlib/gui/screens/photo_albums.py'
2719--- entertainerlib/frontend/gui/screens/photo_albums.py 2009-04-27 03:32:30 +0000
2720+++ entertainerlib/gui/screens/photo_albums.py 2009-05-05 03:31:34 +0000
2721@@ -9,14 +9,14 @@
2722 import gobject
2723 import clutter
2724
2725-from entertainerlib.frontend.gui.screens.screen import Screen
2726-from entertainerlib.frontend.gui.widgets.eyecandy_texture import (
2727+from entertainerlib.gui.screens.screen import Screen
2728+from entertainerlib.gui.widgets.eyecandy_texture import (
2729 EyeCandyTexture)
2730-from entertainerlib.frontend.gui.widgets.label import Label
2731-from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
2732-from entertainerlib.frontend.gui.widgets.texture import Texture
2733-from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
2734-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
2735+from entertainerlib.gui.widgets.label import Label
2736+from entertainerlib.gui.widgets.list_indicator import ListIndicator
2737+from entertainerlib.gui.widgets.texture import Texture
2738+from entertainerlib.gui.widgets.text_menu import TextMenu
2739+from entertainerlib.gui.widgets.text_menu_item import TextMenuItem
2740
2741 class PhotoAlbums(Screen):
2742 '''Screen contains a list of photo albums and album previews.'''
2743
2744=== modified file 'entertainerlib/gui/screens/photographs.py'
2745--- entertainerlib/frontend/gui/screens/photographs.py 2009-04-27 03:32:30 +0000
2746+++ entertainerlib/gui/screens/photographs.py 2009-05-05 03:31:34 +0000
2747@@ -8,14 +8,14 @@
2748 import clutter
2749 import gobject
2750
2751-from entertainerlib.frontend.gui.screens.screen import Screen
2752-from entertainerlib.frontend.gui.widgets.image_menu import ImageMenu
2753-from entertainerlib.frontend.gui.widgets.label import Label
2754-from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
2755-from entertainerlib.frontend.gui.widgets.image_menu_item import ImageMenuItem
2756-from entertainerlib.frontend.gui.widgets.loading_animation import (
2757+from entertainerlib.gui.screens.screen import Screen
2758+from entertainerlib.gui.widgets.image_menu import ImageMenu
2759+from entertainerlib.gui.widgets.label import Label
2760+from entertainerlib.gui.widgets.list_indicator import ListIndicator
2761+from entertainerlib.gui.widgets.image_menu_item import ImageMenuItem
2762+from entertainerlib.gui.widgets.loading_animation import (
2763 LoadingAnimation)
2764-from entertainerlib.frontend.gui.widgets.texture import Texture
2765+from entertainerlib.gui.widgets.texture import Texture
2766
2767 class Photographs(Screen):
2768 '''Screen displays a grid of selectable photograph thumbnails.'''
2769
2770=== modified file 'entertainerlib/gui/screens/question.py'
2771--- entertainerlib/frontend/gui/screens/question.py 2009-04-27 03:32:30 +0000
2772+++ entertainerlib/gui/screens/question.py 2009-05-05 03:31:34 +0000
2773@@ -4,11 +4,11 @@
2774 __copyright__ = "2008, Teje Rodgers"
2775 __author__ = "Teje Rodgers <contact@tejerodgers.com>"
2776
2777-from entertainerlib.frontend.gui.screens.screen import Screen
2778-from entertainerlib.frontend.gui.transitions.transition import Transition
2779-from entertainerlib.frontend.gui.widgets.label import Label
2780-from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
2781-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
2782+from entertainerlib.gui.screens.screen import Screen
2783+from entertainerlib.gui.transitions.transition import Transition
2784+from entertainerlib.gui.widgets.label import Label
2785+from entertainerlib.gui.widgets.text_menu import TextMenu
2786+from entertainerlib.gui.widgets.text_menu_item import TextMenuItem
2787
2788 class Question(Screen):
2789 '''Screen is displayed when the application needs to ask a close ended
2790
2791=== modified file 'entertainerlib/gui/screens/rss.py'
2792--- entertainerlib/frontend/gui/screens/rss.py 2009-04-27 03:32:30 +0000
2793+++ entertainerlib/gui/screens/rss.py 2009-05-06 04:00:53 +0000
2794@@ -6,16 +6,16 @@
2795
2796 import pango
2797
2798-from entertainerlib.frontend.gui.screens.screen import Screen
2799-from entertainerlib.frontend.gui.widgets.label import Label
2800-from entertainerlib.frontend.gui.widgets.menu import Menu
2801-from entertainerlib.frontend.gui.widgets.selector import Selector
2802-from entertainerlib.frontend.gui.widgets.texture import Texture
2803-from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
2804-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
2805-from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
2806-from entertainerlib.frontend.medialibrary.feeds import Feed
2807-from entertainerlib.utils.feed_utils import FeedEntryParser
2808+from entertainerlib.gui.screens.screen import Screen
2809+from entertainerlib.gui.widgets.label import Label
2810+from entertainerlib.gui.widgets.menu import Menu
2811+from entertainerlib.gui.widgets.selector import Selector
2812+from entertainerlib.gui.widgets.texture import Texture
2813+from entertainerlib.gui.widgets.text_menu import TextMenu
2814+from entertainerlib.gui.widgets.text_menu_item import TextMenuItem
2815+from entertainerlib.gui.widgets.list_indicator import ListIndicator
2816+from entertainerlib.client.medialibrary.feeds import Feed
2817+from entertainerlib.backend.components.feeds.feed_utils import FeedEntryParser
2818
2819 class Rss(Screen):
2820 '''Screen displays RSS-feed titles and number of entries.'''
2821
2822=== modified file 'entertainerlib/gui/screens/screen.py'
2823--- entertainerlib/frontend/gui/screens/screen.py 2009-04-30 01:16:33 +0000
2824+++ entertainerlib/gui/screens/screen.py 2009-05-05 03:31:34 +0000
2825@@ -9,9 +9,9 @@
2826 import clutter
2827
2828 from entertainerlib.exceptions import ScreenException
2829-from entertainerlib.frontend.gui.user_event import UserEvent
2830-from entertainerlib.frontend.gui.widgets.base import Base
2831-from entertainerlib.frontend.gui.widgets.tab_group import TabGroup
2832+from entertainerlib.gui.user_event import UserEvent
2833+from entertainerlib.gui.widgets.base import Base
2834+from entertainerlib.gui.widgets.tab_group import TabGroup
2835
2836 class Screen(Base, clutter.Group):
2837 """
2838
2839=== modified file 'entertainerlib/gui/screens/tv_episodes.py'
2840--- entertainerlib/frontend/gui/screens/tv_episodes.py 2009-04-27 03:32:30 +0000
2841+++ entertainerlib/gui/screens/tv_episodes.py 2009-05-05 03:31:34 +0000
2842@@ -7,15 +7,15 @@
2843 import gtk
2844 import pango
2845
2846-from entertainerlib.frontend.gui.screens.screen import Screen
2847-from entertainerlib.frontend.gui.user_event import UserEvent
2848-from entertainerlib.frontend.gui.widgets.eyecandy_texture import (
2849+from entertainerlib.gui.screens.screen import Screen
2850+from entertainerlib.gui.user_event import UserEvent
2851+from entertainerlib.gui.widgets.eyecandy_texture import (
2852 EyeCandyTexture)
2853-from entertainerlib.frontend.gui.widgets.label import Label
2854-from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
2855-from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
2856-from entertainerlib.frontend.gui.widgets.scroll_area import ScrollArea
2857-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
2858+from entertainerlib.gui.widgets.label import Label
2859+from entertainerlib.gui.widgets.list_indicator import ListIndicator
2860+from entertainerlib.gui.widgets.text_menu import TextMenu
2861+from entertainerlib.gui.widgets.scroll_area import ScrollArea
2862+from entertainerlib.gui.widgets.text_menu_item import TextMenuItem
2863
2864 class TvEpisodes(Screen):
2865 '''Screen contains list of all episodes of one specific season.'''
2866
2867=== modified file 'entertainerlib/gui/screens/tv_series.py'
2868--- entertainerlib/frontend/gui/screens/tv_series.py 2009-04-27 03:32:30 +0000
2869+++ entertainerlib/gui/screens/tv_series.py 2009-05-05 03:31:34 +0000
2870@@ -6,13 +6,13 @@
2871
2872 import gtk
2873
2874-from entertainerlib.frontend.gui.screens.screen import Screen
2875-from entertainerlib.frontend.gui.widgets.eyecandy_texture import (
2876+from entertainerlib.gui.screens.screen import Screen
2877+from entertainerlib.gui.widgets.eyecandy_texture import (
2878 EyeCandyTexture)
2879-from entertainerlib.frontend.gui.widgets.label import Label
2880-from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
2881-from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
2882-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
2883+from entertainerlib.gui.widgets.label import Label
2884+from entertainerlib.gui.widgets.list_indicator import ListIndicator
2885+from entertainerlib.gui.widgets.text_menu import TextMenu
2886+from entertainerlib.gui.widgets.text_menu_item import TextMenuItem
2887
2888 class TvSeries(Screen):
2889 '''Screen that contains all seasons of one TV series.'''
2890
2891=== modified file 'entertainerlib/gui/screens/video.py'
2892--- entertainerlib/frontend/gui/screens/video.py 2009-04-27 03:32:30 +0000
2893+++ entertainerlib/gui/screens/video.py 2009-05-05 03:31:34 +0000
2894@@ -4,11 +4,11 @@
2895 __copyright__ = "2008, Lauri Taimila"
2896 __author__ = "Lauri Taimila <lauri@taimila.com>"
2897
2898-from entertainerlib.frontend.gui.screens.screen import Screen
2899-from entertainerlib.frontend.gui.tabs.movies_tab import MoviesTab
2900-from entertainerlib.frontend.gui.tabs.series_tab import SeriesTab
2901-from entertainerlib.frontend.gui.tabs.video_clips_tab import VideoClipsTab
2902-from entertainerlib.frontend.gui.widgets.label import Label
2903+from entertainerlib.gui.screens.screen import Screen
2904+from entertainerlib.gui.tabs.movies_tab import MoviesTab
2905+from entertainerlib.gui.tabs.series_tab import SeriesTab
2906+from entertainerlib.gui.tabs.video_clips_tab import VideoClipsTab
2907+from entertainerlib.gui.widgets.label import Label
2908
2909 class Video(Screen):
2910 '''Screen contains tabs for different video types in the video library.'''
2911
2912=== modified file 'entertainerlib/gui/screens/video_osd.py'
2913--- entertainerlib/frontend/gui/screens/video_osd.py 2009-04-27 03:32:30 +0000
2914+++ entertainerlib/gui/screens/video_osd.py 2009-05-05 03:31:34 +0000
2915@@ -7,11 +7,11 @@
2916 import gobject
2917 import clutter
2918
2919-from entertainerlib.frontend.gui.screens.screen import Screen
2920-from entertainerlib.frontend.gui.user_event import UserEvent
2921-from entertainerlib.frontend.gui.widgets.label import Label
2922-from entertainerlib.frontend.gui.widgets.progress_bar import ProgressBar
2923-from entertainerlib.frontend.gui.widgets.texture import Texture
2924+from entertainerlib.gui.screens.screen import Screen
2925+from entertainerlib.gui.user_event import UserEvent
2926+from entertainerlib.gui.widgets.label import Label
2927+from entertainerlib.gui.widgets.progress_bar import ProgressBar
2928+from entertainerlib.gui.widgets.texture import Texture
2929
2930 class VideoOSD(Screen):
2931 '''Screen is displayed when video is being watched.
2932
2933=== modified file 'entertainerlib/gui/screens/weather.py'
2934--- entertainerlib/frontend/gui/screens/weather.py 2009-04-27 03:32:30 +0000
2935+++ entertainerlib/gui/screens/weather.py 2009-05-06 05:10:14 +0000
2936@@ -4,10 +4,10 @@
2937 __copyright__ = "2008, Jamie Bennett"
2938 __author__ = "Jamie Bennett <jamie@linuxuk.org>"
2939
2940-from entertainerlib.frontend.gui.screens.screen import Screen
2941-from entertainerlib.frontend.gui.widgets.label import Label
2942-from entertainerlib.frontend.gui.widgets.texture import Texture
2943-from entertainerlib.utils.weather import Weather
2944+from entertainerlib.gui.screens.screen import Screen
2945+from entertainerlib.gui.widgets.label import Label
2946+from entertainerlib.gui.widgets.texture import Texture
2947+from entertainerlib.weather import Weather
2948
2949 class WeatherScreen(Screen):
2950 '''Screen to display the user's set weather location.'''
2951
2952=== renamed file 'entertainerlib/utils/system_tray_icon.py' => 'entertainerlib/gui/system_tray_icon.py'
2953--- entertainerlib/utils/system_tray_icon.py 2009-02-07 00:28:11 +0000
2954+++ entertainerlib/gui/system_tray_icon.py 2009-05-06 03:08:19 +0000
2955@@ -9,11 +9,10 @@
2956 import gtk
2957 import gtk.glade
2958
2959-from entertainerlib.utils.log_viewer import LogViewer
2960-from entertainerlib.utils.preferences_dialog import PreferencesDialog
2961-from entertainerlib.utils.content_management_dialog import (
2962- ContentManagementDialog)
2963-from entertainerlib.utils.configuration import Configuration
2964+from entertainerlib.configuration import Configuration
2965+from entertainerlib.dialog import (ContentManagementDialog, LogViewer,
2966+ PreferencesDialog)
2967+
2968
2969 class SystemTrayIcon:
2970 """Implements system tray icon for entertainer."""
2971@@ -43,8 +42,8 @@
2972 os.path.join(self.GLADE_DIR, "system_tray_icon_menu.glade"))
2973
2974 # Bind menu signals
2975- callback_dic = {"on_menuitem_frontend_activate"
2976- : self.on_menuitem_frontend_activate,
2977+ callback_dic = {"on_menuitem_client_activate"
2978+ : self.on_menuitem_client_activate,
2979 "on_menuitem_preferences_activate"
2980 : self.on_menuitem_preferences_activate,
2981 "on_menuitem_content_management_activate"
2982@@ -68,7 +67,7 @@
2983 self.icon_widget.connect('popup-menu', self.open_popup_menu)
2984
2985 def systray_icon_activated(self, widget, data= None):
2986- """Switch visibility of frontend when system tray icon is clicked"""
2987+ """Switch visibility of client when system tray icon is clicked"""
2988 self.toggle_interface_visibility_callback()
2989
2990 def open_popup_menu(self, widget, button, time, data = None):
2991@@ -76,9 +75,9 @@
2992 self.popup.show_all()
2993 self.popup.popup(None, None, None, 3, time)
2994
2995- def on_menuitem_frontend_activate(self, widget):
2996- """Execute frontend here if not running. Show if running"""
2997- self.set_frontend_visible(True)
2998+ def on_menuitem_client_activate(self, widget):
2999+ """Execute client here if not running. Show if running"""
3000+ self.set_client_visible(True)
3001
3002 def on_menuitem_preferences_activate(self, widget):
3003 """Executes preferences-tool."""
3004
3005=== modified file 'entertainerlib/gui/tabs/albums_tab.py'
3006--- entertainerlib/frontend/gui/tabs/albums_tab.py 2009-02-03 23:37:57 +0000
3007+++ entertainerlib/gui/tabs/albums_tab.py 2009-05-05 03:31:34 +0000
3008@@ -9,14 +9,14 @@
3009 import gobject
3010 import pango
3011
3012-from entertainerlib.frontend.gui.tabs.tab import Tab
3013-from entertainerlib.frontend.gui.widgets.image_menu import ImageMenu
3014-from entertainerlib.frontend.gui.widgets.image_menu_item import ImageMenuItem
3015-from entertainerlib.frontend.gui.widgets.label import Label
3016-from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
3017-from entertainerlib.frontend.gui.widgets.loading_animation import (
3018+from entertainerlib.gui.tabs.tab import Tab
3019+from entertainerlib.gui.widgets.image_menu import ImageMenu
3020+from entertainerlib.gui.widgets.image_menu_item import ImageMenuItem
3021+from entertainerlib.gui.widgets.label import Label
3022+from entertainerlib.gui.widgets.list_indicator import ListIndicator
3023+from entertainerlib.gui.widgets.loading_animation import (
3024 LoadingAnimation)
3025-from entertainerlib.frontend.gui.widgets.texture import Texture
3026+from entertainerlib.gui.widgets.texture import Texture
3027
3028 class AlbumsTab(Tab):
3029 '''Tab to show album listings'''
3030
3031=== modified file 'entertainerlib/gui/tabs/artists_tab.py'
3032--- entertainerlib/frontend/gui/tabs/artists_tab.py 2009-02-03 23:37:57 +0000
3033+++ entertainerlib/gui/tabs/artists_tab.py 2009-05-05 03:31:34 +0000
3034@@ -9,13 +9,13 @@
3035 import gobject
3036 import pango
3037
3038-from entertainerlib.frontend.gui.tabs.tab import Tab
3039-from entertainerlib.frontend.gui.widgets.label import Label
3040-from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
3041-from entertainerlib.frontend.gui.widgets.loading_animation import (
3042+from entertainerlib.gui.tabs.tab import Tab
3043+from entertainerlib.gui.widgets.label import Label
3044+from entertainerlib.gui.widgets.list_indicator import ListIndicator
3045+from entertainerlib.gui.widgets.loading_animation import (
3046 LoadingAnimation)
3047-from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
3048-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
3049+from entertainerlib.gui.widgets.text_menu import TextMenu
3050+from entertainerlib.gui.widgets.text_menu_item import TextMenuItem
3051
3052 class ArtistsTab(Tab):
3053 '''Tab for the music screen to show artist listings'''
3054
3055=== modified file 'entertainerlib/gui/tabs/lyrics_tab.py'
3056--- entertainerlib/frontend/gui/tabs/lyrics_tab.py 2009-02-03 23:37:57 +0000
3057+++ entertainerlib/gui/tabs/lyrics_tab.py 2009-05-05 03:31:34 +0000
3058@@ -7,11 +7,11 @@
3059 import pango
3060 import clutter
3061
3062-from entertainerlib.frontend.gui.tabs.tab import Tab
3063-from entertainerlib.frontend.gui.widgets.label import Label
3064-from entertainerlib.frontend.gui.widgets.loading_animation import (
3065+from entertainerlib.gui.tabs.tab import Tab
3066+from entertainerlib.gui.widgets.label import Label
3067+from entertainerlib.gui.widgets.loading_animation import (
3068 LoadingAnimation)
3069-from entertainerlib.frontend.gui.widgets.scroll_area import ScrollArea
3070+from entertainerlib.gui.widgets.scroll_area import ScrollArea
3071
3072 class LyricsTab(Tab):
3073 '''Tab for the audio play screen to show lyrics'''
3074
3075=== modified file 'entertainerlib/gui/tabs/movies_tab.py'
3076--- entertainerlib/frontend/gui/tabs/movies_tab.py 2009-02-03 23:37:57 +0000
3077+++ entertainerlib/gui/tabs/movies_tab.py 2009-05-05 03:31:34 +0000
3078@@ -8,13 +8,13 @@
3079 import gtk
3080 import pango
3081
3082-from entertainerlib.frontend.gui.tabs.tab import Tab
3083-from entertainerlib.frontend.gui.widgets.image_menu import ImageMenu
3084-from entertainerlib.frontend.gui.widgets.image_menu_item import ImageMenuItem
3085-from entertainerlib.frontend.gui.widgets.label import Label
3086-from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
3087-from entertainerlib.frontend.gui.widgets.rounded_texture import RoundedTexture
3088-from entertainerlib.frontend.gui.widgets.texture import Texture
3089+from entertainerlib.gui.tabs.tab import Tab
3090+from entertainerlib.gui.widgets.image_menu import ImageMenu
3091+from entertainerlib.gui.widgets.image_menu_item import ImageMenuItem
3092+from entertainerlib.gui.widgets.label import Label
3093+from entertainerlib.gui.widgets.list_indicator import ListIndicator
3094+from entertainerlib.gui.widgets.rounded_texture import RoundedTexture
3095+from entertainerlib.gui.widgets.texture import Texture
3096
3097 class MoviesTab(Tab):
3098 """
3099
3100=== modified file 'entertainerlib/gui/tabs/playing_tab.py'
3101--- entertainerlib/frontend/gui/tabs/playing_tab.py 2009-02-06 08:33:50 +0000
3102+++ entertainerlib/gui/tabs/playing_tab.py 2009-05-05 03:31:34 +0000
3103@@ -6,9 +6,9 @@
3104
3105 import pango
3106
3107-from entertainerlib.frontend.gui.tabs.tab import Tab
3108-from entertainerlib.frontend.gui.widgets.label import Label
3109-from entertainerlib.frontend.gui.widgets.progress_bar import ProgressBar
3110+from entertainerlib.gui.tabs.tab import Tab
3111+from entertainerlib.gui.widgets.label import Label
3112+from entertainerlib.gui.widgets.progress_bar import ProgressBar
3113
3114 class PlayingTab(Tab):
3115 '''Tab for the audio play screen to show currently playing audio'''
3116
3117=== modified file 'entertainerlib/gui/tabs/series_tab.py'
3118--- entertainerlib/frontend/gui/tabs/series_tab.py 2009-02-03 23:37:57 +0000
3119+++ entertainerlib/gui/tabs/series_tab.py 2009-05-05 03:31:34 +0000
3120@@ -8,13 +8,13 @@
3121 import gtk
3122 import pango
3123
3124-from entertainerlib.frontend.gui.tabs.tab import Tab
3125-from entertainerlib.frontend.gui.widgets.image_menu import ImageMenu
3126-from entertainerlib.frontend.gui.widgets.image_menu_item import ImageMenuItem
3127-from entertainerlib.frontend.gui.widgets.label import Label
3128-from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
3129-from entertainerlib.frontend.gui.widgets.rounded_texture import RoundedTexture
3130-from entertainerlib.frontend.gui.widgets.texture import Texture
3131+from entertainerlib.gui.tabs.tab import Tab
3132+from entertainerlib.gui.widgets.image_menu import ImageMenu
3133+from entertainerlib.gui.widgets.image_menu_item import ImageMenuItem
3134+from entertainerlib.gui.widgets.label import Label
3135+from entertainerlib.gui.widgets.list_indicator import ListIndicator
3136+from entertainerlib.gui.widgets.rounded_texture import RoundedTexture
3137+from entertainerlib.gui.widgets.texture import Texture
3138
3139 class SeriesTab(Tab):
3140 """
3141
3142=== modified file 'entertainerlib/gui/tabs/tab.py'
3143--- entertainerlib/frontend/gui/tabs/tab.py 2009-02-28 17:22:46 +0000
3144+++ entertainerlib/gui/tabs/tab.py 2009-05-05 03:31:34 +0000
3145@@ -6,10 +6,10 @@
3146
3147 import clutter
3148
3149-from entertainerlib.frontend.gui.user_event import UserEvent
3150-from entertainerlib.frontend.gui.widgets.base import Base
3151-from entertainerlib.frontend.gui.widgets.label import Label
3152-from entertainerlib.frontend.gui.widgets.texture import Texture
3153+from entertainerlib.gui.user_event import UserEvent
3154+from entertainerlib.gui.widgets.base import Base
3155+from entertainerlib.gui.widgets.label import Label
3156+from entertainerlib.gui.widgets.texture import Texture
3157
3158 class Tab(Base, clutter.Group):
3159 """
3160
3161=== modified file 'entertainerlib/gui/tabs/tracks_tab.py'
3162--- entertainerlib/frontend/gui/tabs/tracks_tab.py 2009-02-03 23:37:57 +0000
3163+++ entertainerlib/gui/tabs/tracks_tab.py 2009-05-05 03:31:34 +0000
3164@@ -7,11 +7,11 @@
3165 import clutter
3166 import pango
3167
3168-from entertainerlib.frontend.gui.tabs.tab import Tab
3169-from entertainerlib.frontend.gui.widgets.label import Label
3170-from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
3171-from entertainerlib.frontend.gui.widgets.text_menu import TextMenu
3172-from entertainerlib.frontend.gui.widgets.text_menu_item import TextMenuItem
3173+from entertainerlib.gui.tabs.tab import Tab
3174+from entertainerlib.gui.widgets.label import Label
3175+from entertainerlib.gui.widgets.list_indicator import ListIndicator
3176+from entertainerlib.gui.widgets.text_menu import TextMenu
3177+from entertainerlib.gui.widgets.text_menu_item import TextMenuItem
3178
3179 class TracksTab(Tab):
3180 '''Tab for the artist screen to show track listings'''
3181
3182=== modified file 'entertainerlib/gui/tabs/video_clips_tab.py'
3183--- entertainerlib/frontend/gui/tabs/video_clips_tab.py 2009-02-03 23:37:57 +0000
3184+++ entertainerlib/gui/tabs/video_clips_tab.py 2009-05-05 03:31:34 +0000
3185@@ -9,12 +9,12 @@
3186 import gtk
3187 import pango
3188
3189-from entertainerlib.frontend.gui.tabs.tab import Tab
3190-from entertainerlib.frontend.gui.widgets.image_menu import ImageMenu
3191-from entertainerlib.frontend.gui.widgets.image_menu_item import ImageMenuItem
3192-from entertainerlib.frontend.gui.widgets.label import Label
3193-from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
3194-from entertainerlib.frontend.gui.widgets.rounded_texture import RoundedTexture
3195+from entertainerlib.gui.tabs.tab import Tab
3196+from entertainerlib.gui.widgets.image_menu import ImageMenu
3197+from entertainerlib.gui.widgets.image_menu_item import ImageMenuItem
3198+from entertainerlib.gui.widgets.label import Label
3199+from entertainerlib.gui.widgets.list_indicator import ListIndicator
3200+from entertainerlib.gui.widgets.rounded_texture import RoundedTexture
3201
3202 class VideoClipsTab(Tab):
3203 """
3204
3205=== renamed file 'entertainerlib/utils/theme.py' => 'entertainerlib/gui/theme.py'
3206=== modified file 'entertainerlib/gui/transitions/factory.py'
3207--- entertainerlib/frontend/gui/transitions/factory.py 2009-02-07 17:12:21 +0000
3208+++ entertainerlib/gui/transitions/factory.py 2009-05-05 04:11:48 +0000
3209@@ -5,11 +5,11 @@
3210 __copyright__ = '2009, Matt Layman'
3211 __author__ = 'Matt Layman <laymansterms.dev@gmail.com>'
3212
3213-from entertainerlib.frontend.gui.transitions.fade import Fade
3214-from entertainerlib.frontend.gui.transitions.no_effect import NoEffect
3215-from entertainerlib.frontend.gui.transitions.slide import Slide
3216-from entertainerlib.frontend.gui.transitions.zoom_and_fade import ZoomAndFade
3217-from entertainerlib.utils.configuration import Configuration
3218+from entertainerlib.gui.transitions.fade import Fade
3219+from entertainerlib.gui.transitions.no_effect import NoEffect
3220+from entertainerlib.gui.transitions.slide import Slide
3221+from entertainerlib.gui.transitions.zoom_and_fade import ZoomAndFade
3222+from entertainerlib.configuration import Configuration
3223
3224 class TransitionFactory:
3225 '''Generates a transition object based on the configuration setting'''
3226
3227=== modified file 'entertainerlib/gui/transitions/fade.py'
3228--- entertainerlib/frontend/gui/transitions/fade.py 2009-01-26 23:11:40 +0000
3229+++ entertainerlib/gui/transitions/fade.py 2009-05-05 03:31:34 +0000
3230@@ -5,7 +5,7 @@
3231 __author__ = "Lauri Taimila <lauri@taimila.com>"
3232
3233 import clutter
3234-from entertainerlib.frontend.gui.transitions.transition import Transition
3235+from entertainerlib.gui.transitions.transition import Transition
3236
3237 class Fade(Transition):
3238 """
3239
3240=== modified file 'entertainerlib/gui/transitions/no_effect.py'
3241--- entertainerlib/frontend/gui/transitions/no_effect.py 2009-01-26 23:11:40 +0000
3242+++ entertainerlib/gui/transitions/no_effect.py 2009-05-05 03:31:34 +0000
3243@@ -4,7 +4,7 @@
3244 __copyright__ = "2007, Lauri Taimila"
3245 __author__ = "Lauri Taimila <lauri@taimila.com>"
3246
3247-from entertainerlib.frontend.gui.transitions.transition import Transition
3248+from entertainerlib.gui.transitions.transition import Transition
3249
3250 class NoEffect(Transition):
3251 """
3252
3253=== modified file 'entertainerlib/gui/transitions/slide.py'
3254--- entertainerlib/frontend/gui/transitions/slide.py 2009-04-27 02:33:38 +0000
3255+++ entertainerlib/gui/transitions/slide.py 2009-05-05 04:11:48 +0000
3256@@ -6,8 +6,8 @@
3257
3258 import clutter
3259
3260-from entertainerlib.frontend.gui.transitions.transition import Transition
3261-from entertainerlib.utils.configuration import Configuration
3262+from entertainerlib.gui.transitions.transition import Transition
3263+from entertainerlib.configuration import Configuration
3264
3265 class Slide(Transition):
3266 '''Move screens on and off the stage through horizontal movements.'''
3267
3268=== modified file 'entertainerlib/gui/transitions/zoom_and_fade.py'
3269--- entertainerlib/frontend/gui/transitions/zoom_and_fade.py 2009-01-26 23:11:40 +0000
3270+++ entertainerlib/gui/transitions/zoom_and_fade.py 2009-05-05 03:31:34 +0000
3271@@ -5,7 +5,7 @@
3272 __author__ = "Lauri Taimila <lauri@taimila.com>"
3273
3274 import clutter
3275-from entertainerlib.frontend.gui.transitions.transition import Transition
3276+from entertainerlib.gui.transitions.transition import Transition
3277
3278 class ZoomAndFade(Transition):
3279 """
3280
3281=== modified file 'entertainerlib/gui/user_interface.py'
3282--- entertainerlib/frontend/gui/user_interface.py 2009-04-27 03:32:30 +0000
3283+++ entertainerlib/gui/user_interface.py 2009-05-05 04:24:32 +0000
3284@@ -1,4 +1,4 @@
3285-'''UserInterface - Main window of the Entertainer frontend'''
3286+'''UserInterface - Main window of the Entertainer client'''
3287
3288 # Clutter uses _1 to represent the 1 key and pylint complains about it
3289 # pylint: disable-msg=W0212
3290@@ -16,30 +16,30 @@
3291 import gobject
3292 import gtk
3293
3294-from entertainerlib.frontend.gui.screen_history import ScreenHistory
3295-from entertainerlib.frontend.gui.screens.factory import ScreenFactory
3296-from entertainerlib.frontend.gui.screens.screen import Screen
3297-from entertainerlib.frontend.gui.transitions.factory import TransitionFactory
3298-from entertainerlib.frontend.gui.transitions.transition import Transition
3299-from entertainerlib.frontend.gui.user_event import UserEvent
3300-from entertainerlib.frontend.gui.widgets.menu_overlay import MenuOverlay
3301-from entertainerlib.frontend.media_player import MediaPlayer
3302-from entertainerlib.utils.configuration import Configuration
3303-from entertainerlib.utils.logger import Logger
3304+from entertainerlib.gui.screen_history import ScreenHistory
3305+from entertainerlib.gui.screens.factory import ScreenFactory
3306+from entertainerlib.gui.screens.screen import Screen
3307+from entertainerlib.gui.transitions.factory import TransitionFactory
3308+from entertainerlib.gui.transitions.transition import Transition
3309+from entertainerlib.gui.user_event import UserEvent
3310+from entertainerlib.gui.widgets.menu_overlay import MenuOverlay
3311+from entertainerlib.client.media_player import MediaPlayer
3312+from entertainerlib.configuration import Configuration
3313+from entertainerlib.logger import Logger
3314
3315 class UserInterface:
3316 '''A main GUI window of the Entertainer client.'''
3317
3318 def __init__(self, feed_library, image_library, music_library,
3319- video_library, quit_frontend_callback):
3320- self.quit_frontend_callback = quit_frontend_callback
3321+ video_library, quit_client_callback):
3322+ self.quit_client_callback = quit_client_callback
3323 self.config = Configuration()
3324
3325 # Store the dimensions in case users want to return to window mode
3326 self.old_width = self.config.get_stage_width()
3327 self.old_height = self.config.get_stage_height()
3328
3329- self.logger = Logger().getLogger('frontend.gui.UserInterface')
3330+ self.logger = Logger().getLogger('client.gui.UserInterface')
3331
3332 self.window = gtk.Window()
3333 self.window.connect('destroy', self.destroy_callback)
3334@@ -53,7 +53,7 @@
3335 except gobject.GError:
3336 # Must not be installed from a package, get icon from the branch
3337 file_dir = os.path.dirname(__file__)
3338- icon_path = os.path.join(file_dir, '..', '..', '..', 'icons',
3339+ icon_path = os.path.join(file_dir, '..', '..', 'icons',
3340 'hicolor', '48x48', 'apps', 'entertainer.png')
3341 icon = gtk.gdk.pixbuf_new_from_file(icon_path)
3342 self.window.set_icon(icon)
3343@@ -170,7 +170,7 @@
3344 UserEvent.PLAYER_SKIP_FORWARD : self._handle_player_skip_forward,
3345 UserEvent.PLAYER_PREVIOUS : self._handle_player_previous,
3346 UserEvent.PLAYER_NEXT : self._handle_player_next,
3347- UserEvent.QUIT_FRONTEND : self._handle_quit_frontend
3348+ UserEvent.QUIT_FRONTEND : self._handle_quit_client
3349 }
3350
3351 self.logger.debug("Frontend GUI initialized succesfully")
3352@@ -210,7 +210,7 @@
3353
3354 def shutdown(self):
3355 '''Shut down the user interface.'''
3356- self.quit_frontend_callback()
3357+ self.quit_client_callback()
3358
3359 def _toggle_fullscreen(self):
3360 '''Set the User Interface to fullscreen mode or back to window mode.'''
3361@@ -384,7 +384,7 @@
3362 '''Handle UserEvent.TOGGLE_FULLSCREEN.'''
3363 self._toggle_fullscreen()
3364
3365- def _handle_quit_frontend(self, event):
3366+ def _handle_quit_client(self, event):
3367 '''Handle UserEvent.QUIT_FRONTEND.'''
3368 self.confirm_exit()
3369
3370
3371=== modified file 'entertainerlib/gui/widgets/base.py'
3372--- entertainerlib/frontend/gui/widgets/base.py 2009-03-08 03:16:25 +0000
3373+++ entertainerlib/gui/widgets/base.py 2009-05-05 04:11:48 +0000
3374@@ -4,7 +4,7 @@
3375 __copyright__ = "2008, Matt Layman"
3376 __author__ = "Matt Layman <laymansterm.dev@gmail.com>"
3377
3378-from entertainerlib.utils.configuration import Configuration
3379+from entertainerlib.configuration import Configuration
3380
3381 class Base(object):
3382 """Inherited class that allows for transforms from relative to absolute for
3383
3384=== modified file 'entertainerlib/gui/widgets/clock_label.py'
3385--- entertainerlib/frontend/gui/widgets/clock_label.py 2008-11-29 22:44:32 +0000
3386+++ entertainerlib/gui/widgets/clock_label.py 2009-05-05 03:31:34 +0000
3387@@ -8,7 +8,7 @@
3388 import time
3389 import gobject
3390
3391-from entertainerlib.frontend.gui.widgets.label import Label
3392+from entertainerlib.gui.widgets.label import Label
3393
3394 class ClockLabel(Label):
3395 """Label that displays current time."""
3396
3397=== modified file 'entertainerlib/gui/widgets/eyecandy_texture.py'
3398--- entertainerlib/frontend/gui/widgets/eyecandy_texture.py 2008-11-17 21:54:45 +0000
3399+++ entertainerlib/gui/widgets/eyecandy_texture.py 2009-05-05 03:31:34 +0000
3400@@ -5,8 +5,8 @@
3401 __author__ = "Lauri Taimila <lauri@taimila.com>"
3402
3403 import clutter
3404-from entertainerlib.frontend.gui.widgets.rounded_texture import RoundedTexture
3405-from entertainerlib.frontend.gui.widgets.reflection_texture import (
3406+from entertainerlib.gui.widgets.rounded_texture import RoundedTexture
3407+from entertainerlib.gui.widgets.reflection_texture import (
3408 ReflectionTexture)
3409
3410 class EyeCandyTexture(clutter.Group):
3411
3412=== modified file 'entertainerlib/gui/widgets/image_menu.py'
3413--- entertainerlib/frontend/gui/widgets/image_menu.py 2009-02-08 07:43:25 +0000
3414+++ entertainerlib/gui/widgets/image_menu.py 2009-05-05 04:11:48 +0000
3415@@ -6,8 +6,8 @@
3416
3417 import clutter
3418
3419-from entertainerlib.frontend.gui.widgets.grid_menu import GridMenu
3420-from entertainerlib.utils.configuration import Configuration
3421+from entertainerlib.gui.widgets.grid_menu import GridMenu
3422+from entertainerlib.configuration import Configuration
3423
3424 class ImageMenu(GridMenu):
3425 """
3426
3427=== modified file 'entertainerlib/gui/widgets/image_menu_item.py'
3428--- entertainerlib/frontend/gui/widgets/image_menu_item.py 2009-01-11 00:25:55 +0000
3429+++ entertainerlib/gui/widgets/image_menu_item.py 2009-05-05 03:31:34 +0000
3430@@ -5,7 +5,7 @@
3431 __author__ = ("Lauri Taimila <lauri@taimila.com>",
3432 "Matt Layman <laymansterms.dev@gmail.com>")
3433
3434-from entertainerlib.frontend.gui.widgets.menu_item import MenuItem
3435+from entertainerlib.gui.widgets.menu_item import MenuItem
3436
3437 class ImageMenuItem(MenuItem):
3438 """
3439
3440=== modified file 'entertainerlib/gui/widgets/label.py'
3441--- entertainerlib/frontend/gui/widgets/label.py 2009-03-08 03:16:25 +0000
3442+++ entertainerlib/gui/widgets/label.py 2009-05-05 03:31:34 +0000
3443@@ -6,7 +6,7 @@
3444
3445 import clutter
3446
3447-from entertainerlib.frontend.gui.widgets.base import Base
3448+from entertainerlib.gui.widgets.base import Base
3449
3450 class Label(Base, clutter.Label):
3451 """Wrapper of a clutter label to encapsulate some label settings and have
3452
3453=== modified file 'entertainerlib/gui/widgets/list_indicator.py'
3454--- entertainerlib/frontend/gui/widgets/list_indicator.py 2008-11-17 21:53:05 +0000
3455+++ entertainerlib/gui/widgets/list_indicator.py 2009-05-05 04:11:48 +0000
3456@@ -6,9 +6,9 @@
3457
3458 import clutter
3459
3460-from entertainerlib.frontend.gui.widgets.arrow_texture import ArrowTexture
3461-from entertainerlib.frontend.gui.widgets.label import Label
3462-from entertainerlib.utils.configuration import Configuration
3463+from entertainerlib.gui.widgets.arrow_texture import ArrowTexture
3464+from entertainerlib.gui.widgets.label import Label
3465+from entertainerlib.configuration import Configuration
3466
3467 class ListIndicator(clutter.Group):
3468 """
3469
3470=== modified file 'entertainerlib/gui/widgets/menu_item.py'
3471--- entertainerlib/frontend/gui/widgets/menu_item.py 2009-01-11 00:25:55 +0000
3472+++ entertainerlib/gui/widgets/menu_item.py 2009-05-05 03:31:34 +0000
3473@@ -6,7 +6,7 @@
3474
3475 import clutter
3476
3477-from entertainerlib.frontend.gui.widgets.base import Base
3478+from entertainerlib.gui.widgets.base import Base
3479
3480 class MenuItem(Base, clutter.Group):
3481 """
3482
3483=== modified file 'entertainerlib/gui/widgets/menu_overlay.py'
3484--- entertainerlib/frontend/gui/widgets/menu_overlay.py 2008-11-17 11:20:47 +0000
3485+++ entertainerlib/gui/widgets/menu_overlay.py 2009-05-05 03:31:34 +0000
3486@@ -6,7 +6,7 @@
3487
3488 import clutter
3489
3490-from entertainerlib.frontend.gui.widgets.texture import Texture
3491+from entertainerlib.gui.widgets.texture import Texture
3492
3493 class MenuOverlay(Texture):
3494 """
3495
3496=== modified file 'entertainerlib/gui/widgets/scroll_area.py'
3497--- entertainerlib/frontend/gui/widgets/scroll_area.py 2009-03-08 03:16:25 +0000
3498+++ entertainerlib/gui/widgets/scroll_area.py 2009-05-05 03:31:34 +0000
3499@@ -6,8 +6,8 @@
3500
3501 import clutter
3502
3503-from entertainerlib.frontend.gui.widgets.base import Base
3504-from entertainerlib.frontend.gui.widgets.list_indicator import ListIndicator
3505+from entertainerlib.gui.widgets.base import Base
3506+from entertainerlib.gui.widgets.list_indicator import ListIndicator
3507
3508 class ScrollArea(Base, clutter.Group):
3509 """Wrapper of a clutter Group that allows for scrolling. ScrollArea
3510
3511=== modified file 'entertainerlib/gui/widgets/selector.py'
3512--- entertainerlib/frontend/gui/widgets/selector.py 2008-11-26 11:20:54 +0000
3513+++ entertainerlib/gui/widgets/selector.py 2009-05-05 03:31:34 +0000
3514@@ -6,7 +6,7 @@
3515
3516 import clutter
3517
3518-from entertainerlib.frontend.gui.widgets.texture import Texture
3519+from entertainerlib.gui.widgets.texture import Texture
3520
3521 class Selector(clutter.Group):
3522 """
3523
3524=== modified file 'entertainerlib/gui/widgets/tab_group.py'
3525--- entertainerlib/frontend/gui/widgets/tab_group.py 2009-02-27 00:01:53 +0000
3526+++ entertainerlib/gui/widgets/tab_group.py 2009-05-05 04:11:48 +0000
3527@@ -7,9 +7,9 @@
3528 import clutter
3529 import pango
3530
3531-from entertainerlib.frontend.gui.widgets.label import Label
3532-from entertainerlib.frontend.gui.user_event import UserEvent
3533-from entertainerlib.utils.configuration import Configuration
3534+from entertainerlib.gui.widgets.label import Label
3535+from entertainerlib.gui.user_event import UserEvent
3536+from entertainerlib.configuration import Configuration
3537
3538 class TabGroup(clutter.Group):
3539 """
3540
3541=== modified file 'entertainerlib/gui/widgets/text_menu.py'
3542--- entertainerlib/frontend/gui/widgets/text_menu.py 2009-02-08 07:43:25 +0000
3543+++ entertainerlib/gui/widgets/text_menu.py 2009-05-05 03:31:34 +0000
3544@@ -4,8 +4,8 @@
3545 __copyright__ = "2007, Lauri Taimila"
3546 __author__ = "Lauri Taimila <lauri@taimila.com>"
3547
3548-from entertainerlib.frontend.gui.widgets.selector import Selector
3549-from entertainerlib.frontend.gui.widgets.grid_menu import GridMenu
3550+from entertainerlib.gui.widgets.selector import Selector
3551+from entertainerlib.gui.widgets.grid_menu import GridMenu
3552
3553 class TextMenu(GridMenu):
3554 """
3555
3556=== modified file 'entertainerlib/gui/widgets/text_menu_item.py'
3557--- entertainerlib/frontend/gui/widgets/text_menu_item.py 2008-11-25 23:20:28 +0000
3558+++ entertainerlib/gui/widgets/text_menu_item.py 2009-05-05 03:31:34 +0000
3559@@ -7,8 +7,8 @@
3560 import pango
3561 import clutter
3562
3563-from entertainerlib.frontend.gui.widgets.label import Label
3564-from entertainerlib.frontend.gui.widgets.menu_item import MenuItem
3565+from entertainerlib.gui.widgets.label import Label
3566+from entertainerlib.gui.widgets.menu_item import MenuItem
3567
3568 class TextMenuItem(MenuItem):
3569 """
3570
3571=== modified file 'entertainerlib/gui/widgets/texture.py'
3572--- entertainerlib/frontend/gui/widgets/texture.py 2009-03-08 03:16:25 +0000
3573+++ entertainerlib/gui/widgets/texture.py 2009-05-05 04:24:32 +0000
3574@@ -6,8 +6,8 @@
3575
3576 import clutter
3577
3578-from entertainerlib.frontend.gui.widgets.base import Base
3579-from entertainerlib.utils.logger import Logger
3580+from entertainerlib.gui.widgets.base import Base
3581+from entertainerlib.logger import Logger
3582
3583 class Texture(Base, clutter.Texture):
3584 """Wrapper of a clutter texture to encapsulate position settings and have
3585@@ -19,7 +19,7 @@
3586
3587 Base.__init__(self)
3588 clutter.Texture.__init__(self, filename)
3589- self.logger = Logger().getLogger('frontend.gui.widgets.Texture')
3590+ self.logger = Logger().getLogger('client.gui.widgets.Texture')
3591
3592 self._position = None
3593 self._set_position((x_pos_percent, y_pos_percent))
3594
3595=== renamed file 'entertainerlib/utils/logger.py' => 'entertainerlib/logger.py'
3596--- entertainerlib/utils/logger.py 2009-01-11 02:26:45 +0000
3597+++ entertainerlib/logger.py 2009-05-05 04:24:32 +0000
3598@@ -6,7 +6,7 @@
3599
3600 import logging
3601
3602-from entertainerlib.utils.configuration import Configuration
3603+from entertainerlib.configuration import Configuration
3604
3605 class Logger:
3606 '''Logger to record behind the scenes information.
3607
3608=== modified file 'entertainerlib/network/__init__.py'
3609--- entertainerlib/network/__init__.py 2009-04-04 05:30:04 +0000
3610+++ entertainerlib/network/__init__.py 2009-04-30 01:11:22 +0000
3611@@ -26,7 +26,7 @@
3612 def client_main():
3613 '''Entertainer test client code.
3614
3615- This code will go away when what is now the frontend is made into a true
3616+ This code will go away when what is now the client is made into a true
3617 client.
3618 '''
3619 startLogging(sys.stdout)
3620
3621=== modified file 'entertainerlib/tests/__init__.py'
3622--- entertainerlib/tests/__init__.py 2009-02-06 07:23:29 +0000
3623+++ entertainerlib/tests/__init__.py 2009-05-05 04:11:48 +0000
3624@@ -5,9 +5,9 @@
3625 from storm.locals import Store
3626 import testtools
3627
3628-from entertainerlib.backend.core.db.connection import Database
3629-from entertainerlib.frontend.translation_setup import TranslationSetup
3630-from entertainerlib.utils.configuration import Configuration
3631+from entertainerlib.db.connection import Database
3632+from entertainerlib.client.translation_setup import TranslationSetup
3633+from entertainerlib.configuration import Configuration
3634
3635 TranslationSetup()
3636
3637
3638=== modified file 'entertainerlib/tests/mock.py'
3639--- entertainerlib/tests/mock.py 2009-02-27 02:16:34 +0000
3640+++ entertainerlib/tests/mock.py 2009-04-30 01:11:22 +0000
3641@@ -1,23 +1,23 @@
3642 '''Test mock objects'''
3643 # pylint: disable-msg=W0231
3644
3645-from entertainerlib.frontend.backend_connection import BackendConnection
3646-from entertainerlib.frontend.media_player import MediaPlayer
3647-from entertainerlib.frontend.medialibrary.feeds import Entry
3648-from entertainerlib.frontend.medialibrary.feeds import Feed
3649-from entertainerlib.frontend.medialibrary.feeds import FeedLibrary
3650-from entertainerlib.frontend.medialibrary.images import Image
3651-from entertainerlib.frontend.medialibrary.images import ImageLibrary
3652-from entertainerlib.frontend.medialibrary.music import Album
3653-from entertainerlib.frontend.medialibrary.music import MusicLibrary
3654-from entertainerlib.frontend.medialibrary.music import Track
3655-from entertainerlib.frontend.medialibrary.videos import Movie
3656-from entertainerlib.frontend.medialibrary.videos import TVEpisode
3657-from entertainerlib.frontend.medialibrary.videos import TVSeries
3658-from entertainerlib.frontend.medialibrary.videos import VideoLibrary
3659+from entertainerlib.client.backend_connection import BackendConnection
3660+from entertainerlib.client.media_player import MediaPlayer
3661+from entertainerlib.client.medialibrary.feeds import Entry
3662+from entertainerlib.client.medialibrary.feeds import Feed
3663+from entertainerlib.client.medialibrary.feeds import FeedLibrary
3664+from entertainerlib.client.medialibrary.images import Image
3665+from entertainerlib.client.medialibrary.images import ImageLibrary
3666+from entertainerlib.client.medialibrary.music import Album
3667+from entertainerlib.client.medialibrary.music import MusicLibrary
3668+from entertainerlib.client.medialibrary.music import Track
3669+from entertainerlib.client.medialibrary.videos import Movie
3670+from entertainerlib.client.medialibrary.videos import TVEpisode
3671+from entertainerlib.client.medialibrary.videos import TVSeries
3672+from entertainerlib.client.medialibrary.videos import VideoLibrary
3673
3674 class MockAlbum(Album):
3675- '''Mock entertainerlib.frontend.medialibrary.music.Album'''
3676+ '''Mock entertainerlib.client.medialibrary.music.Album'''
3677
3678 def __init__(self, title=None, cursor=None, make_track=True):
3679 self.title = 'Ganging Up on the Sun'
3680@@ -43,7 +43,7 @@
3681
3682
3683 class MockBackendConnection(BackendConnection):
3684- '''Mock entertainerlib.frontend.backend_connection.BackendConnection'''
3685+ '''Mock entertainerlib.client.backend_connection.BackendConnection'''
3686
3687 def __init__(self):
3688 '''Override init to prevent BackenConnection from attempting to connect
3689@@ -67,7 +67,7 @@
3690
3691
3692 class MockEntry(Entry):
3693- '''Mock entertainerlib.frontend.medialibrary.feeds.Entry'''
3694+ '''Mock entertainerlib.client.medialibrary.feeds.Entry'''
3695
3696 def __init__(self, title=None, description=None, identifier=None,
3697 time=None, date=None, read=False):
3698@@ -81,7 +81,7 @@
3699
3700
3701 class MockFeed(Feed):
3702- '''Mock entertainerlib.frontend.medialibrary.feeds.Feed'''
3703+ '''Mock entertainerlib.client.medialibrary.feeds.Feed'''
3704
3705 def __init__(self, url=None):
3706 self.date = 'January 31st'
3707@@ -95,7 +95,7 @@
3708
3709
3710 class MockFeedLibrary(FeedLibrary):
3711- '''Mock entertainerlib.frontend.medialibrary.feeds.FeedLibrary'''
3712+ '''Mock entertainerlib.client.medialibrary.feeds.FeedLibrary'''
3713
3714 def __init__(self, backend_connection=None):
3715 '''Override the intial behavior.'''
3716@@ -106,7 +106,7 @@
3717
3718
3719 class MockImage(Image):
3720- '''Mock entertainerlib.frontend.medialibrary.images.Image'''
3721+ '''Mock entertainerlib.client.medialibrary.images.Image'''
3722
3723 def __init__(self, filename=None, album_path=None, title=None,
3724 description=None, date=None, time=None, width=None, height=None,
3725@@ -117,7 +117,7 @@
3726
3727
3728 class MockImageLibrary(ImageLibrary):
3729- '''Mock entertainerlib.frontend.medialibrary.images.ImageLibrary'''
3730+ '''Mock entertainerlib.client.medialibrary.images.ImageLibrary'''
3731
3732 def __init__(self, backend_connection=None):
3733 '''Override the intial behavior.'''
3734@@ -128,7 +128,7 @@
3735
3736
3737 class MockMediaPlayer(MediaPlayer):
3738- '''Mock entertainerlib.frontend.media_player.MediaPlayer'''
3739+ '''Mock entertainerlib.client.media_player.MediaPlayer'''
3740
3741 def __init__(self, stage=None, width=None, height=None):
3742 '''Override any actual media player set up.'''
3743@@ -149,7 +149,7 @@
3744
3745
3746 class MockMovie(Movie):
3747- '''Mock entertainerlib.frontend.medialibrary.videos.Movie'''
3748+ '''Mock entertainerlib.client.medialibrary.videos.Movie'''
3749
3750 def __init__(self, filename=None):
3751 '''Override init to prevent a database connection'''
3752@@ -195,7 +195,7 @@
3753 return False
3754
3755 class MockMusicLibrary(MusicLibrary):
3756- '''Mock entertainerlib.frontend.medialibrary.music.MusicLibrary'''
3757+ '''Mock entertainerlib.client.medialibrary.music.MusicLibrary'''
3758
3759 def __init__(self, backend_connection=None):
3760 '''Override the intial behavior.'''
3761@@ -222,7 +222,7 @@
3762
3763
3764 class MockTVEpisode(TVEpisode):
3765- '''Mock entertainerlib.frontend.medialibrary.videos.TVEpisode'''
3766+ '''Mock entertainerlib.client.medialibrary.videos.TVEpisode'''
3767
3768 def __init__(self, title=None):
3769 '''Override init to prevent a database connection'''
3770@@ -245,7 +245,7 @@
3771
3772
3773 class MockTVSeries(TVSeries):
3774- '''Mock entertainerlib.frontend.medialibrary.videos.TVSeries'''
3775+ '''Mock entertainerlib.client.medialibrary.videos.TVSeries'''
3776
3777 def __init__(self, title=None):
3778 '''Override init to prevent a database connection'''
3779@@ -268,7 +268,7 @@
3780 return False
3781
3782 class MockTrack(Track):
3783- '''Mock entertainerlib.frontend.medialibrary.music.Track'''
3784+ '''Mock entertainerlib.client.medialibrary.music.Track'''
3785
3786 def __init__(self, filename=None, title=None, tracknumber=None, artist=None,
3787 album=None, genre=None, bitrate=None, year=None, rating=None,
3788@@ -287,7 +287,7 @@
3789
3790
3791 class MockVideoLibrary(VideoLibrary):
3792- '''Mock entertainerlib.frontend.medialibrary.videos.VideoLibrary'''
3793+ '''Mock entertainerlib.client.medialibrary.videos.VideoLibrary'''
3794
3795 def __init__(self, backend_connection=None):
3796 '''Override the intial behavior.'''
3797
3798=== modified file 'entertainerlib/tests/test_base.py'
3799--- entertainerlib/tests/test_base.py 2009-03-08 03:16:25 +0000
3800+++ entertainerlib/tests/test_base.py 2009-05-05 03:31:34 +0000
3801@@ -4,11 +4,11 @@
3802 __copyright__ = "2008, Matt Layman"
3803 __author__ = "Matt Layman <laymansterms.dev@gmail.com>"
3804
3805-from entertainerlib.frontend.gui.widgets.base import Base
3806+from entertainerlib.gui.widgets.base import Base
3807 from entertainerlib.tests import EntertainerTest
3808
3809 class BaseTest(EntertainerTest):
3810- """Test for entertainerlib.frontend.gui.widgets.base"""
3811+ """Test for entertainerlib.gui.widgets.base"""
3812
3813 def setUp(self):
3814 """Set up the test"""
3815
3816=== modified file 'entertainerlib/tests/test_configuration.py'
3817--- entertainerlib/tests/test_configuration.py 2009-03-08 03:16:25 +0000
3818+++ entertainerlib/tests/test_configuration.py 2009-05-05 04:11:48 +0000
3819@@ -8,7 +8,7 @@
3820 import os
3821
3822 from entertainerlib.tests import EntertainerTest
3823-from entertainerlib.utils.configuration import Configuration
3824+from entertainerlib.configuration import Configuration
3825
3826 class ConfigurationTest(EntertainerTest):
3827 '''Test for utils.configuration'''
3828
3829=== modified file 'entertainerlib/tests/test_database.py'
3830--- entertainerlib/tests/test_database.py 2009-01-31 21:36:56 +0000
3831+++ entertainerlib/tests/test_database.py 2009-05-05 03:35:28 +0000
3832@@ -8,7 +8,7 @@
3833
3834 from storm.locals import Store
3835
3836-from entertainerlib.backend.core.db.connection import Database, SCHEMA
3837+from entertainerlib.db.connection import Database, SCHEMA
3838 from entertainerlib.tests import EntertainerTestWithDatabase
3839
3840 class DatabaseTest(EntertainerTestWithDatabase):
3841
3842=== modified file 'entertainerlib/tests/test_feedconfigtools.py'
3843--- entertainerlib/tests/test_feedconfigtools.py 2009-02-08 07:01:25 +0000
3844+++ entertainerlib/tests/test_feedconfigtools.py 2009-05-06 04:00:53 +0000
3845@@ -9,14 +9,17 @@
3846 import gtk
3847 import gtk.glade
3848
3849-from entertainerlib.utils.feed_utils import FeedConfigTools
3850+from entertainerlib.backend.components.feeds.feed_utils import FeedConfigTools
3851 from entertainerlib.tests import EntertainerTest
3852
3853 THIS_DIR = os.path.dirname(__file__)
3854
3855
3856 class FeedConfigToolsTest(EntertainerTest):
3857- '''Test for entertainerlib.utils.feed_utils FeedConfigTools'''
3858+ # pylint: disable-msg=C0301
3859+ # This pylint warning can be removed when the backend stuff gets removed,
3860+ # and the pieces we keep get rearranged.
3861+ '''Test for entertainerlib.backend.components.feeds.feed_utils.FeedConfigTools'''
3862
3863 def setUp(self):
3864 """Sets up everything for the test"""
3865
3866=== modified file 'entertainerlib/tests/test_feedentryparser.py'
3867--- entertainerlib/tests/test_feedentryparser.py 2009-02-08 07:01:25 +0000
3868+++ entertainerlib/tests/test_feedentryparser.py 2009-05-06 04:00:53 +0000
3869@@ -7,7 +7,7 @@
3870
3871 import unittest
3872
3873-from entertainerlib.utils.feed_utils import FeedEntryParser
3874+from entertainerlib.backend.components.feeds.feed_utils import FeedEntryParser
3875
3876
3877 class FeedEntryParserTest(unittest.TestCase):
3878
3879=== modified file 'entertainerlib/tests/test_frontendfeed.py'
3880--- entertainerlib/tests/test_frontendfeed.py 2009-01-31 21:36:56 +0000
3881+++ entertainerlib/tests/test_frontendfeed.py 2009-04-30 01:11:22 +0000
3882@@ -1,4 +1,4 @@
3883-'''Tests feeds from the frontend'''
3884+'''Tests feeds from the client'''
3885
3886 __licence__ = "GPLv2"
3887 __copyright__ = "2008, Joshua Scotton"
3888@@ -6,11 +6,11 @@
3889
3890 from pysqlite2 import dbapi2 as sqlite
3891
3892-from entertainerlib.frontend.medialibrary.feeds import Feed
3893+from entertainerlib.client.medialibrary.feeds import Feed
3894 from entertainerlib.tests import EntertainerTest
3895
3896 class FrontendFeedTest(EntertainerTest):
3897- '''Test reading feeds from the frontend'''
3898+ '''Test reading feeds from the client'''
3899
3900 def setUp(self):
3901 """Sets up everything for the test"""
3902
3903=== modified file 'entertainerlib/tests/test_frontendfeedentry.py'
3904--- entertainerlib/tests/test_frontendfeedentry.py 2009-02-06 08:33:50 +0000
3905+++ entertainerlib/tests/test_frontendfeedentry.py 2009-04-30 01:11:22 +0000
3906@@ -1,4 +1,4 @@
3907-'''Tests feed entries on the frontend'''
3908+'''Tests feed entries on the client'''
3909 # pylint: disable-msg=W0212
3910
3911 __licence__ = "GPLv2"
3912@@ -7,11 +7,11 @@
3913
3914 from pysqlite2 import dbapi2 as sqlite
3915
3916-from entertainerlib.frontend.medialibrary.feeds import Feed, Entry
3917+from entertainerlib.client.medialibrary.feeds import Feed, Entry
3918 from entertainerlib.tests import EntertainerTest
3919
3920 class FrontendFeedEntryTest(EntertainerTest):
3921- '''Tests feed entries on the frontend'''
3922+ '''Tests feed entries on the client'''
3923
3924 entry_title = "Test Entry"
3925 entry_desc = "This is a test description"
3926
3927=== modified file 'entertainerlib/tests/test_frontendfeedlibrary.py'
3928--- entertainerlib/tests/test_frontendfeedlibrary.py 2009-02-04 04:17:37 +0000
3929+++ entertainerlib/tests/test_frontendfeedlibrary.py 2009-04-30 01:11:22 +0000
3930@@ -1,4 +1,4 @@
3931-'''Tests feed library from the frontend'''
3932+'''Tests feed library from the client'''
3933
3934 __licence__ = "GPLv2"
3935 __copyright__ = "2008, Joshua Scotton"
3936@@ -6,13 +6,13 @@
3937
3938 from pysqlite2 import dbapi2 as sqlite
3939
3940-from entertainerlib.frontend.medialibrary.feeds import FeedLibrary
3941+from entertainerlib.client.medialibrary.feeds import FeedLibrary
3942 from entertainerlib.tests import EntertainerTest
3943 from entertainerlib.tests.mock import MockBackendConnection
3944
3945 class FrontendFeedLibraryTest(EntertainerTest):
3946 """
3947- Tests the frontend.medialibrary.feeds.FeedLibrary object
3948+ Tests the client.medialibrary.feeds.FeedLibrary object
3949 @todo comment tests
3950 @todo the test database doesn't seem to be working
3951 """
3952
3953=== modified file 'entertainerlib/tests/test_imagemenuitem.py'
3954--- entertainerlib/tests/test_imagemenuitem.py 2009-02-06 08:33:50 +0000
3955+++ entertainerlib/tests/test_imagemenuitem.py 2009-05-05 03:31:34 +0000
3956@@ -7,14 +7,14 @@
3957
3958 import os
3959
3960-from entertainerlib.frontend.gui.widgets.image_menu_item import ImageMenuItem
3961-from entertainerlib.frontend.gui.widgets.texture import Texture
3962+from entertainerlib.gui.widgets.image_menu_item import ImageMenuItem
3963+from entertainerlib.gui.widgets.texture import Texture
3964 from entertainerlib.tests import EntertainerTest
3965
3966 THIS_DIR = os.path.dirname(__file__)
3967
3968 class ImageMenuItemTest(EntertainerTest):
3969- """Test for entertainerlib.frontend.gui.widgets.image_menu_item"""
3970+ """Test for entertainerlib.gui.widgets.image_menu_item"""
3971
3972 def setUp(self):
3973 """Set up the test"""
3974
3975=== modified file 'entertainerlib/tests/test_label.py'
3976--- entertainerlib/tests/test_label.py 2009-02-06 08:33:50 +0000
3977+++ entertainerlib/tests/test_label.py 2009-05-05 03:31:34 +0000
3978@@ -7,12 +7,12 @@
3979
3980 import clutter
3981
3982-from entertainerlib.frontend.gui.widgets.base import Base
3983-from entertainerlib.frontend.gui.widgets.label import Label
3984+from entertainerlib.gui.widgets.base import Base
3985+from entertainerlib.gui.widgets.label import Label
3986 from entertainerlib.tests import EntertainerTest
3987
3988 class LabelTest(EntertainerTest):
3989- """Test for entertainerlib.frontend.gui.widgets.label"""
3990+ """Test for entertainerlib.gui.widgets.label"""
3991
3992 def setUp(self):
3993 """Set up the test"""
3994
3995=== modified file 'entertainerlib/tests/test_logger.py'
3996--- entertainerlib/tests/test_logger.py 2009-01-31 21:36:56 +0000
3997+++ entertainerlib/tests/test_logger.py 2009-05-05 04:24:32 +0000
3998@@ -7,7 +7,7 @@
3999 import logging
4000
4001 from entertainerlib.tests import EntertainerTest
4002-from entertainerlib.utils.logger import Logger
4003+from entertainerlib.logger import Logger
4004
4005 class TestLogger(EntertainerTest):
4006 '''Logger test case'''
4007
4008=== modified file 'entertainerlib/tests/test_lyricsdownloader.py'
4009--- entertainerlib/tests/test_lyricsdownloader.py 2009-02-06 08:33:50 +0000
4010+++ entertainerlib/tests/test_lyricsdownloader.py 2009-05-06 04:46:51 +0000
4011@@ -9,7 +9,7 @@
4012 import os
4013 import urllib
4014
4015-from entertainerlib.utils.lyrics_downloader import LyricsDownloader
4016+from entertainerlib.download import LyricsDownloader
4017
4018 THIS_DIR = os.path.dirname(__file__)
4019
4020
4021=== modified file 'entertainerlib/tests/test_models.py'
4022--- entertainerlib/tests/test_models.py 2009-01-31 21:36:56 +0000
4023+++ entertainerlib/tests/test_models.py 2009-05-05 03:35:28 +0000
4024@@ -6,7 +6,7 @@
4025
4026 from storm.locals import Store
4027
4028-from entertainerlib.backend.core.db import models
4029+from entertainerlib.db import models
4030 from entertainerlib.tests import EntertainerTestWithDatabase
4031
4032
4033
4034=== modified file 'entertainerlib/tests/test_music.py'
4035--- entertainerlib/tests/test_music.py 2009-02-14 15:26:32 +0000
4036+++ entertainerlib/tests/test_music.py 2009-04-30 01:11:22 +0000
4037@@ -8,11 +8,11 @@
4038
4039 from pysqlite2 import dbapi2 as sqlite
4040
4041-from entertainerlib.frontend.medialibrary.music import (
4042+from entertainerlib.client.medialibrary.music import (
4043 Album, AlbumHasNoTracks, MusicLibrary, Track, CompactDisc,
4044 TrackRatingOutOfRange, TrackTypeError)
4045 from entertainerlib.tests.mock import MockBackendConnection
4046-from entertainerlib.frontend.medialibrary.playable import Playable
4047+from entertainerlib.client.medialibrary.playable import Playable
4048 from entertainerlib.tests import EntertainerTest
4049
4050 class TestMusic(EntertainerTest):
4051
4052=== modified file 'entertainerlib/tests/test_opmlparser.py'
4053--- entertainerlib/tests/test_opmlparser.py 2009-02-08 07:01:25 +0000
4054+++ entertainerlib/tests/test_opmlparser.py 2009-05-06 04:00:53 +0000
4055@@ -9,7 +9,7 @@
4056 import os
4057 import xml.parsers.expat
4058
4059-from entertainerlib.utils.feed_utils import OPMLParser
4060+from entertainerlib.backend.components.feeds.feed_utils import OPMLParser
4061
4062 THIS_DIR = os.path.dirname(__file__)
4063
4064
4065=== modified file 'entertainerlib/tests/test_screen.py'
4066--- entertainerlib/tests/test_screen.py 2009-04-27 23:12:40 +0000
4067+++ entertainerlib/tests/test_screen.py 2009-05-05 03:31:34 +0000
4068@@ -7,15 +7,15 @@
4069 import clutter
4070
4071 from entertainerlib.exceptions import ScreenException
4072-from entertainerlib.frontend.gui.screens.screen import Screen
4073-from entertainerlib.frontend.gui.tabs.tab import Tab
4074-from entertainerlib.frontend.gui.user_event import UserEvent
4075-from entertainerlib.frontend.gui.widgets.base import Base
4076-from entertainerlib.frontend.gui.widgets.tab_group import TabGroup
4077+from entertainerlib.gui.screens.screen import Screen
4078+from entertainerlib.gui.tabs.tab import Tab
4079+from entertainerlib.gui.user_event import UserEvent
4080+from entertainerlib.gui.widgets.base import Base
4081+from entertainerlib.gui.widgets.tab_group import TabGroup
4082 from entertainerlib.tests import EntertainerTest
4083
4084 class ScreenTest(EntertainerTest):
4085- '''Test for entertainerlib.frontend.gui.screens.screen'''
4086+ """Test for entertainerlib.gui.screens.screen"""
4087
4088 def setUp(self):
4089 '''Set up the test.'''
4090
4091=== modified file 'entertainerlib/tests/test_screenfactory.py'
4092--- entertainerlib/tests/test_screenfactory.py 2009-02-06 08:33:50 +0000
4093+++ entertainerlib/tests/test_screenfactory.py 2009-05-05 03:31:34 +0000
4094@@ -7,26 +7,26 @@
4095
4096 import os
4097
4098-from entertainerlib.frontend.gui.screens.album import Album
4099-from entertainerlib.frontend.gui.screens.artist import Artist
4100-from entertainerlib.frontend.gui.screens.audio_play import AudioPlay
4101-from entertainerlib.frontend.gui.screens.disc import Disc
4102-from entertainerlib.frontend.gui.screens.factory import ScreenFactory
4103-from entertainerlib.frontend.gui.screens.feed import Feed
4104-from entertainerlib.frontend.gui.screens.feed_entry import FeedEntry
4105-from entertainerlib.frontend.gui.screens.main import Main
4106-from entertainerlib.frontend.gui.screens.movie import Movie
4107-from entertainerlib.frontend.gui.screens.music import Music
4108-from entertainerlib.frontend.gui.screens.photo import Photo
4109-from entertainerlib.frontend.gui.screens.photo_albums import PhotoAlbums
4110-from entertainerlib.frontend.gui.screens.photographs import Photographs
4111-from entertainerlib.frontend.gui.screens.question import Question
4112-from entertainerlib.frontend.gui.screens.rss import Rss
4113-from entertainerlib.frontend.gui.screens.tv_episodes import TvEpisodes
4114-from entertainerlib.frontend.gui.screens.tv_series import TvSeries
4115-from entertainerlib.frontend.gui.screens.video_osd import VideoOSD
4116-from entertainerlib.frontend.gui.screens.video import Video
4117-from entertainerlib.frontend.gui.screens.weather import WeatherScreen
4118+from entertainerlib.gui.screens.album import Album
4119+from entertainerlib.gui.screens.artist import Artist
4120+from entertainerlib.gui.screens.audio_play import AudioPlay
4121+from entertainerlib.gui.screens.disc import Disc
4122+from entertainerlib.gui.screens.factory import ScreenFactory
4123+from entertainerlib.gui.screens.feed import Feed
4124+from entertainerlib.gui.screens.feed_entry import FeedEntry
4125+from entertainerlib.gui.screens.main import Main
4126+from entertainerlib.gui.screens.movie import Movie
4127+from entertainerlib.gui.screens.music import Music
4128+from entertainerlib.gui.screens.photo import Photo
4129+from entertainerlib.gui.screens.photo_albums import PhotoAlbums
4130+from entertainerlib.gui.screens.photographs import Photographs
4131+from entertainerlib.gui.screens.question import Question
4132+from entertainerlib.gui.screens.rss import Rss
4133+from entertainerlib.gui.screens.tv_episodes import TvEpisodes
4134+from entertainerlib.gui.screens.tv_series import TvSeries
4135+from entertainerlib.gui.screens.video_osd import VideoOSD
4136+from entertainerlib.gui.screens.video import Video
4137+from entertainerlib.gui.screens.weather import WeatherScreen
4138 from entertainerlib.tests import EntertainerTest
4139 from entertainerlib.tests.mock import MockAlbum
4140 from entertainerlib.tests.mock import MockEntry
4141@@ -43,7 +43,7 @@
4142
4143
4144 class ScreenFactoryTest(EntertainerTest):
4145- '''Test for entertainerlib.frontend.gui.screens.factory'''
4146+ '''Test for entertainerlib.gui.screens.factory'''
4147
4148 def setUp(self):
4149 EntertainerTest.setUp(self)
4150
4151=== modified file 'entertainerlib/tests/test_scrollarea.py'
4152--- entertainerlib/tests/test_scrollarea.py 2009-02-06 08:13:48 +0000
4153+++ entertainerlib/tests/test_scrollarea.py 2009-05-05 03:31:34 +0000
4154@@ -6,13 +6,13 @@
4155
4156 import clutter
4157
4158-from entertainerlib.frontend.gui.widgets.base import Base
4159-from entertainerlib.frontend.gui.widgets.label import Label
4160-from entertainerlib.frontend.gui.widgets.scroll_area import ScrollArea
4161+from entertainerlib.gui.widgets.base import Base
4162+from entertainerlib.gui.widgets.label import Label
4163+from entertainerlib.gui.widgets.scroll_area import ScrollArea
4164 from entertainerlib.tests import EntertainerTest
4165
4166 class ScrollAreaTest(EntertainerTest):
4167- """Test for entertainerlib.frontend.gui.widgets.scroll_area"""
4168+ """Test for entertainerlib.gui.widgets.scroll_area"""
4169
4170 def setUp(self):
4171 """Set up the test"""
4172
4173=== modified file 'entertainerlib/tests/test_texture.py'
4174--- entertainerlib/tests/test_texture.py 2009-02-06 08:33:50 +0000
4175+++ entertainerlib/tests/test_texture.py 2009-05-05 03:31:34 +0000
4176@@ -9,14 +9,14 @@
4177
4178 import clutter
4179
4180-from entertainerlib.frontend.gui.widgets.base import Base
4181-from entertainerlib.frontend.gui.widgets.texture import Texture
4182+from entertainerlib.gui.widgets.base import Base
4183+from entertainerlib.gui.widgets.texture import Texture
4184 from entertainerlib.tests import EntertainerTest
4185
4186 THIS_DIR = os.path.dirname(__file__)
4187
4188 class TextureTest(EntertainerTest):
4189- """Test for entertainerlib.frontend.gui.widgets.texture"""
4190+ """Test for entertainerlib.gui.widgets.texture"""
4191
4192 def setUp(self):
4193 """Set up the test"""
4194
4195=== modified file 'entertainerlib/tests/test_theme.py'
4196--- entertainerlib/tests/test_theme.py 2009-01-31 21:36:56 +0000
4197+++ entertainerlib/tests/test_theme.py 2009-05-05 04:17:21 +0000
4198@@ -5,10 +5,10 @@
4199 __author__ = "Matt Layman <laymansterms.dev@gmail.com>"
4200
4201 from entertainerlib.tests import EntertainerTest
4202-from entertainerlib.utils.theme import Theme
4203+from entertainerlib.gui.theme import Theme
4204
4205 class ThemeTest(EntertainerTest):
4206- """Test for entertainerlib.utils.theme"""
4207+ """Test for entertainerlib.gui.theme"""
4208
4209 def setUp(self):
4210 """Set up the test"""
4211
4212=== modified file 'entertainerlib/tests/test_transitionfactory.py'
4213--- entertainerlib/tests/test_transitionfactory.py 2009-02-07 17:12:21 +0000
4214+++ entertainerlib/tests/test_transitionfactory.py 2009-05-05 03:31:34 +0000
4215@@ -5,15 +5,15 @@
4216 __copyright__ = '2009, Matt Layman'
4217 __author__ = 'Matt Layman <laymansterms.dev@gmail.com>'
4218
4219-from entertainerlib.frontend.gui.transitions.factory import TransitionFactory
4220-from entertainerlib.frontend.gui.transitions.fade import Fade
4221-from entertainerlib.frontend.gui.transitions.no_effect import NoEffect
4222-from entertainerlib.frontend.gui.transitions.slide import Slide
4223-from entertainerlib.frontend.gui.transitions.zoom_and_fade import ZoomAndFade
4224+from entertainerlib.gui.transitions.factory import TransitionFactory
4225+from entertainerlib.gui.transitions.fade import Fade
4226+from entertainerlib.gui.transitions.no_effect import NoEffect
4227+from entertainerlib.gui.transitions.slide import Slide
4228+from entertainerlib.gui.transitions.zoom_and_fade import ZoomAndFade
4229 from entertainerlib.tests import EntertainerTest
4230
4231 class TransitionFactoryTest(EntertainerTest):
4232- '''Test for entertainerlib.frontend.gui.transitions.factory'''
4233+ '''Test for entertainerlib.gui.transitions.factory'''
4234
4235 def setUp(self):
4236 '''Set up the test'''
4237
4238=== modified file 'entertainerlib/tests/test_userinterface.py'
4239--- entertainerlib/tests/test_userinterface.py 2009-04-27 23:12:40 +0000
4240+++ entertainerlib/tests/test_userinterface.py 2009-05-05 03:31:34 +0000
4241@@ -6,14 +6,14 @@
4242
4243 import clutter
4244
4245-from entertainerlib.frontend.gui.screens.question import Question
4246-from entertainerlib.frontend.gui.user_event import UserEvent
4247-from entertainerlib.frontend.gui.user_interface import UserInterface
4248+from entertainerlib.gui.screens.question import Question
4249+from entertainerlib.gui.user_event import UserEvent
4250+from entertainerlib.gui.user_interface import UserInterface
4251 from entertainerlib.tests import EntertainerTest
4252 from entertainerlib.tests.mock import MockClutterKeyboardEvent
4253
4254 class UserInterfaceTest(EntertainerTest):
4255- '''Test for entertainerlib.frontend.gui.user_interface'''
4256+ '''Test for entertainerlib.gui.user_interface'''
4257
4258 def setUp(self):
4259 EntertainerTest.setUp(self)
4260
4261=== modified file 'entertainerlib/tests/test_weather.py'
4262--- entertainerlib/tests/test_weather.py 2009-02-10 00:59:03 +0000
4263+++ entertainerlib/tests/test_weather.py 2009-05-06 05:10:14 +0000
4264@@ -7,7 +7,7 @@
4265 from datetime import datetime
4266
4267 from entertainerlib.tests import EntertainerTest
4268-from entertainerlib.utils.weather import Weather
4269+from entertainerlib.weather import Weather
4270
4271 class WeatherTest(EntertainerTest):
4272 """
4273
4274=== modified file 'entertainerlib/thumbnailer.py'
4275--- entertainerlib/thumbnailer.py 2009-02-19 14:25:51 +0000
4276+++ entertainerlib/thumbnailer.py 2009-05-05 04:11:48 +0000
4277@@ -17,7 +17,7 @@
4278
4279 from entertainerlib.exceptions import (ImageThumbnailerException,
4280 ThumbnailerException, VideoThumbnailerException)
4281-from entertainerlib.utils.configuration import Configuration
4282+from entertainerlib.configuration import Configuration
4283
4284
4285 class Thumbnailer(object):
4286
4287=== removed directory 'entertainerlib/utils'
4288=== removed file 'entertainerlib/utils/__init__.py'
4289--- entertainerlib/utils/__init__.py 2008-08-07 01:17:29 +0000
4290+++ entertainerlib/utils/__init__.py 1970-01-01 00:00:00 +0000
4291@@ -1,1 +0,0 @@
4292-'''Entertainer utils'''
4293
4294=== removed file 'entertainerlib/utils/albumart_downloader.py'
4295--- entertainerlib/utils/albumart_downloader.py 2009-03-22 04:53:10 +0000
4296+++ entertainerlib/utils/albumart_downloader.py 1970-01-01 00:00:00 +0000
4297@@ -1,353 +0,0 @@
4298-'''AlbumArtDownloader - Search and download album art from the internet.'''
4299-
4300-__licence__ = "GPLv2"
4301-__copyright__ = "2007, Lauri Taimila, "
4302-__copyright__ += "2008, Matt Layman"
4303-__author__ = "Lauri Taimila <lauri@taimila.com>, "
4304-__author__ += "Matt Layman <laymansterms.dev@gmail.com"
4305-
4306-import os
4307-import re
4308-import locale
4309-import urllib
4310-import threading
4311-from xml.dom import minidom
4312-
4313-# Amazon licence for Entertainer
4314-LICENSE_KEY = "1YCWYZ0SPPAJ27YZZ482"
4315-DEFAULT_LOCALE = "en_US"
4316-ASSOCIATE = "webservices-20"
4317-
4318-# We are not allowed to batch more than 2 requests at once
4319-# http://docs.amazonwebservices.com/AWSEcommerceService/4-0/
4320-# PgCombiningOperations.html
4321-MAX_BATCH_JOBS = 2
4322-
4323-class Bag:
4324- '''Just a helper'''
4325-
4326-
4327-class AlbumArtDownloader(threading.Thread):
4328- """
4329- Search and download album art from the internet.
4330-
4331- This class is heavily based on Rhythmbox - AlbumArt plugin's class
4332- 'AmazonCoverArtSearch'. That plugin is released under GPLv2 (or higher)
4333- and it's copyrights belong to Gareth Murphy and Martin Szulecki.
4334-
4335- See more: http://www.gnome.org/projects/rhythmbox/
4336-
4337- If you want better cover search, please contribute to Rhyhtmbox project.
4338- """
4339-
4340- def __init__(self, album, artist, art_file_path, callback = None):
4341- """
4342- Initialize album art downloader
4343- @param album: Album title
4344- @param artist: Artist name
4345- @param art_file_path: Path where albumart is saved
4346- @param callback: Callback function that is called after search if set
4347- """
4348- threading.Thread.__init__(self)
4349- self.setName("AlbumArt Downloader")
4350- self.callback_function = callback # Callback function
4351- self.album = album # Album title
4352- self.artist = artist # Artist name
4353- # Album art files are in this directory
4354- self.path = art_file_path
4355- (self.tld, self.encoding) = self.__get_locale ()
4356-
4357- def run(self):
4358- """Start searching and downloading albumart."""
4359- self.search()
4360-
4361- def __get_locale (self):
4362- '''Get locale information from user\'s machine'''
4363- # "JP is the only locale that correctly takes UTF8 input.
4364- # All other locales use LATIN1."
4365- # http://developer.amazonwebservices.com/connect/
4366- # entry.jspa?externalID=1295&categoryID=117
4367- supported_locales = {
4368- "en_US" : ("com", "latin1"),
4369- "en_GB" : ("co.uk", "latin1"),
4370- "de" : ("de", "latin1"),
4371- "ja" : ("jp", "utf8")
4372- }
4373-
4374- lc_id = DEFAULT_LOCALE
4375- default = locale.getdefaultlocale ()[0]
4376- if default:
4377- if supported_locales.has_key (default):
4378- lc_id = default
4379- else:
4380- lang = default.split("_")[0]
4381- if supported_locales.has_key (lang):
4382- lc_id = lang
4383-
4384- return supported_locales[lc_id]
4385-
4386- def __valid_match (self, item):
4387- '''Determine if item matches tag criteria'''
4388- return (hasattr (item, "LargeImage") or hasattr (item, "MediumImage")) \
4389- and hasattr (item, "ItemAttributes")
4390-
4391- def __tidy_up_string (self, str_input):
4392- """
4393- Tidy up string. Remove spaces, convert to lowercase and replace chars.
4394- """
4395- # Lowercase
4396- str_input = str_input.lower ()
4397- # Strip
4398- str_input = str_input.strip ()
4399-
4400- # TODO: Convert accented to unaccented
4401- str_input = str_input.replace (" - ", " ")
4402- str_input = str_input.replace (": ", " ")
4403- str_input = str_input.replace (" & ", " and ")
4404-
4405- return str_input
4406-
4407- def search(self):
4408- """Search album art from Amazon"""
4409- self.searching = True
4410- self.cancel = False
4411- self.keywords = []
4412-
4413- st_artist = self.artist or u'Unknown'
4414- st_album = self.album or u'Unknown'
4415-
4416- if st_artist == st_album == u'Unknown':
4417- self.on_search_completed (None)
4418- return
4419-
4420- # Tidy up
4421-
4422- # Replace quote characters
4423- # don't replace single quote: could be important punctuation
4424- for char in ["\""]:
4425- st_artist = st_artist.replace (char, '')
4426- st_album = st_album.replace (char, '')
4427-
4428-
4429- self.st_album = st_album
4430- self.st_artist = st_artist
4431-
4432- # Remove variants of Disc/CD [1-9] from album title before search
4433- for exp in ["\([Dd]isc *[1-9]+\)", "\([Cc][Dd] *[1-9]+\)"]:
4434- p = re.compile (exp)
4435- st_album = p.sub ('', st_album)
4436-
4437- st_album_no_vol = st_album
4438- for exp in ["\(*[Vv]ol.*[1-9]+\)*"]:
4439- p = re.compile (exp)
4440- st_album_no_vol = p.sub ('', st_album_no_vol)
4441-
4442- self.st_album_no_vol = st_album_no_vol
4443-
4444- # Save current search's entry properties
4445- self.search_album = st_album
4446- self.search_artist = st_artist
4447- self.search_album_no_vol = st_album_no_vol
4448-
4449- # TODO: Improve to decrease wrong cover downloads, maybe add severity?
4450- # Assemble list of search keywords (and thus search queries)
4451- if st_album == u'Unknown':
4452- self.keywords.append ("%s Best of" % (st_artist))
4453- self.keywords.append ("%s Greatest Hits" % (st_artist))
4454- self.keywords.append ("%s Essential" % (st_artist))
4455- self.keywords.append ("%s Collection" % (st_artist))
4456- self.keywords.append ("%s" % (st_artist))
4457- elif st_artist == u'Unknown':
4458- self.keywords.append ("%s" % (st_album))
4459- if st_album_no_vol != st_artist:
4460- self.keywords.append ("%s" % (st_album_no_vol))
4461- self.keywords.append ("Various %s" % (st_album))
4462- else:
4463- if st_album != st_artist:
4464- self.keywords.append ("%s %s" % (st_artist, st_album))
4465- if st_album_no_vol != st_album:
4466- self.keywords.append ("%s %s" %
4467- (st_artist, st_album_no_vol))
4468- self.keywords.append ("Various %s" % (st_album))
4469- self.keywords.append ("%s" % (st_artist))
4470-
4471- # Initiate asynchronous search
4472- self.search_next ()
4473-
4474- def search_next(self):
4475- """Search again, because the last one didn't find any covers."""
4476- if len (self.keywords) == 0:
4477- # No keywords left to search -> no results
4478- self.on_search_completed (None)
4479- return False
4480-
4481- self.searching = True
4482-
4483- url = "http://ecs.amazonaws." + self.tld + "/onca/xml" \
4484- "?Service=AWSECommerceService" \
4485- "&AWSAccessKeyId=" + LICENSE_KEY + \
4486- "&AssociateTag=" + ASSOCIATE + \
4487- "&ResponseGroup=Images,ItemAttributes" \
4488- "&Operation=ItemSearch" \
4489- "&ItemSearch.Shared.SearchIndex=Music"
4490-
4491- job = 1
4492- while job <= MAX_BATCH_JOBS and len (self.keywords) > 0:
4493- keyword = self.keywords.pop (0)
4494- keyword = keyword.encode (self.encoding, "ignore")
4495- keyword = keyword.strip ()
4496- keyword = urllib.quote (keyword)
4497- url += "&ItemSearch.%d.Keywords=%s" % (job, keyword)
4498- job += 1
4499-
4500- # Retrieve search for keyword
4501- temp = urllib.urlopen(url)
4502- search_results = temp.read()
4503- self.on_search_response(search_results)
4504- return True
4505-
4506- def __unmarshal(self, element):
4507- rc = Bag()
4508- child_elements = [e for e in element.childNodes if isinstance (e,
4509- minidom.Element)]
4510- if child_elements:
4511- for child in child_elements:
4512- key = child.tagName
4513- if hasattr (rc, key):
4514- if not isinstance (getattr (rc, key), list):
4515- setattr (rc, key, [getattr (rc, key)])
4516- getattr (rc, key).append (self.__unmarshal (child))
4517- # get_best_match_urls() wants a list, even if there is only
4518- # one item/artist
4519- elif child.tagName in ("Items", "Item", "Artist"):
4520- setattr (rc, key, [self.__unmarshal(child)])
4521- else:
4522- setattr (rc, key, self.__unmarshal(child))
4523- else:
4524- rc = "".join ([e.data for e in element.childNodes if isinstance (e,
4525- minidom.Text)])
4526- return rc
4527-
4528- def on_search_response (self, result_data):
4529- '''Check search results
4530-
4531- If results are not good, we search again with the next keyword.
4532- '''
4533- if result_data is None:
4534- self.search_next()
4535- return
4536-
4537- try:
4538- xmldoc = minidom.parseString(result_data)
4539- except (TypeError, AttributeError):
4540- self.search_next()
4541- return
4542-
4543- data = self.__unmarshal (xmldoc)
4544- if not hasattr (data, "ItemSearchResponse") or \
4545- not hasattr (data.ItemSearchResponse, "Items"):
4546- # Something went wrong ...
4547- self.search_next ()
4548- else:
4549- # We got some search results
4550- self.on_search_results (data.ItemSearchResponse.Items)
4551-
4552- def on_search_results (self, results):
4553- '''Results were found, now we need to take action.'''
4554- self.on_search_completed (results)
4555-
4556- def on_search_completed (self, result):
4557- """
4558- Search completed and results found.
4559-
4560- Download large album art image from the first result and save it to
4561- the disk. This function diverges greatly from the rhythmbox
4562- implementation in order to avoid their loader and CoverArtDatabase
4563- """
4564- self.searching = False
4565- image_urls = self.get_best_match_urls(result)
4566- if len(image_urls) == 0:
4567- return
4568- image_url = image_urls[0]
4569- image_file = urllib.urlopen(image_url)
4570- # base64 encode artist and album so there can be a '/' in the artist
4571- # or album
4572- artist_album = self.artist + " - " + self.album
4573- artist_album = artist_album.encode("base64")
4574-
4575- dest = open(os.path.join(self.path, artist_album + ".jpg"),'w')
4576- dest.write(image_file.read())
4577- dest.close()
4578-
4579- if self.callback_function is not None:
4580- self.callback_function(self.artist, self.album)
4581-
4582- def get_best_match_urls (self, search_results):
4583- """Return tuple of URL's to large and medium cover of the best match"""
4584- # Default to "no match", our results must match our criteria
4585-
4586- # This code comes from Rhythmbox so we can't control the use of 'filter'
4587- # pylint: disable-msg=W0141
4588-
4589- if not search_results:
4590- return []
4591-
4592- best_match = None
4593-
4594- for result in search_results:
4595- if not hasattr (result, "Item"):
4596- # Search was unsuccessful, try next batch job
4597- continue
4598-
4599- items = filter(self.__valid_match, result.Item)
4600- if self.search_album != u'Unknown':
4601- album_check = self.__tidy_up_string (self.search_album)
4602- for item in items:
4603- if not hasattr (item.ItemAttributes, "Title"):
4604- continue
4605-
4606- album = self.__tidy_up_string (item.ItemAttributes.Title)
4607- if album == album_check:
4608- # Found exact album, can not get better than that
4609- best_match = item
4610- break
4611- # If we already found a best_match, just keep checking for
4612- # exact one. Check the results for both an album name that
4613- # contains the name we're searching for, and an album name
4614- # that's a substring of the name we're searching for
4615- elif (best_match is None) and \
4616- (album.find (album_check) != -1 or
4617- album_check.find (album) != -1):
4618- best_match = item
4619-
4620- # If we still have no definite hit, use first result where artist
4621- # matches
4622- if (self.search_album == u'Unknown' and \
4623- self.search_artist != u'Unknown'):
4624- artist_check = self.__tidy_up_string (self.search_artist)
4625- if best_match is None:
4626- # Check if artist appears in the Artists list
4627- hit = False
4628- for item in items:
4629- if not hasattr (item.ItemAttributes, "Artist"):
4630- continue
4631-
4632- for artist in item.ItemAttributes.Artist:
4633- artist = self.__tidy_up_string (artist)
4634- if artist.find (artist_check) != -1:
4635- best_match = item
4636- hit = True
4637- break
4638- if hit:
4639- break
4640-
4641- urls = [getattr (best_match, size).URL for size in ("LargeImage",
4642- "MediumImage")
4643- if hasattr (best_match, size)]
4644- if urls:
4645- return urls
4646-
4647- # No search was successful
4648- return []
4649-
4650-
4651
4652=== removed file 'entertainerlib/utils/cd_utils.py'
4653--- entertainerlib/utils/cd_utils.py 2008-06-16 04:10:47 +0000
4654+++ entertainerlib/utils/cd_utils.py 1970-01-01 00:00:00 +0000
4655@@ -1,11 +0,0 @@
4656-'''Utilities for handling the cd'''
4657-# XXX: rockstar - Why do we have module for a single function?
4658-
4659-__licence__ = "GPLv2"
4660-__copyright__ = "2008, Joshua Scotton"
4661-__author__ = "Joshua Scotton <josh@joshuascotton.com>"
4662-
4663-import os
4664-
4665-def eject_cd():
4666- os.system("eject")
4667
4668=== removed file 'entertainerlib/utils/content_management_dialog.py'
4669--- entertainerlib/utils/content_management_dialog.py 2009-04-17 21:35:32 +0000
4670+++ entertainerlib/utils/content_management_dialog.py 1970-01-01 00:00:00 +0000
4671@@ -1,774 +0,0 @@
4672-#!/usr/bin/env python
4673-'''Content management GUI for Entertainer'''
4674-
4675-__licence__ = "GPLv2"
4676-__copyright__ = "2008, Lauri Taimila and Joshua Scotton"
4677-__author__ = ("Lauri Taimila <lauri@taimila.com>",
4678- "Joshua Scotton <josh@joshuascotton.com>")
4679-
4680-import os
4681-import socket
4682-
4683-import gtk
4684-import gtk.glade
4685-
4686-from entertainerlib.utils.weather import Weather
4687-from entertainerlib.utils.configuration import Configuration
4688-from entertainerlib.utils.open_feed_source_dialog import OpenFeedSourceDialog
4689-
4690-from entertainerlib.backend.core.message import Message
4691-from entertainerlib.backend.core.message_bus_proxy import MessageBusProxy
4692-from entertainerlib.backend.core.message_type_priority import MessageType
4693-
4694-class ContentManagementDialog:
4695- """
4696- This is a content management tool for Entertainer media center application.
4697- """
4698-
4699- # Temporary storage for entered URL
4700- url = ""
4701- GLADE_DIR = os.path.join(os.path.dirname(__file__), "glade")
4702-
4703- def __init__(self, stand_alone):
4704- """
4705- Initialize content management dialog
4706- @param stand_alone: Boolean, Is this dialog running as a stand alone
4707- process
4708- """
4709- self.stand_alone = stand_alone
4710- self.config = Configuration()
4711- self.weather = Weather()
4712-
4713- # Load glade UI
4714- self.gladefile = os.path.join(self.GLADE_DIR,
4715- "entertainer-content-management.glade")
4716- self.widgets = gtk.glade.XML(self.gladefile)
4717-
4718- # Get content management dialog and bind signal callbacks
4719- self.dialog = self.widgets.get_widget("ContentManagementDialog")
4720- if (self.dialog):
4721- callback_dic = {
4722- "on_button_open_list_clicked" :
4723- self.on_button_open_list_clicked,
4724- "on_close_button_clicked" : self.on_close_button_clicked,
4725- "on_button_remove_videos_clicked" :
4726- self.on_button_remove_videos_clicked,
4727- "on_button_add_videos_clicked" :
4728- self.on_button_add_videos_clicked,
4729- "on_button_edit_videos_clicked" :
4730- self.on_button_edit_videos_clicked,
4731- "on_checkbutton_video_metadata_toggled" :
4732- self.on_checkbutton_video_metadata_toggled,
4733- "on_button_add_music_clicked" :
4734- self.on_button_add_music_clicked,
4735- "on_button_remove_music_clicked" :
4736- self.on_button_remove_music_clicked,
4737- "on_button_edit_music_clicked" :
4738- self.on_button_edit_music_clicked,
4739- "on_lyrics_checkbox_toggled" : self.on_lyrics_checkbox_toggled,
4740- "on_art_checkbox_toggled" : self.on_art_checkbox_toggled,
4741- "on_button_add_images_clicked" :
4742- self.on_button_add_images_clicked,
4743- "on_button_remove_images_clicked" :
4744- self.on_button_remove_images_clicked,
4745- "on_button_edit_images_clicked" :
4746- self.on_button_edit_images_clicked,
4747- "on_button_add_feed_clicked" :
4748- self.on_button_add_feed_clicked,
4749- "on_button_remove_feed_clicked" :
4750- self.on_button_remove_feed_clicked,
4751- "on_button_edit_feed_clicked" :
4752- self.on_button_edit_feed_clicked,
4753- "on_fetch_interval_spinbutton_value_changed" :
4754- self.on_fetch_interval_spinbutton_value_changed,
4755- "on_ContentManagementDialog_destroy" : self.on_dialog_closed,
4756- "on_url_dialog_delete_event" : self.on_url_dialog_delete_event,
4757- "on_url_dialog_ok_button_clicked" :
4758- self.on_url_dialog_ok_button_clicked,
4759- "on_url_dialog_cancel_button_clicked" :
4760- self.on_url_dialog_cancel_button_clicked,
4761- "on_button_video_rebuild_clicked" :
4762- self.on_button_video_rebuild_clicked,
4763- "on_button_music_rebuild_clicked" :
4764- self.on_button_music_rebuild_clicked,
4765- "on_button_image_rebuild_clicked" :
4766- self.on_button_image_rebuild_clicked,
4767- "on_button_feed_rebuild_clicked" :
4768- self.on_button_feed_rebuild_clicked,
4769- "on_button_add_weather_clicked" :
4770- self.on_button_add_weather_clicked,
4771- "on_button_remove_weather_clicked" :
4772- self.on_button_remove_weather_clicked,
4773- "on_weather_display_checkbox_toggled" :
4774- self.on_weather_display_checkbox_toggled,
4775- "on_location_find_button_clicked" :
4776- self.on_location_find_button_clicked,
4777- "on_location_cancel_button_clicked" :
4778- self.on_location_cancel_button_clicked,
4779- "on_location_add_button_clicked" :
4780- self.on_location_add_button_clicked,
4781- "on_location_entry_activate" : self.on_location_entry_activate}
4782- self.widgets.signal_autoconnect(callback_dic)
4783-
4784- # Initialize dialog widgets with correct values and show dialog
4785- self.init_dialog_values_from_configure_file()
4786- self.dialog.resize(500, 300)
4787- self.dialog.show()
4788-
4789- # Initialize location list in search dialog
4790- result_list = self.widgets.get_widget("location_results_treeview")
4791- store = gtk.ListStore(str)
4792- result_list.set_model(store)
4793- cell_renderer = gtk.CellRendererText()
4794- column = gtk.TreeViewColumn(_("Location"), cell_renderer, text=0)
4795- result_list.append_column(column)
4796-
4797-# Signal handlers
4798-
4799- def on_dialog_closed(self, widget):
4800- """Callback function for dialog's close button"""
4801- try:
4802- proxy = MessageBusProxy(client_name = "Content Management GUI")
4803- proxy.connectToMessageBus()
4804- proxy.sendMessage(Message(MessageType.CONTENT_CONF_UPDATED))
4805- proxy.disconnectFromMessageBus()
4806- except socket.error:
4807- error = gtk.MessageDialog(
4808- None, gtk.DIALOG_MODAL,
4809- gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _(
4810- "Entertainer backend is not running. "
4811- "Cache cannot be rebuilt."
4812- ))
4813- error.run()
4814- error.destroy()
4815-
4816-
4817- if(self.stand_alone):
4818- self.dialog.hide()
4819- self.dialog.destroy()
4820- gtk.main_quit()
4821- else:
4822- self.dialog.hide()
4823- self.dialog.destroy()
4824-
4825- def on_close_button_clicked(self, widget):
4826- """Callback function for dialog's close button"""
4827- if(self.stand_alone):
4828- self.dialog.hide()
4829- self.dialog.destroy()
4830- gtk.main_quit()
4831- else:
4832- self.dialog.hide()
4833- self.dialog.destroy()
4834-
4835- def on_button_add_videos_clicked(self, widget):
4836- """Opens add URL dialog. """
4837- widget = self.widgets.get_widget("treeview_videos")
4838- model = widget.get_model()
4839- # Open "Select folder" dialog
4840- dialog = gtk.FileChooserDialog(_("Select video folder"), None,
4841- gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
4842- (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,
4843- gtk.STOCK_OPEN,gtk.RESPONSE_OK),
4844- None)
4845- status = dialog.run()
4846- # If folder was selected we add it to model and update config file
4847- if(status == gtk.RESPONSE_OK):
4848- self.add_to_model_and_config(dialog.get_current_folder(), model,
4849- self.video_folders, "Videos")
4850- dialog.destroy()
4851-
4852- def on_button_remove_videos_clicked(self, widget):
4853- """Remove currently selected folder from video folders"""
4854- widget = self.widgets.get_widget("treeview_videos")
4855- model = widget.get_model()
4856- selection = widget.get_selection().get_selected()
4857- if selection[1] == None:
4858- return
4859- rm_folder = model.get_value(selection[1], 0)
4860- self.video_folders.remove(rm_folder)
4861- str_folders = ";".join(self.video_folders)
4862- self.config.write_content_value("Videos", "folders", str_folders)
4863- model.remove(selection[1])
4864-
4865- def on_button_edit_videos_clicked(self, widget):
4866- """Edit currently selected folder"""
4867- widget = self.widgets.get_widget("treeview_videos")
4868- url_dialog = self.widgets.get_widget("url_dialog")
4869- url_entry = self.widgets.get_widget("url_entry")
4870- model = widget.get_model()
4871- selection = widget.get_selection().get_selected()
4872- if selection[1] == None:
4873- return
4874- folder = model.get_value(selection[1], 0)
4875- url_entry.set_text(folder)
4876- url_dialog.set_title(_("Edit URL"))
4877- status = url_dialog.run()
4878- if status == gtk.RESPONSE_OK and os.path.exists(self.url):
4879- # Update list model
4880- model.set_value(selection[1], 0, self.url)
4881- # Update configure file
4882- pos = self.video_folders.index(folder)
4883- self.video_folders.remove(folder)
4884- self.video_folders.insert(pos, self.url)
4885- str_folders = ";".join(self.video_folders)
4886- self.config.write_content_value("Videos", "folders",
4887- str_folders)
4888-
4889- def on_checkbutton_video_metadata_toggled(self, widget):
4890- """
4891- Download video file metadata from internet
4892- @param widget: GTK-Widget
4893- """
4894- self.config.write_content_value("Videos", "download_metadata",
4895- widget.get_active())
4896-
4897- def on_button_add_music_clicked(self, widget):
4898- """
4899- Opens add URL dialog
4900- @param widget: GTK-Widget
4901- """
4902- widget = self.widgets.get_widget("treeview_music")
4903- model = widget.get_model()
4904- # Open "Select folder" dialog
4905- dialog = gtk.FileChooserDialog(_("Select music folder"), None,
4906- gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
4907- (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
4908- gtk.RESPONSE_OK),
4909- None)
4910- status = dialog.run()
4911- # If folder was selected we add it to model and update config file
4912- if(status == gtk.RESPONSE_OK):
4913- self.add_to_model_and_config(dialog.get_current_folder(), model,
4914- self.music_folders, "Music")
4915- dialog.destroy()
4916-
4917- def on_button_remove_music_clicked(self, widget):
4918- """Remove currently selected folder from music folders"""
4919- widget = self.widgets.get_widget("treeview_music")
4920- model = widget.get_model()
4921- selection = widget.get_selection().get_selected()
4922- if selection[1] == None:
4923- return
4924- rm_folder = model.get_value(selection[1], 0)
4925- self.music_folders.remove(rm_folder)
4926- str_folders = ";".join(self.music_folders)
4927- self.config.write_content_value("Music", "folders", str_folders)
4928- model.remove(selection[1])
4929-
4930- def on_button_edit_music_clicked(self, widget):
4931- """Edit currently selected music folder"""
4932- widget = self.widgets.get_widget("treeview_music")
4933- url_dialog = self.widgets.get_widget("url_dialog")
4934- url_entry = self.widgets.get_widget("url_entry")
4935- model = widget.get_model()
4936- selection = widget.get_selection().get_selected()
4937- if selection[1] == None:
4938- return
4939- folder = model.get_value(selection[1], 0)
4940- url_entry.set_text(folder)
4941- url_dialog.set_title(_("Edit URL"))
4942- status = url_dialog.run()
4943- if status == gtk.RESPONSE_OK and os.path.exists(self.url):
4944- # Update list model
4945- model.set_value(selection[1], 0, self.url)
4946- # Update configure file
4947- pos = self.music_folders.index(folder)
4948- self.music_folders.remove(folder)
4949- self.music_folders.insert(pos, self.url)
4950- str_folders = ";".join(self.music_folders)
4951- self.config.write_content_value("Music", "folders", str_folders)
4952-
4953- def on_button_add_images_clicked(self, widget):
4954- """Opens add URL dialog. """
4955- widget = self.widgets.get_widget("treeview_images")
4956- model = widget.get_model()
4957- # Open "Select folder" dialog
4958- dialog = gtk.FileChooserDialog(_("Select image folder"), None,
4959- gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
4960- (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
4961- gtk.RESPONSE_OK),
4962- None)
4963- status = dialog.run()
4964- # If folder was selected we add it to model and update config file
4965- if(status == gtk.RESPONSE_OK):
4966- self.add_to_model_and_config(dialog.get_current_folder(), model,
4967- self.image_folders, "Images")
4968- dialog.destroy()
4969-
4970- def on_button_remove_images_clicked(self, widget):
4971- """Remove currently selected folder from images folders"""
4972- widget = self.widgets.get_widget("treeview_images")
4973- model = widget.get_model()
4974- selection = widget.get_selection().get_selected()
4975- if selection[1] == None:
4976- return
4977- rm_folder = model.get_value(selection[1], 0)
4978- self.image_folders.remove(rm_folder)
4979- str_folders = ";".join(self.image_folders)
4980- self.config.write_content_value("Images", "folders", str_folders)
4981- model.remove(selection[1])
4982-
4983- def on_button_edit_images_clicked(self, widget):
4984- """Edit currently selected music folder"""
4985- widget = self.widgets.get_widget("treeview_images")
4986- url_dialog = self.widgets.get_widget("url_dialog")
4987- url_entry = self.widgets.get_widget("url_entry")
4988- model = widget.get_model()
4989- selection = widget.get_selection().get_selected()
4990- if selection[1] == None:
4991- return
4992- folder = model.get_value(selection[1], 0)
4993- url_entry.set_text(folder)
4994- url_dialog.set_title(_("Edit URL"))
4995- status = url_dialog.run()
4996- if status == gtk.RESPONSE_OK and os.path.exists(self.url):
4997- # Update list model
4998- model.set_value(selection[1], 0, self.url)
4999- # Update configure file
5000- pos = self.image_folders.index(folder)
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches