Merge lp:~stolowski/unity-lens-shopping/u1ms-preview into lp:unity-lens-shopping
- u1ms-preview
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~stolowski/unity-lens-shopping/u1ms-preview |
Merge into: | lp:unity-lens-shopping |
Diff against target: |
431 lines (+350/-24) 6 files modified
po/POTFILES.in (+2/-0) po/POTFILES.skip (+2/-0) src/Makefile.am (+2/-0) src/preview-player-client.vala (+112/-0) src/scope.vala (+38/-24) src/u1ms-preview.vala (+194/-0) |
To merge this branch: | bzr merge lp:~stolowski/unity-lens-shopping/u1ms-preview |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michal Hruby (community) | Needs Fixing | ||
Review via email: mp+126211@code.launchpad.net |
This proposal has been superseded by a proposal from 2012-09-26.
Commit message
Reuse u1ms preview code from musicstore scope for u1ms results. The code has been refactored and copied over to have it isolated as much as possible in new classes/files. After Q we need to move it to a library and clean both shopping lens and musicstore scope.
Description of the change
Reuse u1ms preview code from musicstore scope for u1ms results. The code has been refactored and copied over to have it isolated as much as possible in new classes/files. After Q we need to move it to a library and clean both shopping lens and musicstore scope.
Michal Hruby (mhr3) wrote : | # |
Michal Hruby (mhr3) wrote : | # |
Oh actually POTFILES need update, make check fails.
Paweł Stołowski (stolowski) wrote : | # |
Fixed.
Unity Merger (unity-merger) wrote : | # |
There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.
Unmerged revisions
Preview Diff
1 | === modified file 'po/POTFILES.in' | |||
2 | --- po/POTFILES.in 2012-07-25 13:07:48 +0000 | |||
3 | +++ po/POTFILES.in 2012-09-26 09:58:22 +0000 | |||
4 | @@ -1,5 +1,7 @@ | |||
5 | 1 | [encoding: UTF-8] | 1 | [encoding: UTF-8] |
6 | 2 | src/daemon.vala | 2 | src/daemon.vala |
7 | 3 | src/main.vala | 3 | src/main.vala |
8 | 4 | src/u1ms-preview.vala | ||
9 | 5 | src/scope.vala | ||
10 | 4 | [type: gettext/ini]shopping.lens.in.in | 6 | [type: gettext/ini]shopping.lens.in.in |
11 | 5 | 7 | ||
12 | 6 | 8 | ||
13 | === modified file 'po/POTFILES.skip' | |||
14 | --- po/POTFILES.skip 2012-09-17 21:39:10 +0000 | |||
15 | +++ po/POTFILES.skip 2012-09-26 09:58:22 +0000 | |||
16 | @@ -5,3 +5,5 @@ | |||
17 | 5 | src/banshee-scope.c | 5 | src/banshee-scope.c |
18 | 6 | src/rhythmbox-scope.c | 6 | src/rhythmbox-scope.c |
19 | 7 | src/scope.c | 7 | src/scope.c |
20 | 8 | src/preview-player-client.c | ||
21 | 9 | src/u1ms-preview.c | ||
22 | 8 | 10 | ||
23 | === modified file 'src/Makefile.am' | |||
24 | --- src/Makefile.am 2012-09-17 18:04:15 +0000 | |||
25 | +++ src/Makefile.am 2012-09-26 09:58:22 +0000 | |||
26 | @@ -44,6 +44,8 @@ | |||
27 | 44 | main.vala \ | 44 | main.vala \ |
28 | 45 | scope.vala \ | 45 | scope.vala \ |
29 | 46 | markup-cleaner.vala \ | 46 | markup-cleaner.vala \ |
30 | 47 | preview-player-client.vala \ | ||
31 | 48 | u1ms-preview.vala \ | ||
32 | 47 | $(NULL) | 49 | $(NULL) |
33 | 48 | 50 | ||
34 | 49 | unity_shopping_daemon_SOURCES = \ | 51 | unity_shopping_daemon_SOURCES = \ |
35 | 50 | 52 | ||
36 | === added file 'src/preview-player-client.vala' | |||
37 | --- src/preview-player-client.vala 1970-01-01 00:00:00 +0000 | |||
38 | +++ src/preview-player-client.vala 2012-09-26 09:58:22 +0000 | |||
39 | @@ -0,0 +1,112 @@ | |||
40 | 1 | /* | ||
41 | 2 | * Copyright (C) 2012 Canonical Ltd | ||
42 | 3 | * | ||
43 | 4 | * This program is free software: you can redistribute it and/or modify | ||
44 | 5 | * it under the terms of the GNU General Public License version 3 as | ||
45 | 6 | * published by the Free Software Foundation. | ||
46 | 7 | * | ||
47 | 8 | * This program is distributed in the hope that it will be useful, | ||
48 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
49 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
50 | 11 | * GNU General Public License for more details. | ||
51 | 12 | * | ||
52 | 13 | * You should have received a copy of the GNU General Public License | ||
53 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
54 | 15 | * | ||
55 | 16 | * Authored by Pawel Stolowski <pawel.stolowski@canonical.com> | ||
56 | 17 | */ | ||
57 | 18 | |||
58 | 19 | |||
59 | 20 | /* | ||
60 | 21 | * FIXME: this code was copied from music-lens; it needs to be kept in sync and eventually moved to a library !!! | ||
61 | 22 | */ | ||
62 | 23 | namespace Unity.ShoppingLens { | ||
63 | 24 | |||
64 | 25 | static const string PREVIEW_PLAYER_DBUS_NAME = "com.canonical.Unity.Lens.Music.PreviewPlayer"; | ||
65 | 26 | static const string PREVIEW_PLAYER_DBUS_PATH = "/com/canonical/Unity/Lens/Music/PreviewPlayer"; | ||
66 | 27 | |||
67 | 28 | [DBus (name = "com.canonical.Unity.Lens.Music.PreviewPlayer")] | ||
68 | 29 | public interface PreviewPlayerService: GLib.Object | ||
69 | 30 | { | ||
70 | 31 | public signal void progress(string uri, uint32 state, double progress); | ||
71 | 32 | |||
72 | 33 | public abstract async void play (string uri) throws Error; | ||
73 | 34 | public abstract async void pause () throws Error; | ||
74 | 35 | public abstract async void pause_resume () throws Error; | ||
75 | 36 | public abstract async void resume () throws Error; | ||
76 | 37 | public abstract async void stop () throws Error; | ||
77 | 38 | public abstract async void close () throws Error; | ||
78 | 39 | } | ||
79 | 40 | |||
80 | 41 | public class PreviewPlayer: GLib.Object | ||
81 | 42 | { | ||
82 | 43 | public signal void progress(string uri, Unity.MusicPreview.TrackState state, double progress); | ||
83 | 44 | |||
84 | 45 | public async void connect_to () throws Error | ||
85 | 46 | { | ||
86 | 47 | _preview_player_service = Bus.get_proxy_sync (BusType.SESSION, PREVIEW_PLAYER_DBUS_NAME, PREVIEW_PLAYER_DBUS_PATH); | ||
87 | 48 | _preview_player_service.progress.connect (on_progress_signal); | ||
88 | 49 | } | ||
89 | 50 | |||
90 | 51 | public async void play (string uri) throws Error | ||
91 | 52 | { | ||
92 | 53 | if (_preview_player_service == null) | ||
93 | 54 | { | ||
94 | 55 | yield connect_to (); | ||
95 | 56 | } | ||
96 | 57 | yield _preview_player_service.play (uri); | ||
97 | 58 | } | ||
98 | 59 | |||
99 | 60 | public async void pause () throws Error | ||
100 | 61 | { | ||
101 | 62 | if (_preview_player_service == null) | ||
102 | 63 | { | ||
103 | 64 | yield connect_to (); | ||
104 | 65 | } | ||
105 | 66 | yield _preview_player_service.pause (); | ||
106 | 67 | } | ||
107 | 68 | |||
108 | 69 | public async void pause_resume () throws Error | ||
109 | 70 | { | ||
110 | 71 | if (_preview_player_service == null) | ||
111 | 72 | { | ||
112 | 73 | yield connect_to (); | ||
113 | 74 | } | ||
114 | 75 | yield _preview_player_service.pause_resume (); | ||
115 | 76 | } | ||
116 | 77 | |||
117 | 78 | public async void resume () throws Error | ||
118 | 79 | { | ||
119 | 80 | if (_preview_player_service == null) | ||
120 | 81 | { | ||
121 | 82 | yield connect_to (); | ||
122 | 83 | } | ||
123 | 84 | yield _preview_player_service.resume (); | ||
124 | 85 | } | ||
125 | 86 | |||
126 | 87 | public async void stop () throws Error | ||
127 | 88 | { | ||
128 | 89 | if (_preview_player_service == null) | ||
129 | 90 | { | ||
130 | 91 | yield connect_to (); | ||
131 | 92 | } | ||
132 | 93 | yield _preview_player_service.stop (); | ||
133 | 94 | } | ||
134 | 95 | |||
135 | 96 | public async void close () throws Error | ||
136 | 97 | { | ||
137 | 98 | if (_preview_player_service == null) | ||
138 | 99 | { | ||
139 | 100 | yield connect_to (); | ||
140 | 101 | } | ||
141 | 102 | yield _preview_player_service.close (); | ||
142 | 103 | } | ||
143 | 104 | |||
144 | 105 | internal void on_progress_signal (string uri, uint32 state, double progress_value) | ||
145 | 106 | { | ||
146 | 107 | progress(uri, (Unity.MusicPreview.TrackState)state, progress_value); | ||
147 | 108 | } | ||
148 | 109 | |||
149 | 110 | private PreviewPlayerService _preview_player_service; | ||
150 | 111 | } | ||
151 | 112 | } | ||
152 | 0 | \ No newline at end of file | 113 | \ No newline at end of file |
153 | 1 | 114 | ||
154 | === modified file 'src/scope.vala' | |||
155 | --- src/scope.vala 2012-09-20 11:00:09 +0000 | |||
156 | +++ src/scope.vala 2012-09-26 09:58:22 +0000 | |||
157 | @@ -27,6 +27,7 @@ | |||
158 | 27 | 27 | ||
159 | 28 | private HashTable<string, string> results_details_map; | 28 | private HashTable<string, string> results_details_map; |
160 | 29 | private HashTable<string, string> global_results_details_map; | 29 | private HashTable<string, string> global_results_details_map; |
161 | 30 | private PreviewPlayerHandler player; | ||
162 | 30 | 31 | ||
163 | 31 | public ShoppingScope () | 32 | public ShoppingScope () |
164 | 32 | { | 33 | { |
165 | @@ -136,30 +137,43 @@ | |||
166 | 136 | { | 137 | { |
167 | 137 | var parser = get_json_reply (details_uri, null); | 138 | var parser = get_json_reply (details_uri, null); |
168 | 138 | 139 | ||
193 | 139 | var root = parser.get_root ().get_object (); | 140 | if (U1MSPreviewFactory.is_u1ms_details (parser)) |
194 | 140 | unowned string title = root.get_string_member ("title"); | 141 | { |
195 | 141 | unowned string description = root.get_string_member ("description_html"); | 142 | var u1mspf = new U1MSPreviewFactory (); |
196 | 142 | unowned string price = root.get_string_member ("formatted_price"); | 143 | var preview = u1mspf.create_preview (parser); |
197 | 143 | if (price == null) price = root.get_string_member ("price"); | 144 | u1mspf.add_download_action (preview); // download will be handled by normal activation |
198 | 144 | 145 | if (player == null) | |
199 | 145 | var img_obj = root.get_object_member ("images"); | 146 | player = new PreviewPlayerHandler (); |
200 | 146 | string image_uri = extract_image_uri (img_obj, int.MAX); | 147 | player.music_preview = preview; |
201 | 147 | 148 | return preview; | |
202 | 148 | Icon? image = null; | 149 | } |
203 | 149 | if (image_uri != "") | 150 | else |
204 | 150 | { | 151 | { |
205 | 151 | image = new FileIcon (File.new_for_uri (image_uri)); | 152 | var root = parser.get_root ().get_object (); |
206 | 152 | } | 153 | unowned string title = root.get_string_member ("title"); |
207 | 153 | 154 | unowned string description = root.get_string_member ("description_html"); | |
208 | 154 | var preview = new GenericPreview (title, MarkupCleaner.html_to_pango_markup (description), image); | 155 | unowned string price = root.get_string_member ("formatted_price"); |
209 | 155 | var icon_dir = File.new_for_path (ICON_PATH); | 156 | if (price == null) price = root.get_string_member ("price"); |
210 | 156 | var icon = new FileIcon (icon_dir.get_child ("service-amazon.svg")); | 157 | |
211 | 157 | var buy_action = new PreviewAction ("buy", _("Buy"), icon); | 158 | var img_obj = root.get_object_member ("images"); |
212 | 158 | if (price != null) buy_action.extra_text = price; | 159 | string image_uri = extract_image_uri (img_obj, int.MAX); |
213 | 159 | /* Leaving the activation on unity for now */ | 160 | |
214 | 160 | // buy_action.activated.connect ((uri) => { }); | 161 | Icon? image = null; |
215 | 161 | preview.add_action (buy_action); | 162 | if (image_uri != "") |
216 | 162 | return preview; | 163 | { |
217 | 164 | image = new FileIcon (File.new_for_uri (image_uri)); | ||
218 | 165 | } | ||
219 | 166 | |||
220 | 167 | var preview = new GenericPreview (title, MarkupCleaner.html_to_pango_markup (description), image); | ||
221 | 168 | var icon_dir = File.new_for_path (ICON_PATH); | ||
222 | 169 | var icon = new FileIcon (icon_dir.get_child ("service-amazon.svg")); | ||
223 | 170 | var buy_action = new PreviewAction ("buy", _("Buy"), icon); | ||
224 | 171 | if (price != null) buy_action.extra_text = price; | ||
225 | 172 | /* Leaving the activation on unity for now */ | ||
226 | 173 | // buy_action.activated.connect ((uri) => { }); | ||
227 | 174 | preview.add_action (buy_action); | ||
228 | 175 | return preview; | ||
229 | 176 | } | ||
230 | 163 | } | 177 | } |
231 | 164 | catch (Error err) | 178 | catch (Error err) |
232 | 165 | { | 179 | { |
233 | 166 | 180 | ||
234 | === added file 'src/u1ms-preview.vala' | |||
235 | --- src/u1ms-preview.vala 1970-01-01 00:00:00 +0000 | |||
236 | +++ src/u1ms-preview.vala 2012-09-26 09:58:22 +0000 | |||
237 | @@ -0,0 +1,194 @@ | |||
238 | 1 | /* | ||
239 | 2 | * Copyright (C) 2011 Canonical, Ltd. | ||
240 | 3 | * | ||
241 | 4 | * This library is free software; you can redistribute it and/or modify | ||
242 | 5 | * it under the terms of the GNU Lesser General Public License | ||
243 | 6 | * version 3.0 as published by the Free Software Foundation. | ||
244 | 7 | * | ||
245 | 8 | * This library is distributed in the hope that it will be useful, | ||
246 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
247 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
248 | 11 | * GNU Lesser General Public License version 3.0 for more details. | ||
249 | 12 | * | ||
250 | 13 | * You should have received a copy of the GNU Lesser General Public | ||
251 | 14 | * License along with this library. If not, see | ||
252 | 15 | * <http://www.gnu.org/licenses/>. | ||
253 | 16 | * | ||
254 | 17 | * Authored by Pawel Stolowski <pawel.stolowski@canonical.com> | ||
255 | 18 | * | ||
256 | 19 | */ | ||
257 | 20 | |||
258 | 21 | /* | ||
259 | 22 | * FIXME: this code is based on musicstore-scope preview handling code; it needs to be kept in sync and eventually moved to a library !!! | ||
260 | 23 | */ | ||
261 | 24 | namespace Unity.ShoppingLens | ||
262 | 25 | { | ||
263 | 26 | |||
264 | 27 | public class PreviewPlayerHandler | ||
265 | 28 | { | ||
266 | 29 | private PreviewPlayer preview_player; | ||
267 | 30 | private Unity.MusicPreview music_preview_; | ||
268 | 31 | |||
269 | 32 | public Unity.MusicPreview music_preview | ||
270 | 33 | { | ||
271 | 34 | get | ||
272 | 35 | { | ||
273 | 36 | return music_preview_; | ||
274 | 37 | } | ||
275 | 38 | set | ||
276 | 39 | { | ||
277 | 40 | music_preview_ = value; | ||
278 | 41 | if (value != null) | ||
279 | 42 | { | ||
280 | 43 | music_preview.play.connect (play); | ||
281 | 44 | music_preview.pause.connect (pause); | ||
282 | 45 | music_preview.closed.connect (closed); | ||
283 | 46 | } | ||
284 | 47 | } | ||
285 | 48 | } | ||
286 | 49 | |||
287 | 50 | public PreviewPlayerHandler (Unity.MusicPreview? preview = null) | ||
288 | 51 | { | ||
289 | 52 | music_preview = preview; | ||
290 | 53 | } | ||
291 | 54 | |||
292 | 55 | private void on_progress_changed (string uri, Unity.MusicPreview.TrackState state, double progress) | ||
293 | 56 | { | ||
294 | 57 | if (music_preview != null) | ||
295 | 58 | { | ||
296 | 59 | music_preview.current_track_uri = uri; | ||
297 | 60 | music_preview.current_track_state = state; | ||
298 | 61 | music_preview.current_progress = (float)progress; | ||
299 | 62 | } | ||
300 | 63 | } | ||
301 | 64 | |||
302 | 65 | private void closed (Unity.Preview preview) | ||
303 | 66 | { | ||
304 | 67 | if (preview_player != null) | ||
305 | 68 | { | ||
306 | 69 | try | ||
307 | 70 | { | ||
308 | 71 | preview_player.close (); | ||
309 | 72 | } | ||
310 | 73 | catch (Error e) | ||
311 | 74 | { | ||
312 | 75 | warning ("Failed to close preview player: %s", e.message); | ||
313 | 76 | } | ||
314 | 77 | } | ||
315 | 78 | } | ||
316 | 79 | |||
317 | 80 | private void play (Unity.Preview preview, string uri) | ||
318 | 81 | { | ||
319 | 82 | debug ("play request: '%s'", uri); | ||
320 | 83 | |||
321 | 84 | try | ||
322 | 85 | { | ||
323 | 86 | if (preview_player == null) | ||
324 | 87 | { | ||
325 | 88 | preview_player = new PreviewPlayer (); | ||
326 | 89 | preview_player.progress.connect (on_progress_changed); | ||
327 | 90 | preview_player.connect_to (); | ||
328 | 91 | } | ||
329 | 92 | |||
330 | 93 | // we will receive state back in on_progress_changed, but set it now so that it's immediately reflected in the dash | ||
331 | 94 | music_preview.current_track_uri = uri; | ||
332 | 95 | music_preview.current_progress = 0.0f; | ||
333 | 96 | music_preview.current_track_state = Unity.MusicPreview.TrackState.PLAYING; | ||
334 | 97 | |||
335 | 98 | preview_player.play (uri); | ||
336 | 99 | } | ||
337 | 100 | catch (Error e) | ||
338 | 101 | { | ||
339 | 102 | warning ("Failed to play '%s': %s", uri, e.message); | ||
340 | 103 | } | ||
341 | 104 | } | ||
342 | 105 | |||
343 | 106 | public void pause (Unity.Preview preview, string uri) | ||
344 | 107 | { | ||
345 | 108 | debug ("pause request: '%s'", uri); | ||
346 | 109 | |||
347 | 110 | try | ||
348 | 111 | { | ||
349 | 112 | if (preview_player != null) | ||
350 | 113 | { | ||
351 | 114 | // we will receive state back in on_progress_changed, but set it now so that it's immediately reflected in the dash | ||
352 | 115 | music_preview.current_track_uri = uri; | ||
353 | 116 | music_preview.current_track_state = Unity.MusicPreview.TrackState.PAUSED; | ||
354 | 117 | |||
355 | 118 | preview_player.pause (); | ||
356 | 119 | } | ||
357 | 120 | } | ||
358 | 121 | catch (Error e) | ||
359 | 122 | { | ||
360 | 123 | warning ("Failed to pause '%s': %s", uri, e.message); | ||
361 | 124 | } | ||
362 | 125 | } | ||
363 | 126 | } | ||
364 | 127 | |||
365 | 128 | |||
366 | 129 | public class U1MSPreviewFactory | ||
367 | 130 | { | ||
368 | 131 | public string formatted_price { get; internal set; } | ||
369 | 132 | |||
370 | 133 | public static bool is_u1ms_details (Json.Parser parser) | ||
371 | 134 | { | ||
372 | 135 | var root_obj = parser.get_root ().get_object (); | ||
373 | 136 | return root_obj.has_member ("source") && root_obj.get_string_member ("source") == "Ubuntu One Music Store" && root_obj.has_member ("tracks"); | ||
374 | 137 | } | ||
375 | 138 | |||
376 | 139 | public Unity.MusicPreview? create_preview (Json.Parser parser) | ||
377 | 140 | { | ||
378 | 141 | var root_obj = parser.get_root ().get_object (); | ||
379 | 142 | |||
380 | 143 | var title = root_obj.get_string_member ("title"); | ||
381 | 144 | var artwork_path = root_obj.get_string_member ("image"); | ||
382 | 145 | File cover_file = File.new_for_uri (artwork_path); //artwork path is a remote uri | ||
383 | 146 | var cover = new FileIcon (cover_file); | ||
384 | 147 | |||
385 | 148 | var artist = root_obj.get_string_member ("artist"); | ||
386 | 149 | |||
387 | 150 | var preview = new Unity.MusicPreview (title, artist, cover); | ||
388 | 151 | |||
389 | 152 | if (root_obj.has_member ("formatted_price")) | ||
390 | 153 | formatted_price = root_obj.get_string_member ("formatted_price"); | ||
391 | 154 | else | ||
392 | 155 | formatted_price = ""; | ||
393 | 156 | |||
394 | 157 | if (root_obj.has_member ("tracks")) | ||
395 | 158 | { | ||
396 | 159 | var tracks_node = root_obj.get_array_member ("tracks"); | ||
397 | 160 | int i = 1; | ||
398 | 161 | foreach (var track_node in tracks_node.get_elements ()) | ||
399 | 162 | { | ||
400 | 163 | var track_obj = track_node.get_object (); | ||
401 | 164 | TrackMetadata tm = new TrackMetadata (); | ||
402 | 165 | tm.uri = track_obj.get_string_member ("preview"); | ||
403 | 166 | tm.track_no = i++; //FIXME: u1ms search doesn't provide track numbers *yet* | ||
404 | 167 | tm.title = track_obj.get_string_member ("title"); | ||
405 | 168 | tm.length = (int)track_obj.get_member ("duration").get_int (); | ||
406 | 169 | preview.add_track (tm); | ||
407 | 170 | } | ||
408 | 171 | } | ||
409 | 172 | else // details for single track | ||
410 | 173 | { | ||
411 | 174 | TrackMetadata tm = new TrackMetadata (); | ||
412 | 175 | tm.uri = root_obj.get_string_member ("preview"); | ||
413 | 176 | tm.title = root_obj.get_string_member ("title"); | ||
414 | 177 | tm.length = (int)root_obj.get_member ("duration").get_int (); | ||
415 | 178 | preview.add_track (tm); | ||
416 | 179 | } | ||
417 | 180 | return preview; | ||
418 | 181 | } | ||
419 | 182 | |||
420 | 183 | public Unity.PreviewAction? add_download_action (Unity.MusicPreview preview) | ||
421 | 184 | { | ||
422 | 185 | GLib.Icon? icon = new GLib.FileIcon (File.new_for_path (Config.DATADIR + "/icons/unity-icon-theme/places/svg/service-u1.svg")); | ||
423 | 186 | var download_action = new Unity.PreviewAction ("download_album", _("Download"), icon); | ||
424 | 187 | if (formatted_price != null) | ||
425 | 188 | download_action.extra_text = formatted_price; | ||
426 | 189 | preview.add_action (download_action); | ||
427 | 190 | return download_action; | ||
428 | 191 | } | ||
429 | 192 | } | ||
430 | 193 | |||
431 | 194 | } |
Can you please add huge WARNING / FIXME comment on the top saying that the file is a copy, and should be kept in sync with the original / moved in a common lib.
Otherwise looks and works fine.