Merge lp:~shnatsel/noise/contractor-client-new-api into lp:~elementary-apps/noise/trunk

Proposed by Sergey "Shnatsel" Davidoff
Status: Merged
Approved by: Sergey "Shnatsel" Davidoff
Approved revision: 1555
Merged at revision: 1537
Proposed branch: lp:~shnatsel/noise/contractor-client-new-api
Merge into: lp:~elementary-apps/noise/trunk
Diff against target: 201 lines (+89/-10)
1 file modified
src/Views/ListView/Lists/MusicListView.vala (+89/-10)
To merge this branch: bzr merge lp:~shnatsel/noise/contractor-client-new-api
Reviewer Review Type Date Requested Status
Victor Martinez (community) Approve
Sergey "Shnatsel" Davidoff (community) Approve
Corentin Noël Pending
Review via email: mp+189285@code.launchpad.net

This proposal supersedes a proposal from 2013-10-02.

Commit message

Add Contractor support

Description of the change

Show Contractor actions into Noise's right-click menu, so that you can perform actions on songs without the need to dig up the relevant files in file manager.

This code requires the new convenience functions in Granite's Contractor API (https://code.launchpad.net/~shnatsel/granite/get_contracts_for_files/+merge/188432) and the latest Contractor with proper handling of empty string mimetypes.

Since I check file existence in this code anyway, I'd like to indicate missing files in the UI (like playback attempts do). Is this a good idea and how do I go about it?

To post a comment you must log in.
Revision history for this message
Sergey "Shnatsel" Davidoff (shnatsel) wrote : Posted in a previous version of this proposal

Oh wait now it crashes mysteriously

review: Needs Fixing
Revision history for this message
Sergey "Shnatsel" Davidoff (shnatsel) wrote : Posted in a previous version of this proposal

Looks like this code triggers some bug in Noise's TreeView that was not triggered before.
This code gets null Noise.Media's from get_selected_medias (), and the TreeView is visibly corrupted. The diffs from last working revision (1541) to first broken revisions (1542 and 1544) are innocious and cannot have such side effects as TreeView corruption.

Revision history for this message
Sergey "Shnatsel" Davidoff (shnatsel) wrote : Posted in a previous version of this proposal
Revision history for this message
Victor Martinez (victored) wrote : Posted in a previous version of this proposal

Looks like the actual media objects are being freed after your ContractMenuItems are destroyed (when the menu is closed). This is a common problem whenever you use GLib.List to store your objects, as it doesn't reference-count objects.

I only know two trivial ways to deal with this kind of issues:

1) Use "GLib.List<unowned Noise.Media>" in ContractMenuItem, so that the Media objects are not freed along with the list. "medias.copy()" should not be necessary after that.

2) Use a Gee data structure, as it reference-counts GObjects. I'd recommend using a Gee.ArrayList to store the elements.

This modification should suffice:

public class Noise.ContractMenuItem : Gtk.MenuItem {
    private Granite.Services.Contract contract;
    private Gee.List<Media> medias = new Gee.ArrayList<Media>;

    public ContractMenuItem (Granite.Services.Contract contract, GLib.List<Noise.Media> medias) {
 this.contract = contract;

 foreach (var m in medias)
     this.medias.add (m);

        // ...
    }
[...]

review: Needs Fixing
Revision history for this message
Victor Martinez (victored) wrote : Posted in a previous version of this proposal

BTW, thank you for crediting me twice :D

Revision history for this message
Sergey "Shnatsel" Davidoff (shnatsel) wrote : Posted in a previous version of this proposal

W00T! This is why it didn't work! Thanks a lot! Me and Tom have spent 5 hours debugging it with no luck XD

I wonder if I should use the newly-merged file-oriented API or leave it as it is?
Here's the difference between APIs: http://bazaar.launchpad.net/~shnatsel/noise/contractor-client-new-api/revision/1548/src/Views/ListView/Lists/MusicListView.vala

Revision history for this message
Victor Martinez (victored) wrote : Posted in a previous version of this proposal

No problem.

I'd definitely use the new API. Less code is always better.

Revision history for this message
Sergey "Shnatsel" Davidoff (shnatsel) wrote : Posted in a previous version of this proposal

I decided to stick to the old API for now, it's probably better suited for Noise's situation of not knowing of a file actually exists or not but still keeping its entry around.

I've fixed the double crediting and this is now ready for actual review.

Also it appears I've blown up the diff by regenerating the translation template. Oops! Scroll to the very bottom to see the actual code.

Revision history for this message
Sergey "Shnatsel" Davidoff (shnatsel) : Posted in a previous version of this proposal
review: Abstain
Revision history for this message
Victor Martinez (victored) wrote : Posted in a previous version of this proposal

I fail to see the advantages of duplicating the code from Granite here. I would suggest using the new API method you introduced (in Granite) instead.

Also, I've noticed multiple calls like: "File.new_for_uri (m.uri);". Please note that Noise.Media has a "file" member you can access directly, like "m.file" [1]

Other than that and some coding-style issues, it looks good :)

[1] http://bazaar.launchpad.net/~elementary-apps/noise/trunk/view/head:/core/Media.vala#L59

review: Needs Fixing
Revision history for this message
Sergey "Shnatsel" Davidoff (shnatsel) :
review: Needs Fixing
1553. By Sergey "Shnatsel" Davidoff

when encountering a missing file, indicate that it's missing in the UI

Revision history for this message
Sergey "Shnatsel" Davidoff (shnatsel) wrote :

I'm now completely satisfied with this branch, and all the concerns voiced so far should be addressed.

review: Approve
1554. By Sergey "Shnatsel" Davidoff

fix coding style in lines introduced by Contractor-related work

Revision history for this message
Victor Martinez (victored) wrote :

It's working nicely here.

There are indentation issues and an extra blank line between diff lines 142 - 148.

Also, after updating the "unique_status_image" property for each media object, is there any successive call to "queue_draw" on the view (and outside the foreach loop) so that the change is visually reflected?

review: Needs Fixing
Revision history for this message
Sergey "Shnatsel" Davidoff (shnatsel) wrote :

I can't see anything wrong with indentation on diff lines 142 - 148. If you

There are no queue_draw calls in this code, yet the change *is* visually reflected, instantly. Shall I add one? I'm not sure on which object shall I call it.

Revision history for this message
Sergey "Shnatsel" Davidoff (shnatsel) wrote :

I can't see anything wrong with indentation on diff lines 142 - 148. If you think that something is wrong there, you're welcome to fix it.

There are no queue_draw calls in this code, yet the change *is* visually reflected, instantly. Shall I add one? I'm not sure on which object shall I call it.

Revision history for this message
Victor Martinez (victored) wrote :

Hmmm. Something wrong with the diff tool maybe?

- Diff line 148: blank line. We don't leave this kind of blank space in Vala code.
- Diff lines 144 and 145: Code has been indented using 8 spaces instead of 4.
- Diff line 146: Closing bracket has been indented using 4 spaces. Notice how it does not match the indentation level of "foreach".

Logic wise this is OK. Please add the corrections.

Revision history for this message
Victor Martinez (victored) wrote :

Regarding queue_draw, yeah it's often not needed as gtk+ currently seems to re-draw the view when the menu is closed, or when you hover the cursor over an item in the list.

Anyway, it's better to avoid assuming GTK+ will always do it automatically and queue a re-draw explicitly. This may prevent future issues.

Revision history for this message
Sergey "Shnatsel" Davidoff (shnatsel) wrote :

I've fixed the indentation now. Thanks for spotting the mistakes!

Am I calling the redraws on the right object?

1555. By Sergey "Shnatsel" Davidoff

coding style tweak + a redraw call

Revision history for this message
Victor Martinez (victored) wrote :

Yes, you are.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/Views/ListView/Lists/MusicListView.vala'
2--- src/Views/ListView/Lists/MusicListView.vala 2013-06-13 19:59:30 +0000
3+++ src/Views/ListView/Lists/MusicListView.vala 2013-10-13 20:59:26 +0000
4@@ -14,19 +14,53 @@
5 * statement from your version.
6 *
7 * Authored by: Scott Ringwelski <sgringwe@mtu.edu>,
8- * Victor Eduardo <victoreduardm@gmail.com>,
9- * Corentin Noël <tintou@mailoo.org>
10+ * Corentin Noël <tintou@mailoo.org>,
11+ * Lucas Baudin <xapantu@gmail.com>,
12+ * ammonkey <am.monkeyd@gmail.com>,
13+ * Victor Martinez <victoreduardm@gmail.com>,
14+ * Sergey Davidoff <shnatsel@gmail.com>
15 */
16
17 using Gee;
18 using Gtk;
19
20+public class Noise.ContractMenuItem : Gtk.MenuItem {
21+ private Granite.Services.Contract contract;
22+ private Gee.List<Media> medias = new Gee.LinkedList<Media> ();
23+
24+ public ContractMenuItem (Granite.Services.Contract contract, GLib.List<Noise.Media> medias) {
25+ this.contract = contract;
26+
27+ foreach (var m in medias)
28+ this.medias.add (m);
29+
30+ label = contract.get_display_name ();
31+ }
32+
33+ public override void activate () {
34+ File[] files = {};
35+ foreach (Media m in this.medias) {
36+ files += m.file;
37+ debug("Added file to pass to Contractor: %s", m.uri);
38+ }
39+
40+ try {
41+ debug ("Executing contract \"%s\"", contract.get_display_name ());
42+ contract.execute_with_files (files);
43+ } catch (Error err) {
44+ warning ("Error executing contract \"%s\": %s",
45+ contract.get_display_name (), err.message);
46+ }
47+ }
48+}
49+
50 public class Noise.MusicListView : GenericList {
51
52 //for media list right click
53 Gtk.Menu mediaActionMenu;
54 Gtk.MenuItem mediaEditMedia;
55 Gtk.MenuItem mediaFileBrowse;
56+ Gtk.MenuItem mediaMenuContractorEntry; // make menu on fly
57 Gtk.MenuItem mediaTopSeparator;
58 Gtk.MenuItem mediaMenuQueue;
59 Gtk.MenuItem mediaMenuAddToPlaylist; // make menu on fly
60@@ -34,6 +68,7 @@
61 Gtk.MenuItem mediaRemove;
62 Gtk.MenuItem importToLibrary;
63 Gtk.MenuItem mediaScrollToCurrent;
64+ Gtk.MenuItem mediaScrollToCurrentSeparator;
65 bool is_queue = false;
66 bool read_only = false;
67
68@@ -87,8 +122,11 @@
69 button_release_event.connect(viewClickRelease);
70
71 mediaScrollToCurrent = new Gtk.MenuItem.with_label(_("Scroll to Current Song"));
72+ mediaScrollToCurrentSeparator = new SeparatorMenuItem ();
73+ mediaTopSeparator = new SeparatorMenuItem ();
74 mediaEditMedia = new Gtk.MenuItem.with_label(_("Edit Song Info"));
75 mediaFileBrowse = new Gtk.MenuItem.with_label(_("Show in File Browser"));
76+ mediaMenuContractorEntry = new Gtk.MenuItem.with_label(_("Other actions"));
77 mediaMenuQueue = new Gtk.MenuItem.with_label(C_("Action item (verb)", "Queue"));
78 mediaMenuAddToPlaylist = new Gtk.MenuItem.with_label(_("Add to Playlist"));
79 mediaRemove = new Gtk.MenuItem.with_label(_("Remove Song"));
80@@ -103,17 +141,19 @@
81 if(hint != ViewWrapper.Hint.ALBUM_LIST) {
82 //mediaActionMenu.append(browseSame);
83 mediaActionMenu.append(mediaScrollToCurrent);
84+ mediaActionMenu.append(mediaScrollToCurrentSeparator);
85 }
86
87- mediaTopSeparator = new SeparatorMenuItem ();
88-
89 if (read_only == false) {
90 mediaActionMenu.append(mediaEditMedia);
91 }
92+
93 mediaActionMenu.append(mediaFileBrowse);
94+ mediaActionMenu.append(mediaMenuContractorEntry);
95 if (read_only == false) {
96 mediaActionMenu.append(mediaRateMedia);
97 }
98+
99 mediaActionMenu.append(mediaTopSeparator);
100 mediaActionMenu.append(mediaMenuQueue);
101 if (read_only == false) {
102@@ -177,7 +217,7 @@
103 var mediaMenuNewPlaylist = new Gtk.MenuItem.with_label(_("New Playlist…"));
104 mediaMenuNewPlaylist.activate.connect(mediaMenuNewPlaylistClicked);
105 addToPlaylistMenu.append (mediaMenuNewPlaylist);
106- if(parent_wrapper.library.support_playlists () == false) {
107+ if (parent_wrapper.library.support_playlists () == false) {
108 mediaMenuNewPlaylist.set_visible(false);
109 }
110 foreach (var playlist in parent_wrapper.library.get_playlists ()) {
111@@ -235,6 +275,45 @@
112
113 mediaRateMedia.rating_value = set_rating;
114
115+ //remove the previous "Other Actions" submenu and create a new one
116+ var contractorSubMenu = new Gtk.Menu ();
117+ contractorSubMenu.show_all ();
118+ mediaMenuContractorEntry.submenu = contractorSubMenu;
119+ mediaMenuContractorEntry.sensitive = true;
120+
121+ try {
122+ var files = new HashSet<File> (); //for automatic deduplication
123+ var selected_medias = get_selected_medias (); //to avoid querying it every time
124+ debug ("Number of selected medias obtained by MusicListView class: %u\n", selected_medias.length ());
125+
126+ foreach (var media in selected_medias) {
127+ if (media.file.query_exists ()) {
128+ files.add (media.file);
129+ //if the file was marked nonexistent, update its status
130+ if (media.location_unknown && media.unique_status_image != null) {
131+ media.unique_status_image = null;
132+ media.location_unknown = false;
133+ }
134+ } else {
135+ warning ("File %s does not exist, ignoring it", media.uri);
136+ //indicate that the file doesn't exist in the UI
137+ media.unique_status_image = Icons.PROCESS_ERROR.render(Gtk.IconSize.MENU);
138+ media.location_unknown = true;
139+ }
140+ }
141+
142+ var contracts = Granite.Services.ContractorProxy.get_contracts_for_files (files.to_array ());
143+ foreach (var contract in contracts) {
144+ var menu_item = new ContractMenuItem (contract, selected_medias);
145+ contractorSubMenu.append (menu_item);
146+ }
147+ this.queue_draw ();
148+ contractorSubMenu.show_all ();
149+ } catch (Error err) {
150+ warning ("Failed to obtain Contractor actions: %s", err.message);
151+ mediaMenuContractorEntry.sensitive = false;
152+ }
153+
154 mediaActionMenu.popup (null, null, null, 3, get_current_event_time());
155
156 return true;
157@@ -321,7 +400,7 @@
158 var to_edit = new LinkedList<int>();
159 var to_edit_med = new LinkedList<Media>();
160
161- foreach(Media m in get_selected_medias()) {
162+ foreach (Media m in get_selected_medias()) {
163 to_edit.add(m.rowid);
164 if(to_edit.size == 1)
165 to_edit_med.add(m);
166@@ -360,7 +439,7 @@
167 }
168
169 protected void mediaFileBrowseClicked() {
170- foreach(Media m in get_selected_medias()) {
171+ foreach (Media m in get_selected_medias()) {
172 try {
173 var file = File.new_for_uri(m.uri);
174 Gtk.show_uri(null, file.get_parent().get_uri(), 0);
175@@ -399,7 +478,7 @@
176 var los = new LinkedList<Media>();
177 int new_rating = mediaRateMedia.rating_value;
178
179- foreach(Media m in get_selected_medias()) {
180+ foreach (Media m in get_selected_medias()) {
181 m.rating = new_rating;
182 los.add(m);
183 }
184@@ -431,7 +510,7 @@
185 void importToLibraryClicked() {
186 var to_import = new Gee.LinkedList<Media>();
187
188- foreach(Media m in get_selected_medias()) {
189+ foreach (Media m in get_selected_medias()) {
190 to_import.add (m);
191 }
192
193@@ -441,7 +520,7 @@
194 protected virtual void onDragDataGet(Gdk.DragContext context, Gtk.SelectionData selection_data, uint info, uint time_) {
195 string[] uris = null;
196
197- foreach(Media m in get_selected_medias()) {
198+ foreach (Media m in get_selected_medias()) {
199 debug("adding %s\n", m.uri);
200 uris += (m.uri);
201 }

Subscribers

People subscribed via source and target branches