Merge lp:~stolowski/unity-lens-video/vala-rewrite into lp:unity-lens-video

Proposed by Paweł Stołowski
Status: Merged
Approved by: Paweł Stołowski
Approved revision: 158
Merged at revision: 106
Proposed branch: lp:~stolowski/unity-lens-video/vala-rewrite
Merge into: lp:unity-lens-video
Diff against target: 4321 lines (+3341/-668)
50 files modified
Makefile.am (+5/-0)
Makefile.am.coverage (+48/-0)
Makefile.decl (+75/-0)
acinclude.m4 (+40/-0)
autogen.sh (+13/-0)
configure.ac (+208/-0)
data/Makefile.am (+27/-0)
data/unity-lens-video.service.in (+1/-1)
data/unity-scope-video-remote.service.in (+3/-0)
data/video-remote.scope (+3/-0)
debian/changelog (+11/-0)
debian/control (+26/-13)
debian/copyright (+6/-8)
debian/rules (+7/-2)
debian/unity-lens-video.install (+3/-0)
debian/unity-scope-video-remote.install (+3/-0)
debian/watch (+1/-1)
m4/gcov.m4 (+86/-0)
po/POTFILES.in (+5/-2)
po/POTFILES.skip (+7/-0)
po/unity-lens-video.pot (+0/-70)
setup.cfg (+0/-10)
setup.py (+0/-18)
src/Makefile.am (+133/-0)
src/blacklist-tracker.vala (+128/-0)
src/config.vala.in (+16/-0)
src/daemon.vala (+79/-0)
src/locate.vala (+112/-0)
src/main.vala (+59/-0)
src/remote-scope-globals.vala (+25/-0)
src/remote-scope.vala (+491/-0)
src/remote-uri.vala (+52/-0)
src/remote-video-main.vala (+60/-0)
src/scope.vala (+427/-0)
src/thumbnailer.vala (+115/-0)
src/ubuntu-video-search.vala (+229/-0)
src/unity-lens-video (+0/-543)
src/utils.vala (+77/-0)
src/video-file.vala (+59/-0)
tests/unit/Makefile.am (+120/-0)
tests/unit/config-tests.vala.in (+6/-0)
tests/unit/data/videosearch_details1.txt (+26/-0)
tests/unit/data/videosearch_input1.txt (+44/-0)
tests/unit/data/videosearch_input2.txt (+17/-0)
tests/unit/test-locate.vala (+57/-0)
tests/unit/test-ubuntu-video-search.vala (+329/-0)
tests/unit/test-utils.vala (+60/-0)
tests/unit/thumbnailer-mock.vala (+29/-0)
vapi/Makefile.am (+5/-0)
vapi/libsoup-gnome-2.4.vapi (+8/-0)
To merge this branch: bzr merge lp:~stolowski/unity-lens-video/vala-rewrite
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Needs Fixing
Michal Hruby (community) Approve
Review via email: mp+135935@code.launchpad.net

Commit message

This is video lens & remote video scope, rewritten in vala. Majority of the logic in the code is based on the original logic of python code and it shares the same shortcomings (e.g. it's based on "locate"), but this is a starting point for further improvements. This branch also introduces unit tests.

Description of the change

This is video lens & remote video scope, rewritten in vala. Majority of the logic in the code is based on the original logic of python code and it shares the same shortcomings (e.g. it's based on "locate"), but this is a starting point for further improvements. This branch also introduces unity tests.

To post a comment you must log in.
Revision history for this message
Michal Hruby (mhr3) wrote :

How was the rule? Diffs above 1k lines must be needs fixing? :)

889 + public string locate_bin { get; set; default = "/usr/bin/locate"; }

Why the absolute paths?

906 + GLib.Process.spawn_command_line_sync (@"$locate_bin -id $cache_db $query", out stdout);

A limit should be added here, no need for thousands of results. (-l)

939 + int video_count = 0;

Unused.

1128 + recommendations = new Gee.ArrayList<RemoteVideoFile?> (null);

Please get rid of the null, it's default param, and kinda confusing when specified.

1133 + zeitgeist_init ();

Not really sure the lens should be inserting events to zeitgeist, it has browser extensions that would log these kind of events (although they're not installed by default).

1144 + session.user_agent = "Unity Video Lens Remote Scope v0.4";

Let's use the version from configure.ac

1148 + search_changed.connect ((search, search_type, cancellable) => {
1149 + update_search_async.begin (search, search_type, cancellable);
1150 + });

Parse error, the brackets don't match indentation :P (present in multiple lambda instances)

1158 + preferences.notify["remote-content-search"].connect ((obj, pspec) => {
1159 + queue_search_changed (SearchType.GLOBAL);
1160 + });

This scope doesn't participate in global searches, should be DEFAULT.

1205 + recommendations = results;

if (results != null) ...

1678 + if (Utils.dbus_name_has_owner (BUS_NAME))
1679 + {

There should probably be a wrapper for this in Extras.

2194 + If nothing has been found, it tries to generate a thumbnail with Totem,
2195 + stores and uses it.

Really not liking that, unity should do this for us, we're slowing down the result model populating because of thumbnails.

3227 +check_PROGRAMS = \

For future - let's try to minimize the number of test vala binaries, it's overcomplicating the build process, plus GTest supports -p to pick prefix of tests that you want to run.

3332 +# Create dummy video files, needed by tests

Can you just make these part of the bzr tree (inside tests/data)

3340 +# Copy over test input files, needed by tests when built & run our-of-src directory

Why do these have to be copied over? Just load them from the srcdir? The fewer non-standard make rules, the better. (plus no need for these to be .in if they don't depend on any configure variable)

review: Needs Fixing
Revision history for this message
Paweł Stołowski (stolowski) wrote :

Implemented all (hopefully) suggested corrections. As discussed on IRC, zeitgeist and thumbnail handling should stay as is for now.

Revision history for this message
Michal Hruby (mhr3) wrote :

Approving from my side, the behavior didn't change, but mem usage went down from 19mb to 6mb. Please approve globally once the packaging is updated.

review: Approve
Revision history for this message
Michal Hruby (mhr3) wrote :

Setting to Needs fixing again, as we need to ensure that the recent SRU changes for remote-video-scope that went to trunk are backported here as well.

review: Needs Fixing
Revision history for this message
Michal Hruby (mhr3) wrote :

About time! :)

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Terry (mterry) wrote :

You should be able to fix the build by merging from trunk (at least, debian/changelog, which conflicts)

157. By Paweł Stołowski

Merged trunk.

158. By Paweł Stołowski

Fixed changelog entries for merging with trunk.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'AUTHORS'
2=== added file 'ChangeLog'
3=== added file 'Makefile.am'
4--- Makefile.am 1970-01-01 00:00:00 +0000
5+++ Makefile.am 2013-02-19 17:19:01 +0000
6@@ -0,0 +1,5 @@
7+SUBDIRS = src po tests/unit vapi data
8+
9+DISTCHECK_CONFIGURE_FLAGS = --enable-localinstall
10+
11+include $(top_srcdir)/Makefile.am.coverage
12
13=== added file 'Makefile.am.coverage'
14--- Makefile.am.coverage 1970-01-01 00:00:00 +0000
15+++ Makefile.am.coverage 2013-02-19 17:19:01 +0000
16@@ -0,0 +1,48 @@
17+
18+# Coverage targets
19+
20+.PHONY: clean-gcno clean-gcda \
21+ coverage-html generate-coverage-html clean-coverage-html \
22+ coverage-gcovr generate-coverage-gcovr clean-coverage-gcovr
23+
24+clean-local: clean-gcno clean-coverage-html clean-coverage-gcovr
25+
26+if HAVE_GCOV
27+
28+clean-gcno:
29+ @echo Removing old coverage instrumentation
30+ -find -name '*.gcno' -print | xargs -r rm
31+
32+clean-gcda:
33+ @echo Removing old coverage results
34+ -find -name '*.gcda' -print | xargs -r rm
35+
36+coverage-html: clean-gcda
37+ -$(MAKE) $(AM_MAKEFLAGS) -k check
38+ $(MAKE) $(AM_MAKEFLAGS) generate-coverage-html
39+
40+generate-coverage-html:
41+ @echo Collecting coverage data
42+ $(LCOV) --directory $(top_builddir) --capture --output-file coverage.info --no-checksum --compat-libtool
43+ LANG=C $(GENHTML) --prefix $(top_builddir) --output-directory coveragereport --title "Code Coverage" --legend --show-details coverage.info
44+
45+clean-coverage-html: clean-gcda
46+ -$(LCOV) --directory $(top_builddir) -z
47+ -rm -rf coverage.info coveragereport
48+
49+if HAVE_GCOVR
50+
51+coverage-gcovr: clean-gcda
52+ -$(MAKE) $(AM_MAKEFLAGS) -k check
53+ $(MAKE) $(AM_MAKEFLAGS) generate-coverage-gcovr
54+
55+generate-coverage-gcovr:
56+ @echo Generating coverage GCOVR report
57+ $(GCOVR) -x -r $(top_builddir) -o $(top_builddir)/coverage.xml
58+
59+clean-coverage-gcovr: clean-gcda
60+ -rm -rf $(top_builddir)/coverage.xml
61+
62+endif # HAVE_GCOVR
63+
64+endif # HAVE_GCOV
65
66=== added file 'Makefile.decl'
67--- Makefile.decl 1970-01-01 00:00:00 +0000
68+++ Makefile.decl 2013-02-19 17:19:01 +0000
69@@ -0,0 +1,75 @@
70+# GLIB - Library of useful C routines
71+#
72+# This file is copied almost verbatim from the GLib-2.0 distribution
73+#
74+
75+GTESTER = gtester
76+GTESTER_REPORT = gtester-report
77+
78+# initialize variables for unconditional += appending
79+EXTRA_DIST =
80+TEST_PROGS =
81+
82+### testing rules
83+
84+# test: run all tests in cwd and subdirs
85+test: test-nonrecursive
86+ @ for subdir in $(SUBDIRS) . ; do \
87+ test "$$subdir" = "." -o "$$subdir" = "po" || \
88+ ( cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $@ ) || exit $? ; \
89+ done
90+
91+# test-nonrecursive: run tests only in cwd
92+test-nonrecursive: ${TEST_PROGS}
93+ @test -z "${TEST_PROGS}" || G_DEBUG=gc-friendly MALLOC_CHECK_=2 MALLOC_PERTURB_=$$(($${RANDOM:-256} % 256)) ${GTESTER} --verbose ${TEST_PROGS}
94+
95+# test-report: run tests in subdirs and generate report
96+# perf-report: run tests in subdirs with -m perf and generate report
97+# full-report: like test-report: with -m perf and -m slow
98+test-report perf-report full-report: ${TEST_PROGS}
99+ @test -z "${TEST_PROGS}" || { \
100+ case $@ in \
101+ test-report) test_options="-k";; \
102+ perf-report) test_options="-k -m=perf";; \
103+ full-report) test_options="-k -m=perf -m=slow";; \
104+ esac ; \
105+ if test -z "$$GTESTER_LOGDIR" ; then \
106+ ${GTESTER} --verbose $$test_options -o test-report.xml ${TEST_PROGS} ; \
107+ elif test -n "${TEST_PROGS}" ; then \
108+ ${GTESTER} --verbose $$test_options -o `mktemp "$$GTESTER_LOGDIR/log-XXXXXX"` ${TEST_PROGS} ; \
109+ fi ; \
110+ }
111+ @ ignore_logdir=true ; \
112+ if test -z "$$GTESTER_LOGDIR" ; then \
113+ GTESTER_LOGDIR=`mktemp -d "\`pwd\`/.testlogs-XXXXXX"`; export GTESTER_LOGDIR ; \
114+ ignore_logdir=false ; \
115+ fi ; \
116+ REVISION=$(VERSION) ; \
117+ for subdir in $(SUBDIRS) . ; do \
118+ test "$$subdir" = "." -o "$$subdir" = "po" || \
119+ ( cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $@ ) || exit $? ; \
120+ done ; \
121+ $$ignore_logdir || { \
122+ echo '<?xml version="1.0"?>' > $@.xml ; \
123+ echo '<report-collection>' >> $@.xml ; \
124+ echo '<info>' >> $@.xml ; \
125+ echo ' <package>$(PACKAGE)</package>' >> $@.xml ; \
126+ echo ' <version>$(VERSION)</version>' >> $@.xml ; \
127+ echo " <revision>$$REVISION</revision>" >> $@.xml ; \
128+ echo '</info>' >> $@.xml ; \
129+ for lf in `ls -L "$$GTESTER_LOGDIR"/.` ; do \
130+ sed '1,1s/^<?xml\b[^>?]*?>//' <"$$GTESTER_LOGDIR"/"$$lf" >> $@.xml ; \
131+ done ; \
132+ echo >> $@.xml ; \
133+ echo '</report-collection>' >> $@.xml ; \
134+ rm -rf "$$GTESTER_LOGDIR"/ ; \
135+ ${GTESTER_REPORT} --version 2>/dev/null 1>&2 ; test "$$?" != 0 || ${GTESTER_REPORT} $@.xml >$@.html ; \
136+ }
137+.PHONY: test test-report perf-report full-report test-nonrecursive
138+
139+# run tests in cwd as part of make check
140+if ENABLE_HEADLESS_TESTS
141+check-local: test-headless
142+else
143+check-local: test-nonrecursive
144+endif
145
146=== added file 'NEWS'
147=== added file 'acinclude.m4'
148--- acinclude.m4 1970-01-01 00:00:00 +0000
149+++ acinclude.m4 2013-02-19 17:19:01 +0000
150@@ -0,0 +1,40 @@
151+dnl AS_AC_EXPAND(VAR, CONFIGURE_VAR)
152+dnl
153+dnl example
154+dnl AS_AC_EXPAND(SYSCONFDIR, $sysconfdir)
155+dnl will set SYSCONFDIR to /usr/local/etc if prefix=/usr/local
156+
157+AC_DEFUN([AS_AC_EXPAND],
158+[
159+ EXP_VAR=[$1]
160+ FROM_VAR=[$2]
161+
162+ dnl first expand prefix and exec_prefix if necessary
163+ prefix_save=$prefix
164+ exec_prefix_save=$exec_prefix
165+
166+ dnl if no prefix given, then use /usr/local, the default prefix
167+ if test "x$prefix" = "xNONE"; then
168+ prefix=$ac_default_prefix
169+ fi
170+ dnl if no exec_prefix given, then use prefix
171+ if test "x$exec_prefix" = "xNONE"; then
172+ exec_prefix=$prefix
173+ fi
174+
175+ full_var="$FROM_VAR"
176+ dnl loop until it doesn't change anymore
177+ while true; do
178+ new_full_var="`eval echo $full_var`"
179+ if test "x$new_full_var"="x$full_var"; then break; fi
180+ full_var=$new_full_var
181+ done
182+
183+ dnl clean up
184+ full_var=$new_full_var
185+ AC_SUBST([$1], "$full_var")
186+
187+ dnl restore prefix and exec_prefix
188+ prefix=$prefix_save
189+ exec_prefix=$exec_prefix_save
190+])
191
192=== added file 'autogen.sh'
193--- autogen.sh 1970-01-01 00:00:00 +0000
194+++ autogen.sh 2013-02-19 17:19:01 +0000
195@@ -0,0 +1,13 @@
196+#!/bin/sh
197+
198+srcdir=`dirname $0`
199+
200+PKG_NAME="unity-lens-video"
201+
202+which gnome-autogen.sh || {
203+ echo "You need gnome-common from GNOME SVN"
204+ exit 1
205+}
206+
207+USE_GNOME2_MACROS=1 \
208+. gnome-autogen.sh "$@"
209
210=== added file 'configure.ac'
211--- configure.ac 1970-01-01 00:00:00 +0000
212+++ configure.ac 2013-02-19 17:19:01 +0000
213@@ -0,0 +1,208 @@
214+AC_INIT(unity-lens-video, 6.8.0, https://launchpad.net/unity-lens-video)
215+AC_COPYRIGHT([Copyright 2012 Canonical])
216+
217+AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION)
218+
219+#####################################################
220+# Silent build rules
221+#####################################################
222+m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
223+
224+AC_PREREQ(2.59)
225+
226+AC_CONFIG_HEADERS([config.h])
227+
228+#####################################################
229+# Init the other things we depend on
230+#####################################################
231+AM_MAINTAINER_MODE
232+AM_PROG_VALAC([0.16.0])
233+AS_IF([test -z "$VALAC"], [AC_MSG_ERROR(["No valac compiler found."])])
234+AC_PROG_CC
235+AM_PROG_CC_C_O
236+AC_HEADER_STDC
237+
238+LT_INIT
239+AC_CONFIG_MACRO_DIR([m4])
240+
241+#############################################
242+# Gettext
243+#############################################
244+GETTEXT_PACKAGE="$PACKAGE"
245+AC_SUBST(GETTEXT_PACKAGE)
246+#AC_SUBST([CONFIG_STATUS_DEPENDENCIES],['$(top_srcdir)/po/LINGUAS'])
247+AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE", [gettext domain])
248+AM_GLIB_GNU_GETTEXT
249+
250+# AM_GNOME_GETTEXT above substs $DATADIRNAME
251+# this is the directory where the *.{mo,gmo} files are installed
252+localedir='${prefix}/${DATADIRNAME}/locale'
253+AC_SUBST(localedir)
254+
255+IT_PROG_INTLTOOL([0.40.0])
256+
257+AC_DEFINE_UNQUOTED(LOCALE_DIR, "${PREFIX}/${DATADIRNAME}/locale",[Locale directory])
258+AC_DEFINE_UNQUOTED(DATADIR, "${PREFIX}/${DATADIRNAME}",[Data directory])
259+AC_DEFINE_UNQUOTED(PREFIXDIR, "${PREFIX}",[Prefix directory])
260+
261+######################################################
262+# intltool rule for generating translated .lens file
263+######################################################
264+INTLTOOL_LENS_RULE='%.lens: %.lens.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@'
265+AC_SUBST(INTLTOOL_LENS_RULE)
266+
267+###########################
268+# gcov coverage reporting
269+###########################
270+m4_include([m4/gcov.m4])
271+AC_TDD_GCOV
272+AM_CONDITIONAL([HAVE_GCOV], [test "x$ac_cv_check_gcov" = xyes])
273+AM_CONDITIONAL([HAVE_LCOV], [test "x$ac_cv_check_lcov" = xyes])
274+AM_CONDITIONAL([HAVE_GCOVR], [test "x$ac_cv_check_gcovr" = xyes])
275+AC_SUBST(COVERAGE_CFLAGS)
276+AC_SUBST(COVERAGE_LDFLAGS)
277+
278+#####################################################
279+# Check for module and library dependancies
280+#####################################################
281+GLIB_REQUIRED=2.27
282+PKG_CHECK_MODULES(LENS_DAEMON,
283+ glib-2.0 >= $GLIB_REQUIRED
284+ gobject-2.0 >= $GLIB_REQUIRED
285+ gio-2.0 >= $GLIB_REQUIRED
286+ gio-unix-2.0 >= $GLIB_REQUIRED
287+ dee-1.0 >= 1.0.7
288+ gee-1.0
289+ libsoup-gnome-2.4
290+ json-glib-1.0
291+ zeitgeist-1.0 >= 0.3.8
292+ unity >= 6.90.2
293+ unity-extras >= 6.90.2
294+ )
295+
296+AC_SUBST(LENS_DAEMON_CFLAGS)
297+AC_SUBST(LENS_DAEMON_LIBS)
298+
299+####################################################################
300+# C compiler warnings
301+####################################################################
302+AC_ARG_ENABLE([c-warnings],
303+ AC_HELP_STRING([--enable-c-warnings=@<:@no/yes@:>@], [show warnings from the C compiler @<:@default=no@:>@]),,
304+ [enable_c_warnings=no])
305+
306+if test "x$enable_c_warnings" = "xyes"; then
307+ AC_DEFINE(ENABLE_C_WARNINGS, 1, [show warnings from the C compiler])
308+fi
309+
310+AM_CONDITIONAL(ENABLE_C_WARNINGS, test "$enable_c_warnings" = "yes")
311+
312+#####################################################
313+# local install for distcheck and stand-alone running
314+#####################################################
315+with_localinstall="no"
316+AC_ARG_ENABLE(localinstall,
317+ AS_HELP_STRING([--enable-localinstall],
318+ [Install all of the files locally instead of in system directories (for distcheck)]),
319+ with_localinstall=$enableval,
320+ with_localinstall=no)
321+
322+AM_CONDITIONAL([HAVE_LOCALINSTALL], [test "x$with_localinstall" = "xyes"])
323+
324+####################################################################
325+# Headless tests
326+####################################################################
327+AC_ARG_ENABLE([headless-tests],
328+ AS_HELP_STRING([--enable-headless-tests=@<:@no/yes@:>@],[enable headless test suite (requires Xvfb) @<:@default=no@:>@]),,
329+ [enable_headless_tests=no])
330+
331+AM_CONDITIONAL([ENABLE_HEADLESS_TESTS],[test "x$enable_headless_tests" != "xno"])
332+
333+if test "x$enable_headless_tests" = "xyes"; then
334+ AC_PATH_PROG([XVFB],[xvfb-run])
335+fi
336+
337+#####################################################
338+# local install for distcheck and stand-alone running
339+#####################################################
340+with_localinstall="no"
341+AC_ARG_ENABLE(localinstall,
342+ AS_HELP_STRING([--enable-localinstall],
343+ [Install all of the files locally instead of in system directories (for distcheck)]),
344+ with_localinstall=$enableval,
345+ with_localinstall=no)
346+
347+AM_CONDITIONAL([HAVE_LOCALINSTALL], [test "x$with_localinstall" = "xyes"])
348+
349+#####################################################
350+# Expand variables needed for config.vala
351+#####################################################
352+AS_AC_EXPAND(PREFIX, $prefix)
353+AC_SUBST(PREFIX)
354+
355+AS_AC_EXPAND(DATADIR, $datarootdir)
356+AC_SUBST(DATADIR)
357+
358+#####################################################
359+# Look for dbus service dir
360+#####################################################
361+if test "x$with_localinstall" = "xyes"; then
362+ DBUSSERVICEDIR="${datadir}/dbus-1/services/"
363+else
364+ DBUSSERVICEDIR=`$PKG_CONFIG --variable=session_bus_services_dir dbus-1`
365+fi
366+AC_SUBST(DBUSSERVICEDIR)
367+
368+#####################################################
369+# Look for correct Lenses dir
370+#####################################################
371+if test "x$with_localinstall" = "xyes"; then
372+ LENSESDIR="${datadir}/unity/lenses"
373+else
374+ LENSESDIR=`$PKG_CONFIG --variable=lensesdir unity`
375+fi
376+AC_SUBST(LENSESDIR)
377+
378+#############################################
379+# GSettings macros
380+#############################################
381+
382+GLIB_GSETTINGS
383+
384+#####################################################
385+# Create the Makefiles
386+#####################################################
387+AC_CONFIG_FILES([
388+ Makefile
389+ data/Makefile
390+ data/video.lens.in
391+ src/Makefile
392+ po/Makefile.in
393+ src/config.vala
394+ tests/unit/config-tests.vala
395+ tests/unit/Makefile
396+ vapi/Makefile
397+])
398+AC_OUTPUT
399+
400+#####################################################
401+# Output the results
402+#####################################################
403+AC_MSG_NOTICE([
404+
405+ Unity Video Lens Daemon $VERSION
406+ ----------------------------------
407+
408+ Prefix : ${prefix}
409+
410+ Local install : ${with_localinstall}
411+
412+ Extra CFlags : ${CPPFLAGS} $MAINTAINER_CFLAGS
413+ Extra ValaFlags : ${CPPFLAGS} $MAINTAINER_VALAFLAGS
414+
415+ Lenses Directory: ${LENSESDIR}
416+
417+ Testing
418+ Headless tests : ${enable_headless_tests}
419+ Coverage reporting : ${use_gcov}
420+
421+])
422
423=== added directory 'data'
424=== added file 'data/Makefile.am'
425--- data/Makefile.am 1970-01-01 00:00:00 +0000
426+++ data/Makefile.am 2013-02-19 17:19:01 +0000
427@@ -0,0 +1,27 @@
428+dbus_servicesdir = $(DBUSSERVICEDIR)
429+service_in_files = \
430+ unity-scope-video-remote.service.in \
431+ unity-lens-video.service.in \
432+ $(NULL)
433+
434+dbus_services_DATA = $(service_in_files:.service.in=.service)
435+
436+%.service: %.service.in
437+ $(AM_V_GEN)sed -e "s|\@pkglibexecdir\@|$(pkglibexecdir)|" $< > $@
438+
439+lens_in_files = video.lens.in video-remote.scope
440+lensdir = $(LENSESDIR)/video
441+lens_DATA = $(lens_in_files:.lens.in=.lens)
442+
443+@INTLTOOL_LENS_RULE@
444+
445+EXTRA_DIST = \
446+ $(service_in_files) \
447+ $(lens_in_files) \
448+ $(NULL)
449+
450+CLEANFILES = \
451+ unity-scope-video-remote.service \
452+ unity-lens-video.service \
453+ video.lens \
454+ $(NULL)
455
456=== renamed file 'unity-lens-video.desktop' => 'data/unity-lens-video.desktop'
457=== renamed file 'unity-lens-video.png' => 'data/unity-lens-video.png'
458=== renamed file 'unity-lens-video.service' => 'data/unity-lens-video.service.in'
459--- unity-lens-video.service 2012-02-02 19:43:07 +0000
460+++ data/unity-lens-video.service.in 2013-02-19 17:19:01 +0000
461@@ -1,3 +1,3 @@
462 [D-BUS Service]
463 Name=net.launchpad.lens.video
464-Exec=/usr/lib/unity-lens-video/unity-lens-video
465+Exec=@pkglibexecdir@/unity-video-lens-daemon
466
467=== added file 'data/unity-scope-video-remote.service.in'
468--- data/unity-scope-video-remote.service.in 1970-01-01 00:00:00 +0000
469+++ data/unity-scope-video-remote.service.in 2013-02-19 17:19:01 +0000
470@@ -0,0 +1,3 @@
471+[D-BUS Service]
472+Name=net.launchpad.scope.RemoteVideos
473+Exec=@pkglibexecdir@/unity-scope-video-remote
474
475=== added file 'data/video-remote.scope'
476--- data/video-remote.scope 1970-01-01 00:00:00 +0000
477+++ data/video-remote.scope 2013-02-19 17:19:01 +0000
478@@ -0,0 +1,3 @@
479+[Scope]
480+DBusName=net.launchpad.scope.RemoteVideos
481+DBusPath=/net/launchpad/scope/remotevideos
482
483=== renamed file 'video.lens.in' => 'data/video.lens.in.in'
484=== modified file 'debian/changelog'
485--- debian/changelog 2012-12-05 09:29:20 +0000
486+++ debian/changelog 2013-02-19 17:19:01 +0000
487@@ -1,3 +1,14 @@
488+unity-lens-video (0.3.14daily12.12.05-0ubuntu2) UNRELEASED; urgency=low
489+
490+ * debian/control:
491+ - Update Build-Depends for Vala rewrite
492+ - Add unity-scope-video-remote to this source package
493+ * debian/rules:
494+ - Use dh-autoreconf
495+ - Enable xvfb tests
496+
497+ -- Michael Terry <mterry@ubuntu.com> Tue, 19 Feb 2013 17:10:20 +0000
498+
499 unity-lens-video (0.3.14daily12.12.05-0ubuntu1) raring; urgency=low
500
501 [ Michael Terry ]
502
503=== modified file 'debian/control'
504--- debian/control 2012-11-08 04:06:55 +0000
505+++ debian/control 2013-02-19 17:19:01 +0000
506@@ -2,28 +2,41 @@
507 Section: gnome
508 Priority: optional
509 Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
510-XSBC-Original-Maintainer: David Calle <davidc@framli.eu>
511-Build-Depends: debhelper (>= 9),
512- python,
513- python-distutils-extra,
514+Build-Depends: debhelper (>= 9),
515+ dh-autoreconf,
516 dh-translations,
517-Standards-Version: 3.9.3
518-Homepage: https://launchpad.net/unity-lens-videos
519+ gnome-common,
520+ libdee-dev (>= 1.0.7),
521+ libgee-dev,
522+ libglib2.0-dev (>= 2.27),
523+ libjson-glib-dev,
524+ libsoup-gnome2.4-dev,
525+ libunity-dev (>= 6.90.2),
526+ libzeitgeist-dev (>= 0.3.8),
527+ valac-0.18,
528+ xvfb,
529+Standards-Version: 3.9.4
530+Homepage: https://launchpad.net/unity-lens-video
531 # If you aren't a member of ~unity-team but need to upload packaging changes,
532 # just go ahead. ~unity-team will notice and sync up the code again.
533-Vcs-Bzr: https://code.launchpad.net/~unity-team/unity-lens-videos/trunk
534+Vcs-Bzr: https://code.launchpad.net/~unity-team/unity-lens-video/trunk
535
536 Package: unity-lens-video
537-Architecture: all
538+Architecture: any
539 Depends: ${misc:Depends},
540- ${python:Depends},
541- gir1.2-unity-5.0,
542- gir1.2-dee-1.0,
543- gir1.2-glib-2.0,
544- python-zeitgeist,
545+ ${shlibs:Depends},
546 unity-lens-music (>= 6.6.0),
547 Recommends: unity-scope-video-remote
548 Breaks: unity (<< 6.0.0),
549 Description: Unity Video lens
550 A plugin to search videos in the Dash.
551
552+Package: unity-scope-video-remote
553+Architecture: any
554+Depends: ${misc:Depends},
555+ ${shlibs:Depends},
556+ gvfs-bin,
557+ unity-lens-video,
558+Enhances: unity-lens-video
559+Description: Remote videos engine
560+ This scope adds a remote videos search engine to the Video lens.
561
562=== modified file 'debian/copyright'
563--- debian/copyright 2012-02-14 14:21:28 +0000
564+++ debian/copyright 2013-02-19 17:19:01 +0000
565@@ -1,15 +1,13 @@
566-Format: http://dep.debian.net/deps/dep5
567-Source: https://launchpad.net/unity-lens-videos
568-Upstream-Name: unity-lens-video
569-Upstream-Contact: David Calle <davidc@framli.eu>
570+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
571+Source: https://launchpad.net/unity-lens-video
572+Upstream-Name: Unity Video Lens
573
574 Files: *
575-Copyright: 2011 David Calle <davidc@framli.eu>
576-License: GPL-3.0+
577+Copyright: 2011-2013 Canonical Ltd
578+License: GPL-3
579 This program is free software: you can redistribute it and/or modify
580 it under the terms of the GNU General Public License as published by
581- the Free Software Foundation, either version 3 of the License, or
582- (at your option) any later version.
583+ the Free Software Foundation, version 3 of the License.
584 .
585 This program is distributed in the hope that it will be useful,
586 but WITHOUT ANY WARRANTY; without even the implied warranty of
587
588=== modified file 'debian/rules'
589--- debian/rules 2012-11-08 04:06:55 +0000
590+++ debian/rules 2013-02-19 17:19:01 +0000
591@@ -1,9 +1,14 @@
592 #!/usr/bin/make -f
593 # -*- makefile -*-
594
595-
596 %:
597- dh $@ --with python2,translations
598+ dh $@ --with autoreconf,translations
599+
600+override_dh_autoreconf:
601+ NOCONFIGURE=1 dh_autoreconf ./autogen.sh
602+
603+override_dh_auto_configure:
604+ dh_auto_configure -- --enable-headless-tests
605
606 override_dh_install:
607 dh_install --fail-missing
608
609=== added file 'debian/unity-lens-video.install'
610--- debian/unity-lens-video.install 1970-01-01 00:00:00 +0000
611+++ debian/unity-lens-video.install 2013-02-19 17:19:01 +0000
612@@ -0,0 +1,3 @@
613+/usr/lib/*/unity-lens-video/unity-video-lens-daemon
614+/usr/share/dbus-1/services/unity-lens-video.service
615+/usr/share/unity/lenses/video/video.lens
616
617=== added file 'debian/unity-scope-video-remote.install'
618--- debian/unity-scope-video-remote.install 1970-01-01 00:00:00 +0000
619+++ debian/unity-scope-video-remote.install 2013-02-19 17:19:01 +0000
620@@ -0,0 +1,3 @@
621+/usr/lib/*/unity-lens-video/unity-scope-video-remote
622+/usr/share/dbus-1/services/unity-scope-video-remote.service
623+/usr/share/unity/lenses/video/video-remote.scope
624
625=== modified file 'debian/watch'
626--- debian/watch 2012-03-21 21:41:31 +0000
627+++ debian/watch 2013-02-19 17:19:01 +0000
628@@ -1,2 +1,2 @@
629 version=3
630-http://launchpad.net/unity-lens-videos/+download .*/unity-lens-video-([0-9.]+)\.tar\.gz
631+http://launchpad.net/unity-lens-video/+download .*/unity-lens-video-([0-9.]+)\.tar\.gz
632
633=== added directory 'm4'
634=== added file 'm4/gcov.m4'
635--- m4/gcov.m4 1970-01-01 00:00:00 +0000
636+++ m4/gcov.m4 2013-02-19 17:19:01 +0000
637@@ -0,0 +1,86 @@
638+# Checks for existence of coverage tools:
639+# * gcov
640+# * lcov
641+# * genhtml
642+# * gcovr
643+#
644+# Sets ac_cv_check_gcov to yes if tooling is present
645+# and reports the executables to the variables LCOV, GCOVR and GENHTML.
646+AC_DEFUN([AC_TDD_GCOV],
647+[
648+ AC_ARG_ENABLE(gcov,
649+ AS_HELP_STRING([--enable-gcov],
650+ [enable coverage testing with gcov]),
651+ [use_gcov=$enableval], [use_gcov=no])
652+
653+ if test "x$use_gcov" = "xyes"; then
654+ # we need gcc:
655+ if test "$GCC" != "yes"; then
656+ AC_MSG_ERROR([GCC is required for --enable-gcov])
657+ fi
658+
659+ # Check if ccache is being used
660+ AC_CHECK_PROG(SHTOOL, shtool, shtool)
661+ case `$SHTOOL path $CC` in
662+ *ccache*[)] gcc_ccache=yes;;
663+ *[)] gcc_ccache=no;;
664+ esac
665+
666+ if test "$gcc_ccache" = "yes" && (test -z "$CCACHE_DISABLE" || test "$CCACHE_DISABLE" != "1"); then
667+ AC_MSG_ERROR([ccache must be disabled when --enable-gcov option is used. You can disable ccache by setting environment variable CCACHE_DISABLE=1.])
668+ fi
669+
670+ lcov_version_list="1.6 1.7 1.8 1.9"
671+ AC_CHECK_PROG(LCOV, lcov, lcov)
672+ AC_CHECK_PROG(GENHTML, genhtml, genhtml)
673+
674+ if test "$LCOV"; then
675+ AC_CACHE_CHECK([for lcov version], glib_cv_lcov_version, [
676+ glib_cv_lcov_version=invalid
677+ lcov_version=`$LCOV -v 2>/dev/null | $SED -e 's/^.* //'`
678+ for lcov_check_version in $lcov_version_list; do
679+ if test "$lcov_version" = "$lcov_check_version"; then
680+ glib_cv_lcov_version="$lcov_check_version (ok)"
681+ fi
682+ done
683+ ])
684+ else
685+ lcov_msg="To enable code coverage reporting you must have one of the following lcov versions installed: $lcov_version_list"
686+ AC_MSG_ERROR([$lcov_msg])
687+ fi
688+
689+ case $glib_cv_lcov_version in
690+ ""|invalid[)]
691+ lcov_msg="You must have one of the following versions of lcov: $lcov_version_list (found: $lcov_version)."
692+ AC_MSG_ERROR([$lcov_msg])
693+ LCOV="exit 0;"
694+ ;;
695+ esac
696+
697+ if test -z "$GENHTML"; then
698+ AC_MSG_ERROR([Could not find genhtml from the lcov package])
699+ fi
700+
701+ ac_cv_check_gcov=yes
702+ ac_cv_check_lcov=yes
703+
704+ # Remove all optimization flags from CFLAGS
705+ changequote({,})
706+ CFLAGS=`echo "$CFLAGS" | $SED -e 's/-O[0-9]*//g'`
707+ changequote([,])
708+
709+ # Add the special gcc flags
710+ COVERAGE_CFLAGS="-O0 -fprofile-arcs -ftest-coverage"
711+ COVERAGE_CXXFLAGS="-O0 -fprofile-arcs -ftest-coverage"
712+ COVERAGE_LDFLAGS="-lgcov"
713+
714+ # Check availability of gcovr
715+ AC_CHECK_PROG(GCOVR, gcovr, gcovr)
716+ if test -z "$GCOVR"; then
717+ ac_cv_check_gcovr=no
718+ else
719+ ac_cv_check_gcovr=yes
720+ fi
721+
722+fi
723+]) # AC_TDD_GCOV
724
725=== modified file 'po/POTFILES.in'
726--- po/POTFILES.in 2012-02-22 10:47:03 +0000
727+++ po/POTFILES.in 2013-02-19 17:19:01 +0000
728@@ -1,3 +1,6 @@
729 [encoding: UTF-8]
730-src/unity-lens-video
731-[type: gettext/ini]video.lens.in
732+src/main.vala
733+src/remote-video-main.vala
734+src/ubuntu-video-search.vala
735+tests/unit/test-ubuntu-video-search.vala
736+[type: gettext/ini]data/video.lens.in
737
738=== added file 'po/POTFILES.skip'
739--- po/POTFILES.skip 1970-01-01 00:00:00 +0000
740+++ po/POTFILES.skip 2013-02-19 17:19:01 +0000
741@@ -0,0 +1,7 @@
742+src/daemon.c
743+src/scope.c
744+src/remote-scope.c
745+src/ubuntu-video-search.c
746+tests/unit/test-ubuntu-video-search.c
747+tests/unit/ubuntu-video-search.c
748+tests/unit/remote-scope.c
749
750=== removed file 'po/unity-lens-video.pot'
751--- po/unity-lens-video.pot 2012-09-14 09:12:09 +0000
752+++ po/unity-lens-video.pot 1970-01-01 00:00:00 +0000
753@@ -1,70 +0,0 @@
754-# SOME DESCRIPTIVE TITLE.
755-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
756-# This file is distributed under the same license as the PACKAGE package.
757-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
758-#
759-#, fuzzy
760-msgid ""
761-msgstr ""
762-"Project-Id-Version: PACKAGE VERSION\n"
763-"Report-Msgid-Bugs-To: \n"
764-"POT-Creation-Date: 2012-09-14 10:03+0100\n"
765-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
766-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
767-"Language-Team: LANGUAGE <LL@li.org>\n"
768-"Language: \n"
769-"MIME-Version: 1.0\n"
770-"Content-Type: text/plain; charset=CHARSET\n"
771-"Content-Transfer-Encoding: 8bit\n"
772-
773-#: ../src/unity-lens-video:49 ../src/unity-lens-video:56
774-msgid "My Videos"
775-msgstr ""
776-
777-#: ../src/unity-lens-video:50
778-msgid "Online"
779-msgstr ""
780-
781-#: ../src/unity-lens-video:51 ../video.lens.in.h:1
782-msgid "Videos"
783-msgstr ""
784-
785-#: ../src/unity-lens-video:52
786-msgid "Recently Viewed"
787-msgstr ""
788-
789-#: ../src/unity-lens-video:53
790-msgid "More suggestions"
791-msgstr ""
792-
793-#: ../src/unity-lens-video:54
794-msgid "Search Videos"
795-msgstr ""
796-
797-#: ../src/unity-lens-video:55
798-msgid "Sources"
799-msgstr ""
800-
801-#: ../src/unity-lens-video:173
802-msgid "Format"
803-msgstr ""
804-
805-#: ../src/unity-lens-video:174
806-msgid "Dimensions"
807-msgstr ""
808-
809-#: ../src/unity-lens-video:175
810-msgid "Size"
811-msgstr ""
812-
813-#: ../src/unity-lens-video:178
814-msgid "Show in Folder"
815-msgstr ""
816-
817-#: ../src/unity-lens-video:181
818-msgid "Play"
819-msgstr ""
820-
821-#: ../video.lens.in.h:2
822-msgid "Search local videos"
823-msgstr ""
824
825=== removed file 'setup.cfg'
826--- setup.cfg 2012-02-22 10:48:53 +0000
827+++ setup.cfg 1970-01-01 00:00:00 +0000
828@@ -1,10 +0,0 @@
829-[build]
830-i18n=True
831-
832-[build_i18n]
833-domain=unity-lens-video
834-desktop_files=[ ("share/unity/lenses/video",
835- ("video.lens.in",)
836- )
837- ]
838-
839
840=== removed file 'setup.py'
841--- setup.py 2012-10-16 12:12:46 +0000
842+++ setup.py 1970-01-01 00:00:00 +0000
843@@ -1,18 +0,0 @@
844-#!/usr/bin/env python
845-#
846-from distutils.core import setup
847-from DistUtilsExtra.command import *
848-
849-setup(name="unity-lens-video",
850- version="0.3.14",
851- author="David Calle",
852- author_email="davidc@framli.eu",
853- url="http://launchpad.net/~davidc3",
854- license="GNU General Public License (GPL)",
855- data_files=[
856- ('lib/unity-lens-video', ['src/unity-lens-video']),
857- ('share/dbus-1/services', ['unity-lens-video.service']),
858- ('share/applications', ['unity-lens-video.desktop']),
859- ('share/pixmaps', ['unity-lens-video.png']),
860- ], cmdclass={"build": build_extra.build_extra,
861- "build_i18n": build_i18n.build_i18n,})
862
863=== added file 'src/Makefile.am'
864--- src/Makefile.am 1970-01-01 00:00:00 +0000
865+++ src/Makefile.am 2013-02-19 17:19:01 +0000
866@@ -0,0 +1,133 @@
867+NULL =
868+BUILT_SOURCES =
869+CLEANFILES =
870+EXTRA_DIST =
871+
872+DATADIR = $(datadir)
873+
874+AM_CPPFLAGS = $(COVERAGE_CFLAGS)
875+AM_LDFLAGS = $(COVERAGE_LDFLAGS)
876+
877+pkglibexec_PROGRAMS = \
878+ unity-video-lens-daemon \
879+ unity-scope-video-remote \
880+ $(NULL)
881+
882+unity_video_lens_daemon_CPPFLAGS = \
883+ -DDATADIR=\"$(DATADIR)\" \
884+ -DPKGDATADIR=\"$(PKGDATADIR)\" \
885+ -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
886+ -DG_LOG_DOMAIN=\"unity-video-lens-daemon\" \
887+ $(LENS_DAEMON_CFLAGS) \
888+ $(MAINTAINER_CFLAGS) \
889+ -I$(srcdir) \
890+ $(NULL)
891+
892+unity_scope_video_remote_CPPFLAGS = \
893+ -DDATADIR=\"$(DATADIR)\" \
894+ -DPKGDATADIR=\"$(PKGDATADIR)\" \
895+ -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
896+ -DG_LOG_DOMAIN=\"unity-scope-video-remote\" \
897+ $(LENS_DAEMON_CFLAGS) \
898+ $(MAINTAINER_CFLAGS) \
899+ -I$(srcdir) \
900+ $(NULL)
901+
902+unity_video_lens_daemon_VALAFLAGS = \
903+ -C \
904+ --pkg dee-1.0 \
905+ --pkg unity \
906+ --pkg unity-extras \
907+ --pkg gio-2.0 \
908+ --pkg gio-unix-2.0 \
909+ --pkg glib-2.0 \
910+ --pkg zeitgeist-1.0 \
911+ --vapidir $(srcdir) \
912+ --vapidir $(top_srcdir)/vapi \
913+ --target-glib=2.26 \
914+ $(MAINTAINER_VALAFLAGS) \
915+ $(NULL)
916+
917+unity_scope_video_remote_VALAFLAGS = \
918+ -C \
919+ --pkg dee-1.0 \
920+ --pkg unity \
921+ --pkg unity-extras \
922+ --pkg gio-2.0 \
923+ --pkg gio-unix-2.0 \
924+ --pkg glib-2.0 \
925+ --pkg zeitgeist-1.0 \
926+ --pkg libsoup-gnome-2.4 \
927+ --pkg libsoup-2.4 \
928+ --pkg json-glib-1.0 \
929+ --vapidir $(srcdir) \
930+ --vapidir $(top_srcdir)/vapi \
931+ --target-glib=2.26 \
932+ $(MAINTAINER_VALAFLAGS) \
933+ $(NULL)
934+
935+
936+unity_video_lens_daemon_LDADD = \
937+ $(LENS_DAEMON_LIBS) \
938+ $(NULL)
939+
940+unity_scope_video_remote_LDADD = \
941+ $(LENS_DAEMON_LIBS) \
942+ $(NULL)
943+
944+unity_video_lens_daemon_VALASOURCES = \
945+ blacklist-tracker.vala \
946+ daemon.vala \
947+ main.vala \
948+ locate.vala \
949+ scope.vala \
950+ config.vala \
951+ thumbnailer.vala \
952+ utils.vala \
953+ video-file.vala \
954+ $(NULL)
955+
956+unity_scope_video_remote_VALASOURCES = \
957+ config.vala \
958+ remote-scope-globals.vala \
959+ remote-video-main.vala \
960+ remote-scope.vala \
961+ remote-uri.vala \
962+ ubuntu-video-search.vala \
963+ utils.vala \
964+ video-file.vala \
965+ $(NULL)
966+
967+unity_video_lens_daemon_SOURCES = \
968+ $(unity_video_lens_daemon_VALASOURCES:.vala=.c) \
969+ $(NULL)
970+
971+unity_scope_video_remote_SOURCES = \
972+ $(unity_scope_video_remote_VALASOURCES:.vala=.c) \
973+ $(NULL)
974+
975+BUILT_SOURCES += \
976+ unity_video_lens_daemon.vala.stamp \
977+ unity_scope_video_remote.vala.stamp \
978+ $(NULL)
979+
980+EXTRA_DIST += \
981+ unity_video_lens_daemon.vala.stamp \
982+ unity_scope_video_remote.vala.stamp \
983+ $(unity_video_lens_daemon_VALASOURCES) \
984+ $(unity_scope_video_remote_VALASOURCES) \
985+ $(NULL)
986+
987+unity_video_lens_daemon.vala.stamp: $(unity_video_lens_daemon_VALASOURCES)
988+ $(AM_V_GEN) $(VALAC) $(unity_video_lens_daemon_VALAFLAGS) $^
989+ @touch $@
990+
991+unity_scope_video_remote.vala.stamp: $(unity_scope_video_remote_VALASOURCES)
992+ $(AM_V_GEN) $(VALAC) $(unity_scope_video_remote_VALAFLAGS) $^
993+ @touch $@
994+
995+CLEANFILES += \
996+ *.stamp \
997+ $(unity_video_lens_daemon_VALASOURCES:.vala=.c) \
998+ $(unity_scope_video_remote_VALASOURCES:.vala=.c) \
999+ $(NULL)
1000
1001=== added file 'src/blacklist-tracker.vala'
1002--- src/blacklist-tracker.vala 1970-01-01 00:00:00 +0000
1003+++ src/blacklist-tracker.vala 2013-02-19 17:19:01 +0000
1004@@ -0,0 +1,128 @@
1005+/*
1006+ * Copyright (C) 2012 Canonical Ltd
1007+ *
1008+ * This program is free software: you can redistribute it and/or modify
1009+ * it under the terms of the GNU General Public License version 3 as
1010+ * published by the Free Software Foundation.
1011+ *
1012+ * This program is distributed in the hope that it will be useful,
1013+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1014+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1015+ * GNU General Public License for more details.
1016+ *
1017+ * You should have received a copy of the GNU General Public License
1018+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1019+ *
1020+ * Authored by Michal Hruby <michal.hruby@canonical.com>
1021+ *
1022+ */
1023+using Zeitgeist;
1024+using Config;
1025+using Gee;
1026+
1027+namespace Unity.VideoLens
1028+{
1029+ /* Libzeitgeist currently doesn't expose any blacklist API, so we need to
1030+ * use direct dbus calls */
1031+ [DBus (name = "org.gnome.zeitgeist.Blacklist")]
1032+ public interface Blacklist : Object
1033+ {
1034+ [DBus (signature = "a{s(asaasay)}")]
1035+ public abstract async Variant get_templates () throws Error;
1036+ public signal void template_added (string template_id,
1037+ [DBus (signature = "(asassay)")] Variant template);
1038+ public signal void template_removed (string template_id,
1039+ [DBus (signature = "(asassay)")] Variant template);
1040+ }
1041+
1042+ public class BlacklistTracker : Object
1043+ {
1044+ private HashTable<string, Event> event_templates;
1045+ private Blacklist blacklist_proxy;
1046+
1047+ private Set<string> blacklisted_uris;
1048+ private bool cached_uris_dirty;
1049+
1050+ construct
1051+ {
1052+ event_templates = new HashTable<string, Event> (str_hash, str_equal);
1053+ blacklisted_uris = new HashSet<string> ();
1054+
1055+ Bus.get_proxy<Blacklist> (BusType.SESSION, "org.gnome.zeitgeist.Engine",
1056+ "/org/gnome/zeitgeist/blacklist", 0, null, (src, res) =>
1057+ {
1058+ try
1059+ {
1060+ blacklist_proxy = (src as DBusConnection).get_proxy<Blacklist>.end (res);
1061+ fetch_blacklists ();
1062+ }
1063+ catch (GLib.Error err)
1064+ {
1065+ warning ("%s", err.message);
1066+ }
1067+ });
1068+ }
1069+
1070+ private async void fetch_blacklists ()
1071+ {
1072+ blacklist_proxy.template_added.connect (this.template_added);
1073+ blacklist_proxy.template_removed.connect (this.template_removed);
1074+ Variant all_templates = yield blacklist_proxy.get_templates ();
1075+
1076+ VariantIter iter = new VariantIter (all_templates);
1077+ string template_id;
1078+ Variant event_variant;
1079+ while (iter.next ("{s@(asaasay)}", out template_id, out event_variant))
1080+ {
1081+ Event e = new Event.from_variant (event_variant);
1082+ event_templates[template_id] = e;
1083+ }
1084+ cached_uris_dirty = true;
1085+ }
1086+
1087+ private void template_added (string id, Variant template)
1088+ {
1089+ Event e = new Event.from_variant (template);
1090+ event_templates[id] = e;
1091+ cached_uris_dirty = true;
1092+ }
1093+
1094+ private void template_removed (string id, Variant template)
1095+ {
1096+ event_templates.remove (id);
1097+ cached_uris_dirty = true;
1098+ }
1099+
1100+ private void update_uris ()
1101+ {
1102+ var iter = HashTableIter<string, Event> (event_templates);
1103+ unowned Event e;
1104+ while (iter.next (null, out e))
1105+ {
1106+ if (e.num_subjects () > 0)
1107+ {
1108+ unowned Subject s = e.get_subject (0);
1109+ unowned string uri = s.get_uri ();
1110+ if (uri == null || uri == "") continue;
1111+
1112+ if (uri.has_suffix ("*"))
1113+ {
1114+ blacklisted_uris.add (uri.substring (0, uri.length - 1));
1115+ }
1116+ }
1117+ }
1118+ }
1119+
1120+ public unowned Set<string> get_blacklisted_uris ()
1121+ {
1122+ if (cached_uris_dirty)
1123+ {
1124+ blacklisted_uris.clear ();
1125+ update_uris ();
1126+ cached_uris_dirty = false;
1127+ }
1128+ return blacklisted_uris;
1129+ }
1130+ }
1131+
1132+} /* namespace */
1133
1134=== added file 'src/config.vala.in'
1135--- src/config.vala.in 1970-01-01 00:00:00 +0000
1136+++ src/config.vala.in 2013-02-19 17:19:01 +0000
1137@@ -0,0 +1,16 @@
1138+namespace Config {
1139+
1140+ const string PREFIX = "@prefix@";
1141+
1142+ const string DATADIR = "@DATADIR@";
1143+
1144+ const string PKGDATADIR = "@DATADIR@/unity";
1145+
1146+ const string BINDIR = "@prefix@/bin";
1147+
1148+ const string LOCALEDIR = "@DATADIR@/locale";
1149+
1150+ const string PACKAGE = "@PACKAGE@";
1151+
1152+ const string VERSION = "@VERSION@";
1153+}
1154
1155=== added file 'src/daemon.vala'
1156--- src/daemon.vala 1970-01-01 00:00:00 +0000
1157+++ src/daemon.vala 2013-02-19 17:19:01 +0000
1158@@ -0,0 +1,79 @@
1159+/*
1160+ * Copyright (C) 2012 Canonical Ltd
1161+ *
1162+ * This program is free software: you can redistribute it and/or modify
1163+ * it under the terms of the GNU General Public License version 3 as
1164+ * published by the Free Software Foundation.
1165+ *
1166+ * This program is distributed in the hope that it will be useful,
1167+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1168+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1169+ * GNU General Public License for more details.
1170+ *
1171+ * You should have received a copy of the GNU General Public License
1172+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1173+ *
1174+ * Authored by Pawel Stolowski <pawel.stolowski@canonical.com>
1175+ * based on python code by David Calle <davidc@framli.eu>
1176+ */
1177+using Config;
1178+
1179+namespace Unity.VideoLens {
1180+
1181+ const string ICON_PATH = Config.DATADIR + "/icons/unity-icon-theme/places/svg/";
1182+
1183+ public class Daemon : GLib.Object
1184+ {
1185+ private Unity.Lens lens;
1186+
1187+ construct
1188+ {
1189+ lens = new Unity.Lens("/net/launchpad/lens/video", "video");
1190+ lens.search_in_global = true;
1191+ lens.search_hint = _("Search videos");
1192+ lens.sources_display_name = _("Sources");
1193+ lens.visible = true;
1194+
1195+ populate_categories ();
1196+ populate_filters ();
1197+
1198+ var video_scope = new VideoScope ();
1199+ lens.add_local_scope (video_scope);
1200+
1201+ try {
1202+ lens.export ();
1203+ } catch (GLib.IOError e) {
1204+ stdout.printf ("error %s\n", e.message);
1205+ }
1206+ }
1207+
1208+ private void populate_filters ()
1209+ {
1210+ var filters = new GLib.List<Unity.Filter> ();
1211+
1212+ /* TODO */
1213+
1214+ /* A filter */
1215+ {
1216+ }
1217+
1218+ /* Another filter */
1219+ {
1220+ }
1221+
1222+ lens.filters = filters;
1223+ }
1224+
1225+ private void populate_categories ()
1226+ {
1227+ var categories = new GLib.List<Unity.Category> ();
1228+ var icon_dir = File.new_for_path (ICON_PATH);
1229+
1230+ categories.append(new Unity.Category (_("My Videos"), new FileIcon (icon_dir.get_child ("group-videos.svg")), Unity.CategoryRenderer.VERTICAL_TILE));
1231+ categories.append(new Unity.Category (_("Online"), new FileIcon (icon_dir.get_child ("group-internet.svg")), Unity.CategoryRenderer.VERTICAL_TILE));
1232+ categories.append(new Unity.Category (_("More suggestions"), new FileIcon (icon_dir.get_child ("group-treat-yourself.svg")), Unity.CategoryRenderer.VERTICAL_TILE));
1233+
1234+ lens.categories = categories;
1235+ }
1236+ }
1237+}
1238
1239=== added file 'src/locate.vala'
1240--- src/locate.vala 1970-01-01 00:00:00 +0000
1241+++ src/locate.vala 2013-02-19 17:19:01 +0000
1242@@ -0,0 +1,112 @@
1243+/*
1244+ * Copyright (C) 2012 Canonical Ltd
1245+ *
1246+ * This program is free software: you can redistribute it and/or modify
1247+ * it under the terms of the GNU General Public License version 3 as
1248+ * published by the Free Software Foundation.
1249+ *
1250+ * This program is distributed in the hope that it will be useful,
1251+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1252+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1253+ * GNU General Public License for more details.
1254+ *
1255+ * You should have received a copy of the GNU General Public License
1256+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1257+ *
1258+ * Authored by Pawel Stolowski <pawel.stolowski@canonical.com>
1259+ *
1260+ */
1261+
1262+namespace Unity.VideoLens
1263+{
1264+ public class Locate
1265+ {
1266+ /* Filter used by locate handling loop; return value of false means file will be ignored */
1267+ public delegate bool LocateFilter (string path);
1268+
1269+ private static const int MAX_RESULTS = 100;
1270+ private static const int MAX_LOCATE_RESULTS = 200;
1271+
1272+ private string cache_db;
1273+ private string videos_folder;
1274+ public string locate_bin { get; set; default = "locate"; }
1275+ public string updatedb_bin { get; set; default = "updatedb"; }
1276+
1277+ public Locate (string cache_dir, string videos_dir)
1278+ {
1279+ cache_db = cache_dir + "/videos.db";
1280+ videos_folder = videos_dir;
1281+ }
1282+
1283+ public Gee.ArrayList<VideoFile?>? run_locate (string search_string, Thumbnailer thumbnailer, LocateFilter? filter = null)
1284+ {
1285+ if (Utils.is_regular_file (cache_db))
1286+ {
1287+ string stdout;
1288+ try
1289+ {
1290+ string query = locate_query_string (search_string);
1291+ GLib.Process.spawn_command_line_sync (@"$locate_bin -l $MAX_LOCATE_RESULTS -id $cache_db $query", out stdout);
1292+ var result_list = parse_locate_results (stdout, 100, thumbnailer, filter);
1293+ return result_list;
1294+ }
1295+ catch (GLib.Error e)
1296+ {
1297+ warning ("Failed to run locate: %s", e.message);
1298+ }
1299+ }
1300+ return null;
1301+ }
1302+
1303+ internal string locate_query_string (string search_string)
1304+ {
1305+ return videos_folder + "*" + search_string.replace (" ", "*") + "*";
1306+ }
1307+
1308+ public void updatedb ()
1309+ {
1310+ try
1311+ {
1312+ GLib.Process.spawn_command_line_async (@"$updatedb_bin -o $cache_db -l 0 -U $videos_folder");
1313+ }
1314+ catch (GLib.Error e)
1315+ {
1316+ warning ("Can't create database, will retry: %s", e.message);
1317+ }
1318+ }
1319+
1320+ public Gee.ArrayList<VideoFile?> parse_locate_results (string locate_output, int max, Thumbnailer thumbnailer, LocateFilter? filter)
1321+ {
1322+ var results_list = locate_output.split ("\n");
1323+ var res = new Gee.ArrayList<VideoFile?> ();
1324+ int video_count = 0;
1325+ foreach (var video in results_list)
1326+ {
1327+ if (video_count >= MAX_RESULTS)
1328+ break;
1329+
1330+ try
1331+ {
1332+ if (Utils.is_video (video) && (filter == null || filter (video)))
1333+ {
1334+ video_count++;
1335+ var name = Utils.get_name (video);
1336+ VideoFile video_file = VideoFile ()
1337+ {
1338+ title = name,
1339+ lc_title = name.down (),
1340+ uri = "file://" + video,
1341+ icon = thumbnailer.get_icon (video)
1342+ };
1343+ res.add (video_file);
1344+ }
1345+ }
1346+ catch (Error e)
1347+ {
1348+ // silently ignore
1349+ }
1350+ }
1351+ return res;
1352+ }
1353+ }
1354+}
1355\ No newline at end of file
1356
1357=== added file 'src/main.vala'
1358--- src/main.vala 1970-01-01 00:00:00 +0000
1359+++ src/main.vala 2013-02-19 17:19:01 +0000
1360@@ -0,0 +1,59 @@
1361+/*
1362+ * Copyright (C) 2012 Canonical Ltd
1363+ *
1364+ * This program is free software: you can redistribute it and/or modify
1365+ * it under the terms of the GNU General Public License version 3 as
1366+ * published by the Free Software Foundation.
1367+ *
1368+ * This program is distributed in the hope that it will be useful,
1369+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1370+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1371+ * GNU General Public License for more details.
1372+ *
1373+ * You should have received a copy of the GNU General Public License
1374+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1375+ */
1376+
1377+using GLib;
1378+using Config;
1379+
1380+namespace Unity.VideoLens {
1381+
1382+ static Application? app = null;
1383+ static Daemon? daemon = null;
1384+
1385+ public static int main (string[] args)
1386+ {
1387+ GLib.Environment.set_prgname ("unity-video-lens");
1388+
1389+ /* Sort up locale to get translations but also sorting and
1390+ * punctuation right */
1391+ GLib.Intl.textdomain (Config.PACKAGE);
1392+ GLib.Intl.bindtextdomain (Config.PACKAGE, Config.LOCALEDIR);
1393+ GLib.Intl.bind_textdomain_codeset (Config.PACKAGE, "UTF-8");
1394+ GLib.Intl.setlocale(GLib.LocaleCategory.ALL, "");
1395+
1396+ try
1397+ {
1398+ app = Extras.dbus_own_name ("net.launchpad.lens.video", () =>
1399+ {
1400+ daemon = new Daemon ();
1401+ });
1402+ }
1403+ catch (Error e)
1404+ {
1405+ warning ("Failed to start video lens daemon: %s\n", e.message);
1406+ return 1;
1407+ }
1408+
1409+ if (app == null)
1410+ {
1411+ warning ("Another instance of the Unity Videos Lens " +
1412+ "already appears to be running.\nBailing out.\n");
1413+ return 2;
1414+ }
1415+
1416+ return app.run ();
1417+ }
1418+
1419+} /* namespace */
1420
1421=== added file 'src/remote-scope-globals.vala'
1422--- src/remote-scope-globals.vala 1970-01-01 00:00:00 +0000
1423+++ src/remote-scope-globals.vala 2013-02-19 17:19:01 +0000
1424@@ -0,0 +1,25 @@
1425+
1426+/*
1427+ * Copyright (C) 2012 Canonical Ltd
1428+ *
1429+ * This program is free software: you can redistribute it and/or modify
1430+ * it under the terms of the GNU General Public License version 3 as
1431+ * published by the Free Software Foundation.
1432+ *
1433+ * This program is distributed in the hope that it will be useful,
1434+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1435+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1436+ * GNU General Public License for more details.
1437+ *
1438+ * You should have received a copy of the GNU General Public License
1439+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1440+ *
1441+ * Authored by Pawel Stolowski <pawel.stolowski@canonical.com>
1442+ *
1443+ */
1444+
1445+namespace Unity.VideoLens
1446+{
1447+ static int CAT_INDEX_ONLINE = 1;
1448+ static int CAT_INDEX_MORE = 2;
1449+}
1450\ No newline at end of file
1451
1452=== added file 'src/remote-scope.vala'
1453--- src/remote-scope.vala 1970-01-01 00:00:00 +0000
1454+++ src/remote-scope.vala 2013-02-19 17:19:01 +0000
1455@@ -0,0 +1,491 @@
1456+/*
1457+ * Copyright (C) 2012 Canonical Ltd
1458+ *
1459+ * This program is free software: you can redistribute it and/or modify
1460+ * it under the terms of the GNU General Public License version 3 as
1461+ * published by the Free Software Foundation.
1462+ *
1463+ * This program is distributed in the hope that it will be useful,
1464+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1465+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1466+ * GNU General Public License for more details.
1467+ *
1468+ * You should have received a copy of the GNU General Public License
1469+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1470+ *
1471+ * Authored by Pawel Stolowski <pawel.stolowski@canonical.com>
1472+ * based on python code by David Calle <davidc@framli.eu>
1473+ *
1474+ */
1475+
1476+namespace Unity.VideoLens
1477+{
1478+ public class RemoteVideoScope : Unity.Scope
1479+ {
1480+ private static int REFRESH_INTERVAL = 3600; // fetch sources & recommendations once an hour
1481+ private static int RETRY_INTERVAL = 60; // retry sources/recommendations after a minute
1482+
1483+ private Soup.Session session;
1484+ private PreferencesManager preferences = PreferencesManager.get_default ();
1485+ Gee.ArrayList<RemoteVideoFile?> recommendations;
1486+ int64 recommendations_last_update = 0;
1487+ Zeitgeist.DataSourceRegistry zg_sources;
1488+ bool use_zeitgeist;
1489+
1490+ public RemoteVideoScope ()
1491+ {
1492+ Object (dbus_path: "/net/launchpad/scope/remotevideos");
1493+ }
1494+
1495+ protected override void constructed ()
1496+ {
1497+ recommendations = new Gee.ArrayList<RemoteVideoFile?> ();
1498+
1499+ use_zeitgeist = false;
1500+ try
1501+ {
1502+ zeitgeist_init ();
1503+ use_zeitgeist = true;
1504+ }
1505+ catch (Error e)
1506+ {
1507+ warning ("Failed to initialize Zeitgeist, won't store events");
1508+ }
1509+
1510+ session = new Soup.SessionAsync ();
1511+ session.ssl_use_system_ca_file = true;
1512+ session.ssl_strict = true;
1513+ session.user_agent = "Unity Video Lens Remote Scope v" + Config.VERSION;
1514+ session.add_feature_by_type (typeof (SoupGNOME.ProxyResolverGNOME));
1515+
1516+ search_in_global = false;
1517+ search_changed.connect ((search, search_type, cancellable) =>
1518+ {
1519+ update_search_async.begin (search, search_type, cancellable);
1520+ });
1521+ filters_changed.connect (on_filters_changed);
1522+ sources.notify["filtering"].connect (on_filters_changed);
1523+
1524+ generate_search_key.connect ((scope, search) =>
1525+ {
1526+ return search.search_string.strip ();
1527+ });
1528+
1529+ preferences.notify["remote-content-search"].connect ((obj, pspec) =>
1530+ {
1531+ queue_search_changed (SearchType.DEFAULT);
1532+ });
1533+
1534+ activate_uri.connect (on_activate_uri);
1535+ preview_uri.connect (on_preview_uri);
1536+
1537+ query_list_of_sources ();
1538+
1539+ // refresh the at least once every 30 minutes
1540+ GLib.Timeout.add_seconds (REFRESH_INTERVAL/2, () =>
1541+ {
1542+ queue_search_changed (SearchType.DEFAULT); return true;
1543+ });
1544+
1545+ try
1546+ {
1547+ export ();
1548+ }
1549+ catch (Error e)
1550+ {
1551+ error ("Failed to export scope: %s", e.message);
1552+ }
1553+ }
1554+
1555+ /* Query the server for a list of sources that will be used
1556+ * to build sources filter options and search queries.
1557+ */
1558+ private void query_list_of_sources ()
1559+ {
1560+ var msg = new Soup.Message ("GET", UbuntuVideoSearch.sources_uri ());
1561+ session.queue_message (msg, sources_cb);
1562+ }
1563+
1564+ private Gee.ArrayList<RemoteVideoFile?>? handle_search_response (Soup.Message msg, bool is_treat_yourself = false)
1565+ {
1566+ if (msg.status_code != 200)
1567+ {
1568+ warning ("Unable to get results from the server: %u, %s", msg.status_code, msg.reason_phrase);
1569+ }
1570+ else
1571+ {
1572+ try
1573+ {
1574+ return UbuntuVideoSearch.process_search_results ((string)msg.response_body.data, is_treat_yourself);
1575+ }
1576+ catch (Error e)
1577+ {
1578+ warning ("Error processing search results: %s", e.message);
1579+ }
1580+ }
1581+ return null;
1582+ }
1583+
1584+ private void sources_cb (Soup.Session session, Soup.Message msg)
1585+ {
1586+ uint interval = RETRY_INTERVAL;
1587+ if (msg.status_code != 200)
1588+ {
1589+ warning ("Unable to query the server for a list of sources, %u: %s", msg.status_code, msg.reason_phrase);
1590+ }
1591+ else
1592+ {
1593+ try
1594+ {
1595+ var sources_array = UbuntuVideoSearch.process_sources_results ((string) msg.response_body.data);
1596+
1597+ // remove all existing sources
1598+ foreach (var opt in sources.options)
1599+ {
1600+ sources.remove_option (opt.id);
1601+ }
1602+
1603+ // add sources
1604+ foreach (var src in sources_array)
1605+ {
1606+ sources.add_option (src, src, null);
1607+ }
1608+ interval = REFRESH_INTERVAL;
1609+ }
1610+ catch (Error e)
1611+ {
1612+ warning ("Got invalid json from the server");
1613+ }
1614+ }
1615+ GLib.Timeout.add_seconds (interval, () =>
1616+ {
1617+ query_list_of_sources (); return false;
1618+ });
1619+ }
1620+
1621+ private Unity.ActivationResponse on_activate_uri (string rawuri)
1622+ {
1623+ var fakeuri = RemoteUri.from_rawuri (rawuri);
1624+ if (fakeuri != null)
1625+ {
1626+ if (use_zeitgeist)
1627+ zeitgeist_insert_event (fakeuri.uri, fakeuri.title, fakeuri.icon);
1628+ try
1629+ {
1630+ GLib.AppInfo.launch_default_for_uri (fakeuri.uri, null);
1631+ return new Unity.ActivationResponse (Unity.HandledType.HIDE_DASH);
1632+ }
1633+ catch (GLib.Error e)
1634+ {
1635+ warning ("Failed to launch default application for '%s': %s", fakeuri.uri, e.message);
1636+ }
1637+ }
1638+ else
1639+ {
1640+ warning ("Invalid raw uri: '%s'", rawuri);
1641+ }
1642+ return new Unity.ActivationResponse (Unity.HandledType.NOT_HANDLED);
1643+ }
1644+
1645+ private Unity.ActivationResponse on_play_video (string rawuri)
1646+ {
1647+ return on_activate_uri (rawuri);
1648+ }
1649+
1650+ private Unity.Preview? build_preview (RemoteUri uri, RemoteVideoDetails? details)
1651+ {
1652+ string title = uri.title;
1653+ string subtitle = "";
1654+ string description = "";
1655+
1656+ if (details != null)
1657+ {
1658+ title = details.title;
1659+ description = details.description;
1660+
1661+ if (details.release_date != null && details.release_date != "")
1662+ subtitle = details.release_date;
1663+
1664+ if (details.duration > 0)
1665+ {
1666+ string duration = ngettext ("%d min", "%d mins", details.duration).printf (details.duration);
1667+ if (subtitle != "")
1668+ subtitle += ", " + duration;
1669+ else
1670+ subtitle = duration;
1671+ }
1672+ }
1673+
1674+ GLib.Icon thumbnail = new GLib.FileIcon (GLib.File.new_for_uri (details != null ? details.image : uri.icon));
1675+
1676+ var real_preview = new Unity.MoviePreview (title, subtitle, description, thumbnail);
1677+ var play_video = new Unity.PreviewAction ("play", _("Play"), null);
1678+ play_video.activated.connect (on_play_video);
1679+ real_preview.add_action (play_video);
1680+
1681+ // For now, rating == -1 and num_ratings == 0 hides the rating widget from the preview
1682+ real_preview.set_rating (-1, 0);
1683+
1684+ if (details != null)
1685+ {
1686+ //TODO: For details of future source types, factor out common detail key/value pairs
1687+ if (details.directors.length > 0)
1688+ real_preview.add_info (new Unity.InfoHint ("directors", ngettext ("Director", "Directors", details.directors.length), null, string.joinv (", ", details.directors)));
1689+
1690+ if (details.starring != null && details.starring != "")
1691+ real_preview.add_info (new Unity.InfoHint ("cast", _("Cast"), null, details.starring));
1692+
1693+ if (details.genres != null && details.genres.length > 0)
1694+ real_preview.add_info (new Unity.InfoHint ("genres", ngettext("Genre", "Genres", details.genres.length), null, string.joinv (", ", details.genres)));
1695+
1696+ // TODO: Add Vimeo & YouTube details for v1 of JSON API
1697+ if (details.uploaded_by != null && details.uploaded_by != "")
1698+ real_preview.add_info (new Unity.InfoHint ("uploaded-by", _("Uploaded by"), null, details.uploaded_by));
1699+
1700+ if (details.date_uploaded != null && details.date_uploaded != "")
1701+ real_preview.add_info (new Unity.InfoHint ("uploaded-on", _("Uploaded on"), null, details.date_uploaded));
1702+ }
1703+
1704+ return real_preview;
1705+ }
1706+
1707+ private Unity.Preview? on_preview_uri (string rawuri)
1708+ {
1709+ var fakeuri = RemoteUri.from_rawuri (rawuri);
1710+ if (fakeuri != null)
1711+ {
1712+ if (fakeuri.details_uri != null && fakeuri.details_uri != "")
1713+ {
1714+ var preview = new AsyncPreview ();
1715+ get_details.begin (fakeuri.details_uri, (obj, res) =>
1716+ {
1717+ try
1718+ {
1719+ var details = get_details.end (res);
1720+ preview.preview_ready (build_preview (fakeuri, details));
1721+ }
1722+ catch (Error e)
1723+ {
1724+ warning ("Failed to fetch video details: %s", e.message);
1725+ preview.preview_ready (build_preview (fakeuri, null));
1726+ }
1727+ });
1728+ return preview;
1729+ }
1730+ else
1731+ {
1732+ return build_preview (fakeuri, null);
1733+ }
1734+ }
1735+ else
1736+ {
1737+ warning ("Invalid raw uri: '%s'", rawuri);
1738+ }
1739+
1740+ return null;
1741+ }
1742+
1743+ private async RemoteVideoDetails? get_details (string url) throws Error
1744+ {
1745+ var msg = new Soup.Message ("GET", url);
1746+ session.queue_message (msg, (session_, msg_) =>
1747+ {
1748+ msg = msg_;
1749+ get_details.callback ();
1750+ });
1751+
1752+ yield;
1753+
1754+ if (msg.status_code != 200)
1755+ {
1756+ warning ("Unable to get details from the server: %u, %s", msg.status_code, msg.reason_phrase);
1757+ return null;
1758+ }
1759+ else
1760+ {
1761+ var details = UbuntuVideoSearch.process_details_results ((string) msg.response_body.data);
1762+ return details;
1763+ }
1764+ }
1765+
1766+ private async void update_search_async (LensSearch search, SearchType search_type, Cancellable? cancellable)
1767+ {
1768+ var search_string = search.search_string.strip ();
1769+ debug ("Remote search string changed to: %s", search_string);
1770+
1771+ var model = search.results_model;
1772+ model.clear ();
1773+
1774+ // only perform the request if the user has not disabled
1775+ // online/commercial suggestions. That will hide the category as well.
1776+ if (preferences.remote_content_search != Unity.PreferencesManager.RemoteContent.ALL)
1777+ {
1778+ search.finished();
1779+ return;
1780+ }
1781+
1782+ // create a list of activated sources
1783+ var active_sources = new Gee.ArrayList<string> (null);
1784+ foreach (var opt in sources.options)
1785+ {
1786+ if (source_activated (opt.id))
1787+ active_sources.add (opt.id);
1788+ }
1789+
1790+ // If all the sources are activated, don't bother passing them as arguments
1791+ if (active_sources.size == sources.options.length ())
1792+ {
1793+ active_sources.clear ();
1794+ }
1795+
1796+ if (search_type == Unity.SearchType.DEFAULT)
1797+ {
1798+ if (at_least_one_source_is_on (active_sources))
1799+ {
1800+ yield perform_search (search_string, search, active_sources, cancellable);
1801+ }
1802+ }
1803+
1804+ search.finished ();
1805+ }
1806+
1807+ private bool source_activated (string id)
1808+ {
1809+ bool active = sources.get_option (id).active;
1810+ bool filtering = sources.filtering;
1811+
1812+ if ((active && filtering) || (!active && !filtering))
1813+ return true;
1814+ return false;
1815+ }
1816+
1817+ /* Return a general activation state of all sources of this scope.
1818+ * This is needed, because we don't want to show recommends if an option
1819+ * from another scope is the only one activated
1820+ */
1821+ private bool at_least_one_source_is_on (Gee.ArrayList<string> active_sources)
1822+ {
1823+ return (sources.filtering && active_sources.size > 0 || !sources.filtering);
1824+ }
1825+
1826+ private void on_filters_changed ()
1827+ {
1828+ queue_search_changed (SearchType.DEFAULT);
1829+ }
1830+
1831+ /* Query the server with the search string and the list of sources.
1832+ */
1833+ private async void perform_search (string search_string, LensSearch search, Gee.ArrayList<string> active_sources, Cancellable? cancellable)
1834+ {
1835+ search.results_model.clear ();
1836+
1837+ if ((search_string == null || search_string == "") && (active_sources.size == 0) && (recommendations.size > 0))
1838+ {
1839+ var time = new DateTime.now_utc ();
1840+ if (time.to_unix () - recommendations_last_update < REFRESH_INTERVAL)
1841+ {
1842+ debug ("Updating search results with recommendations");
1843+ update_results_model (search.results_model, recommendations);
1844+ return;
1845+ }
1846+ }
1847+
1848+ var url = UbuntuVideoSearch.build_search_uri (search_string, active_sources);
1849+ debug ("Querying the server: %s", url);
1850+
1851+ bool is_treat_yourself = (search_string == null || search_string == "" || active_sources.size == 0);
1852+ var msg = new Soup.Message ("GET", url);
1853+
1854+ session.queue_message (msg, (session_, msg_) =>
1855+ {
1856+ msg = msg_;
1857+ perform_search.callback ();
1858+ });
1859+
1860+ var cancelled = false;
1861+ ulong cancel_id = 0;
1862+ if (cancellable != null)
1863+ {
1864+ cancel_id = cancellable.connect (() =>
1865+ {
1866+ cancelled = true;
1867+ session.cancel_message (msg, Soup.KnownStatusCode.CANCELLED);
1868+ });
1869+ }
1870+
1871+ yield;
1872+
1873+ if (cancelled)
1874+ {
1875+ // we can't disconnect right away, as that would deadlock (cause
1876+ // cancel_message doesn't return before invoking the callback)
1877+ Idle.add (perform_search.callback);
1878+ yield;
1879+ cancellable.disconnect (cancel_id);
1880+ throw new IOError.CANCELLED ("Cancelled");
1881+ }
1882+
1883+ if (cancellable != null)
1884+ {
1885+ // clean up
1886+ cancellable.disconnect (cancel_id);
1887+ }
1888+
1889+ var results = handle_search_response (msg, is_treat_yourself);
1890+ if (results != null)
1891+ {
1892+ if (search_string == null || search_string.strip () == "" && active_sources.size == 0)
1893+ {
1894+ debug ("Empty search, updating recommendations");
1895+ var time = new DateTime.now_utc ();
1896+ recommendations = results;
1897+ recommendations_last_update = time.to_unix ();
1898+ }
1899+ update_results_model (search.results_model, results);
1900+ }
1901+ }
1902+
1903+ private void update_results_model (Dee.Model model, Gee.ArrayList<RemoteVideoFile?> results)
1904+ {
1905+ foreach (var video in results)
1906+ {
1907+ if (video.uri.has_prefix ("http"))
1908+ {
1909+ var fake_uri = new RemoteUri (video.uri, video.title, video.icon, video.details_uri);
1910+ var result_icon = video.icon;
1911+
1912+ if (video.category == CAT_INDEX_MORE && video.price != null && video.price != "")
1913+ {
1914+ var anno_icon = new Unity.AnnotatedIcon (new FileIcon (File.new_for_uri (result_icon)));
1915+ anno_icon.category = Unity.CategoryType.MOVIE;
1916+ anno_icon.ribbon = video.price;
1917+ result_icon = anno_icon.to_string ();
1918+ }
1919+
1920+ model.append (fake_uri.to_rawuri (), result_icon, video.category, "text/html", video.title, video.comment, video.uri);
1921+ }
1922+ }
1923+ }
1924+
1925+ private void zeitgeist_init () throws Error
1926+ {
1927+ zg_sources = new Zeitgeist.DataSourceRegistry ();
1928+ var templates = new PtrArray.sized(1);
1929+ var ev = new Zeitgeist.Event.full (Zeitgeist.ZG_ACCESS_EVENT, Zeitgeist.ZG_USER_ACTIVITY, "lens://unity-lens-video");
1930+ templates.add ((ev as GLib.Object).ref());
1931+ var data_source = new Zeitgeist.DataSource.full ("98898", "Unity Video Lens", "", templates);
1932+ zg_sources.register_data_source (data_source, null);
1933+ }
1934+
1935+ private void zeitgeist_insert_event (string uri, string title, string icon)
1936+ {
1937+ var subject = new Zeitgeist.Subject.full (uri, Zeitgeist.NFO_VIDEO, Zeitgeist.NFO_REMOTE_DATA_OBJECT, "", uri, title, icon);
1938+ var event = new Zeitgeist.Event.full (Zeitgeist.ZG_ACCESS_EVENT, Zeitgeist.ZG_USER_ACTIVITY, "lens://unity-lens-video");
1939+ event.add_subject (subject);
1940+
1941+ var ev_array = new PtrArray.sized(1);
1942+ ev_array.add ((event as GLib.Object).ref ());
1943+ Zeitgeist.Log.get_default ().insert_events_from_ptrarray (ev_array, null);
1944+ }
1945+ }
1946+}
1947
1948=== added file 'src/remote-uri.vala'
1949--- src/remote-uri.vala 1970-01-01 00:00:00 +0000
1950+++ src/remote-uri.vala 2013-02-19 17:19:01 +0000
1951@@ -0,0 +1,52 @@
1952+/*
1953+ * Copyright (C) 2012 Canonical Ltd
1954+ *
1955+ * This program is free software: you can redistribute it and/or modify
1956+ * it under the terms of the GNU General Public License version 3 as
1957+ * published by the Free Software Foundation.
1958+ *
1959+ * This program is distributed in the hope that it will be useful,
1960+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1961+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1962+ * GNU General Public License for more details.
1963+ *
1964+ * You should have received a copy of the GNU General Public License
1965+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1966+ *
1967+ * Authored by Pawel Stolowski <pawel.stolowski@canonical.com>
1968+ */
1969+
1970+namespace Unity.VideoLens
1971+{
1972+ public class RemoteUri
1973+ {
1974+ public string uri { get; set; }
1975+ public string title { get; set; }
1976+ public string icon { get; set; }
1977+ public string details_uri { get; set; }
1978+
1979+ public RemoteUri (string uri, string title, string icon, string details_uri)
1980+ {
1981+ this.uri = uri;
1982+ this.title = title;
1983+ this.icon = icon;
1984+ this.details_uri = details_uri;
1985+ }
1986+
1987+ public static RemoteUri? from_rawuri (string raw_uri)
1988+ {
1989+ RemoteUri uri = null;
1990+ string[] args = raw_uri.split ("lens-meta://", 4);
1991+ if (args.length == 4)
1992+ {
1993+ uri = new RemoteUri (args[0], args[1], args[2], args[3]);
1994+ }
1995+ return uri;
1996+ }
1997+
1998+ public string to_rawuri ()
1999+ {
2000+ return string.join ("lens-meta://", uri, title, icon, details_uri);
2001+ }
2002+ }
2003+}
2004\ No newline at end of file
2005
2006=== added file 'src/remote-video-main.vala'
2007--- src/remote-video-main.vala 1970-01-01 00:00:00 +0000
2008+++ src/remote-video-main.vala 2013-02-19 17:19:01 +0000
2009@@ -0,0 +1,60 @@
2010+/*
2011+ * Copyright (C) 2012 Canonical Ltd
2012+ *
2013+ * This program is free software: you can redistribute it and/or modify
2014+ * it under the terms of the GNU General Public License version 3 as
2015+ * published by the Free Software Foundation.
2016+ *
2017+ * This program is distributed in the hope that it will be useful,
2018+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2019+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2020+ * GNU General Public License for more details.
2021+ *
2022+ * You should have received a copy of the GNU General Public License
2023+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2024+ */
2025+
2026+using GLib;
2027+using Config;
2028+
2029+namespace Unity.VideoLens {
2030+
2031+ static const string BUS_NAME = "net.launchpad.scope.RemoteVideos";
2032+ Unity.Scope scope;
2033+ static Application? app = null;
2034+
2035+ public static int main (string[] args)
2036+ {
2037+ GLib.Environment.set_prgname ("unity-remote-video-scope");
2038+
2039+ /* Sort up locale to get translations but also sorting and
2040+ * punctuation right */
2041+ GLib.Intl.textdomain (Config.PACKAGE);
2042+ GLib.Intl.bindtextdomain (Config.PACKAGE, Config.LOCALEDIR);
2043+ GLib.Intl.bind_textdomain_codeset (Config.PACKAGE, "UTF-8");
2044+ GLib.Intl.setlocale(GLib.LocaleCategory.ALL, "");
2045+
2046+ try
2047+ {
2048+ app = Extras.dbus_own_name (BUS_NAME, () =>
2049+ {
2050+ scope = new RemoteVideoScope ();
2051+ });
2052+ }
2053+ catch (Error e)
2054+ {
2055+ warning ("Failed to start video lens daemon: %s\n", e.message);
2056+ return 1;
2057+ }
2058+
2059+ if (app == null)
2060+ {
2061+ warning ("Another instance of the Unity Videos Lens " +
2062+ "already appears to be running.\nBailing out.\n");
2063+ return 2;
2064+ }
2065+
2066+ return app.run ();
2067+ }
2068+
2069+} /* namespace */
2070
2071=== added file 'src/scope.vala'
2072--- src/scope.vala 1970-01-01 00:00:00 +0000
2073+++ src/scope.vala 2013-02-19 17:19:01 +0000
2074@@ -0,0 +1,427 @@
2075+/*
2076+ * Copyright (C) 2012 Canonical Ltd
2077+ *
2078+ * This program is free software: you can redistribute it and/or modify
2079+ * it under the terms of the GNU General Public License version 3 as
2080+ * published by the Free Software Foundation.
2081+ *
2082+ * This program is distributed in the hope that it will be useful,
2083+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2084+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2085+ * GNU General Public License for more details.
2086+ *
2087+ * You should have received a copy of the GNU General Public License
2088+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2089+ *
2090+ * Authored by Pawel Stolowski <pawel.stolowski@canonical.com>
2091+ * based on python code by David Calle <davidc@framli.eu>
2092+ *
2093+ */
2094+
2095+namespace Unity.VideoLens
2096+{
2097+ public class VideoScope : Unity.Scope
2098+ {
2099+ private static const int MAX_ZG_EVENTS = 24;
2100+ private static const int CAT_INDEX_MY_VIDEOS = 0;
2101+ private static const int CAT_INDEX_ONLINE = 1;
2102+ private static const int CAT_INDEX_MORE = 2;
2103+ private static const int REFRESH_TIMEOUT = 30;
2104+
2105+ private static string cache_directory;
2106+
2107+ private string videos_folder;
2108+ private Unity.Extras.PreviewPlayer preview_player;
2109+ private Thumbnailer thumbnailer;
2110+ private Locate locate;
2111+ private BlacklistTracker blacklist_tracker;
2112+
2113+ public VideoScope ()
2114+ {
2115+ Object (dbus_path: "/net/launchpad/lens/video/main");
2116+
2117+ videos_folder = GLib.Environment.get_user_special_dir (GLib.UserDirectory.VIDEOS);
2118+ cache_directory = GLib.Environment.get_user_cache_dir () + "/unity-lens-video";
2119+
2120+ // create cache directory
2121+ try
2122+ {
2123+ var cache_dir = GLib.File.new_for_path (cache_directory);
2124+ if (! cache_dir.query_exists (null))
2125+ cache_dir.make_directory (null);
2126+ }
2127+ catch (Error e)
2128+ {
2129+ error ("Failed to create cache directory: %s", e.message);
2130+ }
2131+
2132+ blacklist_tracker = new BlacklistTracker ();
2133+ thumbnailer = new Thumbnailer (cache_directory);
2134+ locate = new Locate (cache_directory, videos_folder);
2135+
2136+ search_in_global = true;
2137+ sources.add_option ("local", _("My Videos"), null);
2138+ provides_personal_content = true;
2139+
2140+ GLib.Timeout.add_seconds (REFRESH_TIMEOUT, refresh_results);
2141+
2142+ search_changed.connect ((search, search_type, cancellable) =>
2143+ {
2144+ dispatch_search (search, search_type, cancellable);
2145+ });
2146+
2147+ filters_changed.connect (on_filters_changed);
2148+ sources.notify["filtering"].connect (on_filters_changed);
2149+ preview_uri.connect ((uri) =>
2150+ {
2151+ return generate_preview_for_uri (uri);
2152+ });
2153+ }
2154+
2155+ private bool refresh_results ()
2156+ {
2157+ debug ("Queuing new search because of timeout");
2158+ queue_search_changed (Unity.SearchType.DEFAULT);
2159+ return true;
2160+ }
2161+
2162+ private void on_filters_changed ()
2163+ {
2164+ queue_search_changed (Unity.SearchType.DEFAULT);
2165+ }
2166+
2167+ private async void dispatch_search (LensSearch search, SearchType search_type, Cancellable cancellable)
2168+ {
2169+ var search_string = search.search_string.strip ();
2170+ var search_status = search;
2171+ var model = search.results_model;
2172+ debug ("Search changed to '%s'", search_string);
2173+
2174+ if (source_activated ("local"))
2175+ {
2176+ if (search_type == Unity.SearchType.GLOBAL)
2177+ {
2178+ if (search_string == "")
2179+ {
2180+ model.clear ();
2181+ if (search != null)
2182+ search_status.finished ();
2183+ debug ("Global view without search string : hide");
2184+ }
2185+ else
2186+ {
2187+ update_results_model (search_string, model, "global", cancellable, search_status);
2188+ }
2189+ }
2190+ else
2191+ {
2192+ if (search_string == null || search_string == "")
2193+ {
2194+ try
2195+ {
2196+ zg_call (cancellable, search_status);
2197+ }
2198+ catch (GLib.Error e)
2199+ {
2200+ warning ("Failed to call zeitgeist: %s", e.message);
2201+ }
2202+ }
2203+ else
2204+ {
2205+ update_results_model (search_string, model, "lens", cancellable, search_status);
2206+ }
2207+ }
2208+ }
2209+ }
2210+
2211+ private void update_results_model (string search_string, Dee.Model model, string cat, Cancellable? cancellable, LensSearch search, bool clear_model = true)
2212+ {
2213+ var home_folder = GLib.Environment.get_home_dir ();
2214+
2215+ if (videos_folder != home_folder)
2216+ {
2217+ locate.updatedb ();
2218+ }
2219+
2220+ var result_list = locate.run_locate (search_string, thumbnailer, video_filter);
2221+ if (result_list != null)
2222+ {
2223+ GLib.Idle.add (() =>
2224+ {
2225+ result_list.sort ((GLib.CompareFunc?)sort_alpha);
2226+ add_results (search, model, cat, cancellable, result_list, search_string, clear_model);
2227+ return false;
2228+ });
2229+ }
2230+ }
2231+
2232+ internal void add_results (LensSearch search_status, Dee.Model model, string cat, Cancellable? cancellable, Gee.ArrayList<VideoFile?> result_list, string search, bool clear_model)
2233+ {
2234+ if (cancellable != null && !cancellable.is_cancelled ())
2235+ {
2236+ if (clear_model)
2237+ search_status.results_model.clear ();
2238+
2239+ foreach (var video in result_list)
2240+ {
2241+ results_model.append (video.uri, video.icon, video.category, "text/html", video.title, video.comment, video.uri);
2242+ }
2243+
2244+ if (search_status != null)
2245+ {
2246+ debug ("Search finished");
2247+ search_status.finished ();
2248+ }
2249+ }
2250+ }
2251+
2252+ internal static int sort_alpha (VideoFile a, VideoFile b)
2253+ {
2254+ return a.lc_title.collate (b.lc_title);
2255+ }
2256+
2257+ private async void zg_call (Cancellable? cancellable, LensSearch search_status) throws Error
2258+ {
2259+ bool active = sources.get_option ("local").active;
2260+ bool filtering = sources.filtering;
2261+ string uri = active && filtering ? "file:*" : "*";
2262+
2263+ var time_range = new Zeitgeist.TimeRange.to_now ();
2264+ var event_template = new Zeitgeist.Event ();
2265+ var subject = new Zeitgeist.Subject.full (uri, Zeitgeist.NFO_VIDEO, "", "", "", "", "");
2266+ event_template.add_subject (subject);
2267+
2268+ var templates = new PtrArray.sized (1);
2269+ templates.add ((event_template as GLib.Object).ref());
2270+ var results = yield Zeitgeist.Log.get_default ().find_events (time_range, templates, Zeitgeist.StorageState.ANY, MAX_ZG_EVENTS, Zeitgeist.ResultType.MOST_RECENT_SUBJECTS, cancellable);
2271+ process_zg_events (results, cancellable, search_status);
2272+ }
2273+
2274+ internal bool video_filter (string path)
2275+ {
2276+ try
2277+ {
2278+ if (Utils.is_video (path) && !Utils.is_hidden (path))
2279+ {
2280+ var file = File.new_for_path (path);
2281+ if (!is_blacklisted(file.get_uri ()))
2282+ return true;
2283+ }
2284+ }
2285+ catch (Error e)
2286+ {
2287+ warning ("Failed to get properties of '%s': %s", path, e.message);
2288+ }
2289+ return false;
2290+ }
2291+
2292+ internal void process_zg_events (Zeitgeist.ResultSet events, Cancellable cancellable, LensSearch search_status)
2293+ {
2294+ var result_list = new Gee.ArrayList<VideoFile?> ();
2295+
2296+ foreach (var event in events)
2297+ {
2298+ if (cancellable.is_cancelled ())
2299+ return;
2300+
2301+ var event_uri = event.get_subject (0).get_uri ();
2302+ if (event_uri.has_prefix ("file://"))
2303+ {
2304+ try
2305+ {
2306+ // If the file is local, we use the same methods
2307+ // as other result items.
2308+ var file = GLib.File.new_for_uri (event_uri);
2309+ var path = file.get_path ();
2310+ if (video_filter (path))
2311+ {
2312+ var finfo = file.query_info (GLib.FileAttribute.STANDARD_DISPLAY_NAME, GLib.FileQueryInfoFlags.NONE, null);
2313+ VideoFile video = VideoFile ()
2314+ {
2315+ title = finfo.get_display_name (),
2316+ lc_title = finfo.get_display_name ().down (),
2317+ comment = "",
2318+ uri = event_uri,
2319+ icon = thumbnailer.get_icon (path),
2320+ category = CAT_INDEX_MY_VIDEOS
2321+ };
2322+ result_list.add (video);
2323+ }
2324+ }
2325+ catch (GLib.Error e)
2326+ {
2327+ warning ("Failed to access file '%s': %s", event_uri, e.message);
2328+ }
2329+ }
2330+ else if (event_uri.has_prefix ("http"))
2331+ {
2332+ // If the file is distant, we take
2333+ // all we need from Zeitgeist
2334+ // this one can be any unicode string:
2335+ VideoFile video = VideoFile ()
2336+ {
2337+ title = event.get_subject (0).get_text (),
2338+ comment = "",
2339+ uri = event_uri,
2340+ icon = event.get_subject (0).get_storage (),
2341+ category = CAT_INDEX_ONLINE
2342+ };
2343+ result_list.add (video);
2344+ }
2345+ }
2346+
2347+ search_status.results_model.clear ();
2348+ foreach (var video in result_list)
2349+ {
2350+ results_model.append (video.uri, video.icon, video.category, "text/html", video.title, video.comment, video.uri);
2351+ }
2352+
2353+ update_results_model ("", search_status.results_model, "lens", cancellable, search_status, false);
2354+ }
2355+
2356+ private Unity.ActivationResponse show_in_folder (string uri)
2357+ {
2358+ try
2359+ {
2360+ Unity.Extras.show_in_folder (uri);
2361+ return new Unity.ActivationResponse (Unity.HandledType.HIDE_DASH);
2362+ }
2363+ catch (GLib.Error e)
2364+ {
2365+ warning ("Failed to show in folder '%s': %s", uri, e.message);
2366+ }
2367+ return new Unity.ActivationResponse (Unity.HandledType.NOT_HANDLED);
2368+ }
2369+
2370+ private Preview? generate_preview_for_uri (string uri)
2371+ {
2372+ debug ("Preview uri: %s", uri);
2373+
2374+ Unity.Preview preview = null;
2375+ var model = results_model;
2376+ var iter = model.get_first_iter ();
2377+ while (!model.is_last (iter))
2378+ {
2379+ if (model.get_string (iter, 0) == uri)
2380+ {
2381+ var title = model.get_string (iter, 4);
2382+ string subtitle = "";
2383+ int64 file_size = 0;
2384+ bool local_video = uri.has_prefix ("file://");
2385+
2386+ if (local_video)
2387+ {
2388+ var file = GLib.File.new_for_uri (uri);
2389+ try
2390+ {
2391+ var finfo = file.query_info (GLib.FileAttribute.TIME_MODIFIED + "," + GLib.FileAttribute.STANDARD_SIZE, GLib.FileQueryInfoFlags.NONE, null);
2392+ file_size = finfo.get_size ();
2393+ var tval = finfo.get_modification_time ();
2394+ var dt = new DateTime.from_timeval_local (tval);
2395+ subtitle = dt.format ("%x, %X");
2396+ }
2397+ catch (Error e)
2398+ {
2399+ // empty subtitle
2400+ }
2401+ }
2402+ var desc = model.get_string (iter, 5);
2403+
2404+ preview = new Unity.MoviePreview (title, subtitle, desc, null);
2405+ preview.closed.connect (on_preview_closed);
2406+ var play_video = new Unity.PreviewAction ("play", _("Play"), null);
2407+ preview.add_action (play_video);
2408+ preview.image_source_uri = model.get_string (iter, 1);
2409+
2410+ if (local_video)
2411+ {
2412+ var show_folder = new Unity.PreviewAction ("show-in-folder", _("Show in Folder"), null);
2413+ show_folder.activated.connect (show_in_folder);
2414+ preview.add_action (show_folder);
2415+ }
2416+
2417+ // we may get remote uris from zeitgeist - fetch details for local files only
2418+ if (local_video)
2419+ {
2420+ var async_preview = new Unity.AsyncPreview ();
2421+ preview.image_source_uri = uri;
2422+
2423+ if (preview_player == null)
2424+ preview_player = new Unity.Extras.PreviewPlayer ();
2425+
2426+ preview_player.video_properties.begin (uri, (obj, res) =>
2427+ {
2428+ try
2429+ {
2430+ var props = preview_player.video_properties.end (res);
2431+ if ("width" in props && "height" in props && "codec" in props)
2432+ {
2433+ var width = props["width"].get_uint32 ();
2434+ var height = props["height"].get_uint32 ();
2435+ var codec = props["codec"].get_string ();
2436+ string dimensions = "%u*%u".printf (width, height);
2437+ if (width > 0 && height > 0)
2438+ {
2439+ var gcd = Utils.gcd (width, height);
2440+ dimensions += ", %u:%u".printf (width / gcd, height / gcd);
2441+ }
2442+
2443+ preview.add_info (new Unity.InfoHint ("format", _("Format"), null, codec));
2444+ preview.add_info (new Unity.InfoHint ("dimensions", _("Dimensions"), null, dimensions));
2445+ preview.add_info (new Unity.InfoHint ("size", _("Size"), null, GLib.format_size (file_size)));
2446+ }
2447+ }
2448+ catch (Error e)
2449+ {
2450+ warning ("Couldn't get video details: %s", e.message);
2451+ }
2452+ async_preview.preview_ready (preview);
2453+ });
2454+ return async_preview;
2455+ }
2456+ break;
2457+ }
2458+ iter = model.next (iter);
2459+ }
2460+
2461+ if (preview == null)
2462+ warning ("Couldn't find model row for requested preview uri: %s", uri);
2463+
2464+ return preview;
2465+ }
2466+
2467+ private bool is_blacklisted (string uri)
2468+ {
2469+ foreach (var blacklisted_uri in blacklist_tracker.get_blacklisted_uris ())
2470+ {
2471+ if (uri.has_prefix (blacklisted_uri))
2472+ return true;
2473+ }
2474+ return false;
2475+ }
2476+
2477+ private void on_preview_closed (Unity.Preview preview)
2478+ {
2479+ if (preview_player != null)
2480+ {
2481+ try
2482+ {
2483+ preview_player.close ();
2484+ }
2485+ catch (Error e)
2486+ {
2487+ warning ("Failed to close preview player: %s", e.message);
2488+ }
2489+ }
2490+ }
2491+
2492+ private bool source_activated (string source)
2493+ {
2494+ // Return the state of a sources filter option
2495+ var active = sources.get_option (source).active;
2496+ var filtering = sources.filtering;
2497+ return (active && filtering) || (!active && !filtering);
2498+ }
2499+
2500+ }
2501+}
2502
2503=== added file 'src/thumbnailer.vala'
2504--- src/thumbnailer.vala 1970-01-01 00:00:00 +0000
2505+++ src/thumbnailer.vala 2013-02-19 17:19:01 +0000
2506@@ -0,0 +1,115 @@
2507+/*
2508+ * Copyright (C) 2012 Canonical Ltd
2509+ *
2510+ * This program is free software: you can redistribute it and/or modify
2511+ * it under the terms of the GNU General Public License version 3 as
2512+ * published by the Free Software Foundation.
2513+ *
2514+ * This program is distributed in the hope that it will be useful,
2515+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2516+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2517+ * GNU General Public License for more details.
2518+ *
2519+ * You should have received a copy of the GNU General Public License
2520+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2521+ *
2522+ * Authored by Pawel Stolowski <pawel.stolowski@canonical.com>
2523+ * based on python code by David Calle <davidc@framli.eu>
2524+ *
2525+ */
2526+
2527+namespace Unity.VideoLens
2528+{
2529+ public class Thumbnailer
2530+ {
2531+ private static const int MAX_THUMBNAILERS = 3;
2532+
2533+ private string videos_folder;
2534+ private string cache_directory;
2535+ private Gee.LinkedList<GLib.Pid?> thumbnailer_pids;
2536+
2537+ public Thumbnailer (string cache_dir)
2538+ {
2539+ cache_directory = cache_dir;
2540+ videos_folder = GLib.Environment.get_user_special_dir (GLib.UserDirectory.VIDEOS);
2541+ thumbnailer_pids = new Gee.LinkedList<GLib.Pid?> ();
2542+ }
2543+
2544+ public string get_icon (string video_file)
2545+ {
2546+ /* This method checks several locations for a video thumbnail.
2547+
2548+ 1) <filename>.jpg file in the same folder (not activated for now)
2549+ 2) Nautilus thumbnails
2550+ 3) Cached thumbnails generated by the scope
2551+
2552+ If nothing has been found, it tries to generate a thumbnail with Totem,
2553+ stores and uses it.
2554+ If the generation fails or is slow, it fallbacks to the standard video
2555+ icon.
2556+ */
2557+ var file = GLib.File.new_for_path (video_file);
2558+ var video_path = file.get_path ();
2559+
2560+ string? icon_path = null;
2561+
2562+ // check if nautilus thumbnail is available
2563+ try
2564+ {
2565+ var finfo = file.query_info (GLib.FileAttribute.THUMBNAIL_PATH + "," + GLib.FileAttribute.THUMBNAILING_FAILED, GLib.FileQueryInfoFlags.NONE);
2566+ if (finfo.get_attribute_boolean (GLib.FileAttribute.THUMBNAILING_FAILED))
2567+ return "video";
2568+ else
2569+ icon_path = finfo.get_attribute_as_string (GLib.FileAttribute.THUMBNAIL_PATH);
2570+ }
2571+ catch (GLib.Error e)
2572+ {
2573+ // silently ignore
2574+ }
2575+
2576+ if (icon_path == null || icon_path == "")
2577+ {
2578+ string check_path = cache_directory + "/" + video_path.replace (videos_folder, "").replace ("/", "_");
2579+
2580+ // check for thumbnail generated by this scope
2581+ if (Utils.is_regular_file (check_path))
2582+ return check_path;
2583+
2584+ create_thumbnail (video_path, check_path);
2585+
2586+ // FIXME: this is not reliable, since we spawn thumbnailer async
2587+ if (Utils.is_regular_file (check_path))
2588+ return check_path;
2589+ }
2590+
2591+ if (icon_path == null || icon_path == "")
2592+ icon_path = "video";
2593+
2594+ return icon_path;
2595+ }
2596+
2597+ private void thumbnailer_process_watch (Pid pid, int status)
2598+ {
2599+ thumbnailer_pids.remove (pid);
2600+ }
2601+
2602+ public void create_thumbnail (string video_path, string thumbnail_path)
2603+ {
2604+ if (thumbnailer_pids.size < MAX_THUMBNAILERS)
2605+ {
2606+ GLib.Pid pid;
2607+ string[] args = {"/usr/bin/totem-video-thumbnailer", video_path, thumbnail_path};
2608+ try
2609+ {
2610+ GLib.Process.spawn_async (null, args, null, GLib.SpawnFlags.DO_NOT_REAP_CHILD, null, out pid);
2611+ GLib.ChildWatch.add (pid, thumbnailer_process_watch);
2612+ thumbnailer_pids.add (pid);
2613+ }
2614+ catch (Error e)
2615+ {
2616+ warning ("Failed to spawn thumbailer: %s", e.message);
2617+ }
2618+ }
2619+ }
2620+ }
2621+}
2622\ No newline at end of file
2623
2624=== added file 'src/ubuntu-video-search.vala'
2625--- src/ubuntu-video-search.vala 1970-01-01 00:00:00 +0000
2626+++ src/ubuntu-video-search.vala 2013-02-19 17:19:01 +0000
2627@@ -0,0 +1,229 @@
2628+/*
2629+ * Copyright (C) 2012 Canonical Ltd
2630+ *
2631+ * This program is free software: you can redistribute it and/or modify
2632+ * it under the terms of the GNU General Public License version 3 as
2633+ * published by the Free Software Foundation.
2634+ *
2635+ * This program is distributed in the hope that it will be useful,
2636+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2637+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2638+ * GNU General Public License for more details.
2639+ *
2640+ * You should have received a copy of the GNU General Public License
2641+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2642+ *
2643+ * Authored by Pawel Stolowski <pawel.stolowski@canonical.com>
2644+ *
2645+ */
2646+
2647+namespace Unity.VideoLens.UbuntuVideoSearch
2648+{
2649+ private static const string SERVER = "http://videosearch.ubuntu.com/v0";
2650+
2651+ public static string sources_uri ()
2652+ {
2653+ return SERVER + "/sources";
2654+ }
2655+
2656+ public static string recommendations_uri ()
2657+ {
2658+ return SERVER + "/search?q=&sources=Amazon";
2659+ }
2660+
2661+ public static string build_search_uri (string query, Gee.ArrayList<string>? sources)
2662+ {
2663+ var uri = new StringBuilder ();
2664+ uri.append (SERVER);
2665+ uri.append ("/search?q=");
2666+ uri.append (Uri.escape_string (query, "", false));
2667+ uri.append ("&split=true");
2668+ if (sources != null && sources.size>0)
2669+ {
2670+ uri.append ("&sources=");
2671+ uri.append (sources[0]);
2672+ for (int i=1; i<sources.size; i++)
2673+ {
2674+ uri.append (",");
2675+ uri.append (sources[i]);
2676+ }
2677+ }
2678+ return uri.str;
2679+ }
2680+
2681+ /**
2682+ * Parses JSON data returned by search requests, returns list of videos.
2683+ */
2684+ public Gee.ArrayList<RemoteVideoFile?>? process_search_results (string json_data, bool is_treat_yourself) throws Error
2685+ {
2686+ var parser = new Json.Parser ();
2687+
2688+ parser.load_from_data (json_data);
2689+ var root = parser.get_root ();
2690+
2691+ if (root.get_node_type () == Json.NodeType.OBJECT)
2692+ {
2693+ var records = root.get_object ().get_array_member ("other");
2694+ var results = process_results_array (records, CAT_INDEX_ONLINE);
2695+ records = root.get_object ().get_array_member ("treats");
2696+ results.add_all (process_results_array (records, CAT_INDEX_MORE));
2697+ return results;
2698+ }
2699+ else
2700+ {
2701+ var records = root.get_array ();
2702+ var results = process_results_array (records, is_treat_yourself ? CAT_INDEX_MORE : CAT_INDEX_ONLINE);
2703+ return results;
2704+ }
2705+ }
2706+
2707+ /**
2708+ * Joins elements of JSON array of strings.
2709+ */
2710+ internal string join_array (Json.Array array, string separator)
2711+ {
2712+ var res = new StringBuilder ();
2713+ var len = array.get_length ();
2714+ array.foreach_element ((a, index, node) =>
2715+ {
2716+ res.append (node.get_string ());
2717+ if (index < len - 1)
2718+ res.append (separator);
2719+ });
2720+ return res.str;
2721+ }
2722+
2723+ public string[] json_array_to_str_array (Json.Array array)
2724+ {
2725+ string [] res = {};
2726+ array.foreach_element ((a, index, node) =>
2727+ {
2728+ res += node.get_string ();
2729+ });
2730+ return res;
2731+ }
2732+
2733+ public RemoteVideoDetails process_details_results (string json_data) throws Error
2734+ {
2735+ var parser = new Json.Parser ();
2736+ parser.load_from_data (json_data);
2737+ var details = parser.get_root ().get_object ();
2738+
2739+ var video = RemoteVideoDetails ();
2740+ video.title = details.get_string_member ("title");
2741+ video.image = details.get_string_member ("image");
2742+ video.description = details.get_string_member ("description");
2743+ video.source = details.get_string_member ("source");
2744+
2745+ if (details.has_member ("release_date"))
2746+ {
2747+ // v1 spec states release_date will be YYYY-MM-DD
2748+ var release_date = GLib.Time ();
2749+ if (release_date.strptime (details.get_string_member ("release_date"), "%Y-%m-%d") != null)
2750+ {
2751+ video.release_date = release_date.format ("%Y");
2752+ }
2753+ else
2754+ {
2755+ warning ("Failed to parse release_date: '%s'", details.get_string_member ("release_date"));
2756+ }
2757+ }
2758+
2759+ video.price = details.has_member ("formatted_price") ? details.get_string_member ("formatted_price") : "";
2760+
2761+ video.duration = 0;
2762+ if (details.has_member ("duration"))
2763+ video.duration = int.parse (details.get_string_member ("duration")) / 60;
2764+
2765+ video.directors = {};
2766+ if (details.has_member ("directors"))
2767+ {
2768+ var directors = details.get_array_member ("directors");
2769+ video.directors = json_array_to_str_array (directors);
2770+ }
2771+
2772+ if (details.has_member ("starring"))
2773+ {
2774+ var starring = details.get_array_member ("starring");
2775+ video.starring = join_array (starring, ", ");
2776+ }
2777+
2778+ video.genres = {};
2779+ if (details.has_member ("genres"))
2780+ {
2781+ var genres = details.get_array_member ("genres");
2782+ video.genres = json_array_to_str_array (genres);
2783+ }
2784+
2785+ if (details.has_member ("uploaded_by"))
2786+ video.uploaded_by = details.get_string_member ("uploaded_by");
2787+
2788+ if (details.has_member ("date_uploaded"))
2789+ video.date_uploaded = details.get_string_member ("date_uploaded");
2790+
2791+ return video;
2792+ }
2793+
2794+ /**
2795+ * Parses JSON data returned by 'sources' query, returns list of sources (e.g. "Amazon", "Youtube" etc.)
2796+ */
2797+ public Gee.ArrayList<string>? process_sources_results (string json_data) throws Error
2798+ {
2799+ var parser = new Json.Parser ();
2800+
2801+ parser.load_from_data (json_data);
2802+ var sources_array = parser.get_root ().get_array ();
2803+ var results = new Gee.ArrayList<string> (null);
2804+
2805+ sources_array.foreach_element ((array, index, node) =>
2806+ {
2807+ results.add (node.get_string ());
2808+ });
2809+
2810+ return results;
2811+ }
2812+
2813+ /**
2814+ * Converts JSON results array to list of RemoteVideoFile records.
2815+ */
2816+ internal Gee.ArrayList<RemoteVideoFile?> process_results_array (Json.Array results, int category)
2817+ {
2818+ var videos = new Gee.ArrayList<RemoteVideoFile?> ();
2819+
2820+ results.foreach_element ((array, index, node) =>
2821+ {
2822+ try
2823+ {
2824+ var video = RemoteVideoFile ();
2825+ var rec = node.get_object ();
2826+ video.uri = rec.get_string_member ("url");
2827+ video.title = rec.get_string_member ("title");
2828+ video.icon = rec.get_string_member ("img");
2829+ video.comment = rec.get_string_member ("source");
2830+ video.details_uri = (rec.has_member ("details") ? rec.get_string_member ("details") : "");
2831+ video.category = category;
2832+ video.price = null;
2833+
2834+ if (category == CAT_INDEX_MORE)
2835+ {
2836+ string price = null;
2837+ if (rec.has_member ("formatted_price"))
2838+ {
2839+ price = rec.get_string_member ("formatted_price");
2840+ }
2841+ if (price == null || price == "free")
2842+ {
2843+ price = _("Free");
2844+ }
2845+ video.price = price;
2846+ }
2847+ videos.add (video);
2848+ }
2849+ catch (Error e)
2850+ {
2851+ warning ("Malformed result, skipping: %s", e.message);
2852+ }
2853+ });
2854+ return videos;
2855+ }
2856+}
2857\ No newline at end of file
2858
2859=== removed file 'src/unity-lens-video'
2860--- src/unity-lens-video 2012-10-18 15:05:16 +0000
2861+++ src/unity-lens-video 1970-01-01 00:00:00 +0000
2862@@ -1,543 +0,0 @@
2863-#! /usr/bin/python
2864-# -*- coding: utf-8 -*-
2865-
2866-# Copyright (c) 2011 David Calle <davidc@framli.eu>
2867-
2868-# This program is free software: you can redistribute it and/or modify
2869-# it under the terms of the GNU General Public License as published by
2870-# the Free Software Foundation, either version 3 of the License, or
2871-# (at your option) any later version.
2872-
2873-# This program is distributed in the hope that it will be useful,
2874-# but WITHOUT ANY WARRANTY; without even the implied warranty of
2875-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2876-# GNU General Public License for more details.
2877-
2878-# You should have received a copy of the GNU General Public License
2879-# along with this program. If not, see <http://www.gnu.org/licenses/>.
2880-
2881-"""The unity video lens. Indexing videos in ~/Videos and generating thumbnails if needed."""
2882-
2883-import gettext
2884-import locale
2885-import os
2886-import sys
2887-from zeitgeist.client import ZeitgeistClient
2888-from zeitgeist import client, datamodel
2889-import time
2890-import fractions
2891-import dbus
2892-
2893-#pylint: disable=E0611
2894-from gi.repository import (
2895- GLib,
2896- GObject,
2897- Gio,
2898- Unity,
2899- Dee
2900-)
2901-#pylint: enable=E0611
2902-
2903-APP_NAME = "unity-lens-video"
2904-LOCAL_PATH = "/usr/share/locale/"
2905-
2906-gettext.bindtextdomain(APP_NAME, LOCAL_PATH)
2907-gettext.textdomain(APP_NAME)
2908-_ = gettext.gettext
2909-
2910-# Translatable strings
2911-CAT_ONCOMPUTER = _("My Videos")
2912-CAT_ONLINE = _("Online")
2913-CAT_GLOBAL = _("Videos")
2914-CAT_RECENT = _("Recently Viewed")
2915-CAT_MORE_SUGGESTIONS = _("More suggestions")
2916-HINT = _("Search Videos")
2917-SOURCES = _("Sources")
2918-LOCAL_VIDEOS = _("My Videos")
2919-
2920-CAT_INDEX_MY_VIDEOS = 0
2921-CAT_INDEX_ONLINE = 1
2922-CAT_INDEX_MORE = 2
2923-
2924-BUS_NAME = "net.launchpad.lens.video"
2925-FOLDER = GLib.get_user_special_dir(GLib.USER_DIRECTORY_VIDEOS)
2926-HOME_FOLDER = GLib.get_home_dir()
2927-CACHE = "%s/unity-lens-video" % GLib.get_user_cache_dir()
2928-DB = "videos.db"
2929-Q = []
2930-Q_MAX = 3
2931-try:
2932- ZG = ZeitgeistClient()
2933-except:
2934- raise SystemExit(1)
2935-
2936-REFRESH_TIMEOUT = 300
2937-PREVIEW_PLAYER_DBUS_NAME = "com.canonical.Unity.Lens.Music.PreviewPlayer"
2938-PREVIEW_PLAYER_DBUS_PATH = "/com/canonical/Unity/Lens/Music/PreviewPlayer"
2939-PREVIEW_PLAYER_DBUS_IFACE = PREVIEW_PLAYER_DBUS_NAME
2940-
2941-# pylint: disable=R0903
2942-class Daemon:
2943-
2944- """Creation of a lens with a local scope."""
2945-
2946- def __init__(self):
2947- #Create the lens
2948- self._lens = Unity.Lens.new("/net/launchpad/lens/video", "video")
2949- self._lens.props.search_hint = HINT
2950- self._lens.props.visible = True
2951- self._lens.props.search_in_global = True
2952- self._lens.props.sources_display_name = SOURCES
2953-
2954- svg_dir = "/usr/share/icons/unity-icon-theme/places/svg/"
2955- cats = []
2956- cats.append(Unity.Category.new(CAT_ONCOMPUTER,
2957- Gio.ThemedIcon.new(svg_dir + "group-videos.svg"),
2958- Unity.CategoryRenderer.VERTICAL_TILE))
2959- cats.append(Unity.Category.new(CAT_ONLINE,
2960- Gio.ThemedIcon.new(svg_dir + "group-internet.svg"),
2961- Unity.CategoryRenderer.VERTICAL_TILE))
2962- cats.append(Unity.Category.new(CAT_MORE_SUGGESTIONS,
2963- Gio.ThemedIcon.new(svg_dir + "group-treat-yourself.svg"),
2964- Unity.CategoryRenderer.VERTICAL_TILE))
2965- self._lens.props.categories = cats
2966-
2967- filters = []
2968- self._lens.props.filters = filters
2969-
2970- self.bus = dbus.SessionBus()
2971-
2972- # Create the scope
2973- self._scope = Unity.Scope.new("/net/launchpad/lens/video/main")
2974- self._scope.search_in_global = True
2975- self._scope.props.sources.add_option('local', LOCAL_VIDEOS, None)
2976- self._scope.props.provides_personal_content=True
2977- self._scope.connect("search-changed", self.on_search_changed)
2978- self._scope.connect("filters-changed",self.on_filtering_changed)
2979- self._scope.props.sources.connect("notify::filtering",
2980- self.on_filtering_changed)
2981- self._scope.connect('preview-uri', self.on_preview_uri)
2982- self._lens.add_local_scope(self._scope)
2983- self._lens.export()
2984-
2985- GLib.timeout_add_seconds(REFRESH_TIMEOUT, self.refresh_results)
2986- self._scope.queue_search_changed(Unity.SearchType.DEFAULT)
2987-
2988- def refresh_results(self, *_):
2989- """Update the results on a timeout."""
2990- print "Queuing new search because of timeout"
2991- self._scope.queue_search_changed(Unity.SearchType.DEFAULT)
2992- return True
2993-
2994- def on_filtering_changed(self, *_):
2995- """Run another search when a filter change is notified."""
2996- self._scope.queue_search_changed(Unity.SearchType.DEFAULT)
2997-
2998- def source_activated(self, source):
2999- """Return the state of a sources filter option."""
3000- active = self._scope.props.sources.get_option(source).props.active
3001- filtering = self._scope.props.sources.props.filtering
3002- if (active and filtering) or (not active and not filtering):
3003- return True
3004- else:
3005- return False
3006-
3007- def on_preview_closed(self, preview):
3008- try:
3009- player = self.bus.get_object (PREVIEW_PLAYER_DBUS_NAME, PREVIEW_PLAYER_DBUS_PATH)
3010- dbus.Interface (player, PREVIEW_PLAYER_DBUS_IFACE).Close()
3011- except Exception as e:
3012- print "Failed to send close signal to preview player:", e
3013-
3014- def on_preview_uri(self, scope, uri):
3015- """Preview request handler"""
3016- preview = None
3017- model = self._scope.props.results_model
3018- iter = model.get_first_iter()
3019- while not model.is_last(iter):
3020- if model.get_string(iter, 0) == uri:
3021- title = model.get_string(iter, 4);
3022- try:
3023- subtitle = time.strftime("%x, %X", time.localtime(os.path.getmtime(GLib.filename_from_uri(uri, None))))
3024- except:
3025- # Instead of empty, maybe the date/time of the zg event?
3026- subtitle = ''
3027- desc = model.get_string(iter, 5);
3028- preview = Unity.MoviePreview.new(title, subtitle, desc, None)
3029- preview.connect('closed', self.on_preview_closed)
3030-
3031- # we may get remote uris from zeitgeist - fetch details for local files only
3032- if uri.startswith("file://"):
3033- local_video = True
3034- preview.props.image_source_uri = uri
3035- try:
3036- player = self.bus.get_object (PREVIEW_PLAYER_DBUS_NAME, PREVIEW_PLAYER_DBUS_PATH)
3037- props = dbus.Interface (player, PREVIEW_PLAYER_DBUS_IFACE).VideoProperties(uri)
3038- width = props['width']
3039- height = props['height']
3040- codec = props['codec']
3041- dimensions = str(width) + "*" + str(height)
3042- if width > 0 and height > 0:
3043- gcd = fractions.gcd(width, height)
3044- dimensions += ", " + str(width / gcd) + ":" + str(height / gcd)
3045- preview.add_info(Unity.InfoHint.new("format", _("Format"), None, codec))
3046- preview.add_info(Unity.InfoHint.new("dimensions", _("Dimensions"), None, dimensions))
3047- preview.add_info(Unity.InfoHint.new("size", _("Size"), None, GLib.format_size(os.path.getsize(GLib.filename_from_uri(uri, None)))))
3048- except Exception as e:
3049- print "Couldn't get video details", e
3050- else:
3051- local_video = False
3052- preview.props.image_source_uri = model.get_string(iter, 1)
3053- play_video = Unity.PreviewAction.new("play", _("Play"), None)
3054- preview.add_action(play_video)
3055- if local_video:
3056- show_folder = Unity.PreviewAction.new("show-in-folder", _("Show in Folder"), None)
3057- show_folder.connect('activated', self.show_in_folder)
3058- preview.add_action(show_folder)
3059- break
3060- iter = model.next(iter)
3061- if preview == None:
3062- print "Couldn't find model row for requested preview uri:", uri
3063- return preview
3064-
3065- def show_in_folder(self, scope, uri):
3066- """ Open folder that contains given video """
3067- file = Gio.file_new_for_uri (uri)
3068- parent = file.get_parent()
3069- if parent == None:
3070- parent = Gio.file_new_for_path("/")
3071- try:
3072- Gio.app_info_launch_default_for_uri(parent.get_uri(), None)
3073- except Exception as e:
3074- print "Couldn't launch default app for uri", parent.get_uri(), e
3075- return Unity.ActivationResponse(handled=Unity.HandledType.NOT_HANDLED)
3076- return Unity.ActivationResponse(handled=Unity.HandledType.HIDE_DASH)
3077-
3078- def on_search_changed(self, scope, search, search_type, cancellable):
3079- """On a new search, differentiate between lens view
3080- and global search before updating the model"""
3081- search_status = search
3082- search_string = search.props.search_string.strip()
3083- print "Search changed to \"%s\"" % search_string
3084- model = search.props.results_model
3085- if self.source_activated('local'):
3086- if search_type is Unity.SearchType.GLOBAL:
3087- if search_string == '':
3088- model.clear ()
3089- if search_status:
3090- search_status.finished ()
3091- print "Global view without search string : hide"
3092-
3093- else:
3094- self.update_results_model(search_string, model, 'global', cancellable, search_status)
3095- else:
3096- if not search_string:
3097- # this will call update_results_model as well
3098- self.zg_call(cancellable, search_status)
3099- else:
3100- self.update_results_model(search_string, model, 'lens', cancellable, search_status)
3101- else:
3102- model.clear()
3103- if search_status:
3104- search_status.finished ()
3105-
3106- def update_results_model(self, search, model, cat, cancellable, search_status, clear_model=True):
3107- """Check for the existence of the cache folder, create it if needed,
3108- and run the search method."""
3109- if not Gio.file_new_for_path(CACHE).query_exists(None):
3110- Gio.file_new_for_path(CACHE).make_directory(None)
3111- if FOLDER != HOME_FOLDER:
3112- try:
3113- GLib.spawn_async(['/usr/bin/updatedb', '-o', CACHE+'/'+DB,
3114- '-l', '0', '-U', FOLDER])
3115- except GLib.GError:
3116- print "Can't create the database, will retry."
3117-
3118- if self.is_file(CACHE+'/'+DB):
3119- try:
3120- results = GLib.spawn_sync(None,
3121- ['/usr/bin/locate',
3122- '-id', CACHE+'/'+DB,
3123- FOLDER+'*'+search.replace (" ","*")+'*' ],
3124- None, 0, None, None)
3125- except GLib.GError:
3126- results = None
3127- else:
3128- # spawn_sync returns bool, stdout, stderr, exit_status
3129- if results[3] == 0:
3130- results = results[1]
3131- else:
3132- results = None
3133- else:
3134- results = None
3135- result_list = []
3136- blacklist = self.get_blacklist ()
3137- if results:
3138- video_counter = 0
3139- print len(results.split('\n'))
3140- for video in results.split('\n'):
3141- if video_counter < 100:
3142- if self.is_video(video) and not self.is_hidden(video, blacklist):
3143- video_counter+= 1
3144- title = self.get_name(video)
3145- comment = ''
3146- uri = 'file://%s' % video
3147- icon = self.get_icon(video)
3148- if title:
3149- item = []
3150- item.append(title)
3151- item.append(comment)
3152- item.append(uri)
3153- item.append(icon)
3154- result_list.append(item)
3155- result_list = self.sort_alpha(result_list)
3156-
3157- GLib.idle_add(self.add_results, search_status, model, cat, cancellable, result_list, search, clear_model)
3158-
3159-
3160- def add_results (self, search_status=None, model=None,
3161- cat=None, cancellable=None, result_list=[], search=None,
3162- clear_model=None):
3163- result_sets = []
3164-
3165- if cancellable and not cancellable.is_cancelled():
3166- if cat == 'global':
3167- # Create only one result set for the Global search
3168- result_sets.append({'category':CAT_INDEX_MY_VIDEOS, 'results':result_list})
3169- else:
3170- result_sets.append({'category':CAT_INDEX_MY_VIDEOS, 'results':result_list})
3171- if clear_model: model.clear()
3172- for result_set in result_sets:
3173- cat = result_set['category']
3174- for i in result_set['results']:
3175- title = str(i[0])
3176- comment = str(i[1])
3177- uri = str(i[2])
3178- dnd_uri = str(i[2])
3179- icon_hint = str(i[3])
3180- model.append(uri, icon_hint, cat, "text/html",
3181- title, comment, dnd_uri)
3182- if search_status:
3183- print "Search finished"
3184- search_status.finished()
3185- else:
3186- print "Search cancelled"
3187- return False
3188-
3189- def is_file(self, uri):
3190- """Check if the file is an actual file"""
3191- g_file = Gio.file_new_for_path(uri)
3192- if g_file.query_exists(None):
3193- file_type = g_file.query_file_type(Gio.FileQueryInfoFlags.NONE,
3194- None)
3195- if file_type is Gio.FileType.REGULAR:
3196- return True
3197-
3198- def get_blacklist(self):
3199- """Get zeitgeist blacklist"""
3200- bt_list = []
3201- try:
3202- iface = client.ZeitgeistDBusInterface()
3203- except:
3204- print "Unable to connect to Zeitgeist, won't handle blacklists."
3205- iface = None
3206- if iface:
3207- blacklist = iface.get_extension("Blacklist", "blacklist")
3208- bt_list = blacklist.GetTemplates ()
3209- return bt_list
3210-
3211- def is_hidden(self, uri, blacklist):
3212- """Check if the file is hidden"""
3213- g_file = Gio.file_new_for_path(uri)
3214- hidden = g_file.query_info(
3215- Gio.FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
3216- Gio.FileQueryInfoFlags.NONE,
3217- None).get_attribute_boolean('standard::is-hidden')
3218- blacklisted = False
3219- for bt in blacklist:
3220- bt = bt.replace('dir-/', '/').encode("utf-8")
3221- if uri.startswith(bt):
3222- blacklisted = True
3223- if hidden or uri.find('/.') > -1 or blacklisted:
3224- return True
3225-
3226- def sort_alpha(self, results):
3227- """Sort results in several ways, depending on the category"""
3228- results.sort(key=lambda x: x[0].lower(), reverse=False)
3229- return results
3230-
3231- def is_video(self, uri):
3232- """Check if the file is a video"""
3233- if self.is_file(uri):
3234- g_file = Gio.file_new_for_path(uri)
3235- content_type = g_file.query_info('standard::content-type',
3236- Gio.FileQueryInfoFlags.NONE,
3237- None).get_content_type()
3238- if 'video' in content_type:
3239- return True
3240-
3241- def get_name(self, uri):
3242- """Get the display name of the file"""
3243- g_file = Gio.file_new_for_path(uri)
3244- name = g_file.query_info(
3245- Gio.FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
3246- Gio.FileQueryInfoFlags.NONE,
3247- None)
3248- display_name = name.get_attribute_as_string(
3249- Gio.FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME)
3250- return display_name
3251-
3252- def get_icon(self, uri):
3253- """This method checks several locations for a video thumbnail.
3254-
3255- 1) <filename>.jpg file in the same folder (not activated for now)
3256- 1) Nautilus thumbnails
3257- 2) Cached thumbnails generated by the scope
3258-
3259- If nothing has been found, it tries to generate a thumbnail with Totem,
3260- stores and uses it.
3261- If the generation fails or is slow, it fallbacks to the standard video
3262- icon.
3263- """
3264- icon_path = None
3265- g_file = Gio.file_new_for_path(uri)
3266- video_path = g_file.get_path()
3267- thumb_name = video_path.replace(FOLDER, '').replace('/', '_')
3268- icon_check = '%s/thumb_%s.png' % (CACHE, thumb_name)
3269- # if not icon_path:
3270- # print 'Check for local cover'
3271- # local_cover = uri.replace('.%s' % uri.split('.')[-1], '.jpg')
3272- # if self.is_file(local_cover):
3273- # icon_path = local_cover
3274- if not icon_path:
3275- icon = g_file.query_info(
3276- ','.join((Gio.FILE_ATTRIBUTE_THUMBNAIL_PATH,
3277- Gio.FILE_ATTRIBUTE_THUMBNAILING_FAILED)),
3278- Gio.FileQueryInfoFlags.NONE,
3279- None)
3280- if icon.get_attribute_boolean(Gio.FILE_ATTRIBUTE_THUMBNAILING_FAILED):
3281- icon_path = 'video'
3282- else:
3283- icon_path = icon.get_attribute_as_string(
3284- Gio.FILE_ATTRIBUTE_THUMBNAIL_PATH)
3285- if not icon_path:
3286- if self.is_file(icon_check):
3287- icon_path = icon_check
3288- if not icon_path:
3289- # Check if processes can be removed from the thumbnailing queue
3290- for process in Q:
3291- if not Gio.file_new_for_path(
3292- "/proc/"+str(process)).query_exists(None):
3293- Q.remove(process)
3294- if len(Q) < Q_MAX:
3295- try:
3296- p = GLib.spawn_async(['/usr/bin/totem-video-thumbnailer',
3297- video_path, icon_check])
3298- Q.append(p[0])
3299- if self.is_file(icon_check):
3300- icon_path = icon_check
3301- except:
3302- print "Warning : the file may have been removed."
3303- if not icon_path:
3304- icon_path = 'video'
3305- return icon_path
3306-
3307- def zg_call (self, cancellable, search_status):
3308- active = self._scope.props.sources.get_option("local").props.active
3309- filtering = self._scope.props.sources.props.filtering
3310- if active and filtering:
3311- uri = "file:*"
3312- else:
3313- uri = "*"
3314- time_range = datamodel.TimeRange.until_now ()
3315- max_amount_results = 24
3316- event_template = datamodel.Event()
3317- interpretation = datamodel.Interpretation.VIDEO
3318- event_template.append_subject(
3319- datamodel.Subject.new_for_values(uri=uri,interpretation=interpretation))
3320- def wrap(callback):
3321- def wrapped(events):
3322- callback(events, cancellable, search_status)
3323-
3324- return wrapped
3325-
3326- ZG.find_events_for_templates(
3327- [event_template, ],
3328- wrap(self.progress_zg_events),
3329- timerange = time_range,
3330- storage_state = datamodel.StorageState.Any,
3331- num_events = max_amount_results,
3332- result_type = datamodel.ResultType.MostRecentSubjects
3333- )
3334-
3335- def progress_zg_events(self, events, cancellable, search_status):
3336- if cancellable.is_cancelled(): return
3337- blacklist = self.get_blacklist ()
3338- result_list = []
3339- for event in events:
3340- item = []
3341- uri = event.get_subjects()[0].uri
3342- if uri.startswith('file://'):
3343- # If the file is local, we use the same methods
3344- # as other result items.
3345- g_file = Gio.file_new_for_uri(uri)
3346- path = g_file.get_path()
3347- if self.is_video(path) and not self.is_hidden(path, blacklist):
3348- item.append(self.get_name(path))
3349- item.append('')
3350- item.append(uri.encode("utf-8"))
3351- item.append(self.get_icon(path))
3352- item.append(CAT_INDEX_MY_VIDEOS)
3353- result_list.append(item)
3354- elif uri.startswith('http'):
3355- # If the file is distant, we take
3356- # all we need from Zeitgeist
3357- # this one can be any unicode string:
3358- item.append(event.get_subjects()[0].text.encode("utf-8"))
3359- item.append('')
3360- # these two *should* be ascii, but it can't hurt to be safe
3361- item.append(event.get_subjects()[0].uri.encode("utf-8"))
3362- item.append(event.get_subjects()[0].storage.encode("utf-8"))
3363- item.append(CAT_INDEX_ONLINE)
3364- result_list.append(item)
3365- search_status.props.results_model.clear ()
3366- for i in result_list:
3367- title = str(i[0])
3368- comment = str(i[1])
3369- uri = str(i[2])
3370- dnd_uri = str(i[2])
3371- icon_hint = str(i[3])
3372- category = i[4]
3373- self._scope.props.results_model.append(uri, icon_hint,
3374- category, "text/html", title, comment, dnd_uri)
3375-
3376- self.update_results_model("", search_status.props.results_model, 'lens', cancellable, search_status, False)
3377-
3378-# pylint: enable=R0903
3379-
3380-def main():
3381- """Connect to the session bus, exit if there is a running instance."""
3382- try:
3383- session_bus_connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
3384- session_bus = Gio.DBusProxy.new_sync(session_bus_connection, 0, None,
3385- 'org.freedesktop.DBus',
3386- '/org/freedesktop/DBus',
3387- 'org.freedesktop.DBus', None)
3388- result = session_bus.call_sync('RequestName',
3389- GLib.Variant("(su)", (BUS_NAME, 0x4)),
3390- 0, -1, None)
3391-
3392- # Unpack variant response with signature "(u)". 1 means we got it.
3393- result = result.unpack()[0]
3394-
3395- if result != 1:
3396- print >> sys.stderr, "Failed to own name %s. Bailing out." % BUS_NAME
3397- raise SystemExit(1)
3398- except:
3399- raise SystemExit(1)
3400-
3401- daemon = Daemon()
3402- GObject.MainLoop().run()
3403-
3404-if __name__ == "__main__":
3405- main()
3406
3407=== added file 'src/utils.vala'
3408--- src/utils.vala 1970-01-01 00:00:00 +0000
3409+++ src/utils.vala 2013-02-19 17:19:01 +0000
3410@@ -0,0 +1,77 @@
3411+/*
3412+ * Copyright (C) 2012 Canonical Ltd
3413+ *
3414+ * This program is free software: you can redistribute it and/or modify
3415+ * it under the terms of the GNU General Public License version 3 as
3416+ * published by the Free Software Foundation.
3417+ *
3418+ * This program is distributed in the hope that it will be useful,
3419+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3420+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3421+ * GNU General Public License for more details.
3422+ *
3423+ * You should have received a copy of the GNU General Public License
3424+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3425+ *
3426+ * Authored by Pawel Stolowski <pawel.stolowski@canonical.com>
3427+ * based on python code by David Calle <davidc@framli.eu>
3428+ */
3429+
3430+namespace Unity.Utils
3431+{
3432+ public bool is_regular_file (string path)
3433+ {
3434+ var file = GLib.File.new_for_path (path);
3435+ if (file.query_exists (null))
3436+ return file.query_file_type (GLib.FileQueryInfoFlags.NONE, null) == GLib.FileType.REGULAR;
3437+ return false;
3438+ }
3439+
3440+ public bool is_video (string path) throws Error
3441+ {
3442+ var file = GLib.File.new_for_path (path);
3443+ if (file.query_exists (null))
3444+ {
3445+ if (file.query_file_type (GLib.FileQueryInfoFlags.NONE, null) == GLib.FileType.REGULAR)
3446+ {
3447+ var content_type = file.query_info ("standard::content-type", GLib.FileQueryInfoFlags.NONE, null).get_content_type ();
3448+ return content_type.contains ("video");
3449+ }
3450+ }
3451+ return false;
3452+ }
3453+
3454+ private bool is_hidden (string path) throws Error
3455+ {
3456+ var file = GLib.File.new_for_path (path);
3457+ return file.query_info (GLib.FileAttribute.STANDARD_IS_HIDDEN, GLib.FileQueryInfoFlags.NONE, null).get_is_hidden ();
3458+ }
3459+
3460+ public string get_name (string path) throws Error
3461+ {
3462+ var file = GLib.File.new_for_path (path);
3463+ var finfo = file.query_info (GLib.FileAttribute.STANDARD_DISPLAY_NAME, GLib.FileQueryInfoFlags.NONE, null);
3464+ return finfo.get_attribute_as_string (GLib.FileAttribute.STANDARD_DISPLAY_NAME);
3465+ }
3466+
3467+ public uint gcd (uint a, uint b)
3468+ requires (a > 0 && b > 0)
3469+ ensures (result > 0)
3470+ {
3471+ for (;;)
3472+ {
3473+ if (a > b)
3474+ {
3475+ a = a % b;
3476+ if (a == 0)
3477+ return b;
3478+ }
3479+ else
3480+ {
3481+ b = b % a;
3482+ if (b == 0)
3483+ return a;
3484+ }
3485+ }
3486+ }
3487+}
3488\ No newline at end of file
3489
3490=== added file 'src/video-file.vala'
3491--- src/video-file.vala 1970-01-01 00:00:00 +0000
3492+++ src/video-file.vala 2013-02-19 17:19:01 +0000
3493@@ -0,0 +1,59 @@
3494+/*
3495+ * Copyright (C) 2012 Canonical Ltd
3496+ *
3497+ * This program is free software: you can redistribute it and/or modify
3498+ * it under the terms of the GNU General Public License version 3 as
3499+ * published by the Free Software Foundation.
3500+ *
3501+ * This program is distributed in the hope that it will be useful,
3502+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3503+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3504+ * GNU General Public License for more details.
3505+ *
3506+ * You should have received a copy of the GNU General Public License
3507+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3508+ *
3509+ * Authored by Pawel Stolowski <pawel.stolowski@canonical.com>
3510+ *
3511+ */
3512+
3513+namespace Unity.VideoLens
3514+{
3515+ public struct VideoFile
3516+ {
3517+ string title;
3518+ string lc_title;
3519+ string comment;
3520+ string uri;
3521+ string icon;
3522+ int category;
3523+ }
3524+
3525+ public struct RemoteVideoFile
3526+ {
3527+ string title;
3528+ string comment;
3529+ string uri;
3530+ string icon;
3531+ string details_uri;
3532+ string price;
3533+ int category;
3534+ }
3535+
3536+public struct RemoteVideoDetails
3537+ {
3538+ string title;
3539+ string description;
3540+ string uri;
3541+ string image;
3542+ string source;
3543+ string release_date;
3544+ int duration;
3545+ string[] directors;
3546+ string starring;
3547+ string[] genres;
3548+ string uploaded_by;
3549+ string date_uploaded;
3550+ string price;
3551+ }
3552+}
3553\ No newline at end of file
3554
3555=== added directory 'tests/unit'
3556=== added file 'tests/unit/Makefile.am'
3557--- tests/unit/Makefile.am 1970-01-01 00:00:00 +0000
3558+++ tests/unit/Makefile.am 2013-02-19 17:19:01 +0000
3559@@ -0,0 +1,120 @@
3560+include $(top_srcdir)/Makefile.decl
3561+
3562+check_PROGRAMS = \
3563+ test-locate \
3564+ test-utils \
3565+ test-ubuntu-video-search \
3566+ $(NULL)
3567+
3568+TEST_PROGS += $(check_PROGRAMS)
3569+
3570+AM_VALAFLAGS = \
3571+ --pkg dee-1.0 \
3572+ --pkg unity \
3573+ --pkg unity-extras \
3574+ --pkg gio-2.0 \
3575+ --pkg gio-unix-2.0 \
3576+ --pkg json-glib-1.0 \
3577+ --pkg glib-2.0 \
3578+ --pkg zeitgeist-1.0 \
3579+ --pkg libsoup-gnome-2.4 \
3580+ --pkg libsoup-2.4 \
3581+ --vapidir $(srcdir) \
3582+ --vapidir $(top_srcdir)/vapi \
3583+ --target-glib=2.26 \
3584+ $(MAINTAINER_VALAFLAGS) \
3585+ $(NULL)
3586+
3587+LDADD = $(LENS_DAEMON_LIBS) \
3588+ $(test_libs)
3589+
3590+AM_CPPFLAGS = \
3591+ $(LENS_DAEMON_CFLAGS) \
3592+ -I$(srcdir) \
3593+ -I$(top_srcdir)/src \
3594+ -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
3595+ $(COVERAGE_CFLAGS) \
3596+ $(NULL)
3597+
3598+AM_LDFLAGS = $(COVERAGE_LDFLAGS)
3599+
3600+if !ENABLE_C_WARNINGS
3601+ AM_CPPFLAGS += -w
3602+endif
3603+
3604+test_locate_VALASOURCES = \
3605+ test-locate.vala \
3606+ thumbnailer-mock.vala \
3607+ config-tests.vala \
3608+ $(top_srcdir)/src/locate.vala \
3609+ $(top_srcdir)/src/utils.vala \
3610+ $(top_srcdir)/src/video-file.vala \
3611+ $(NULL)
3612+
3613+test_utils_VALASOURCES = \
3614+ test-utils.vala \
3615+ config-tests.vala \
3616+ $(top_srcdir)/src/utils.vala \
3617+ $(NULL)
3618+
3619+test_ubuntu_video_search_VALASOURCES = \
3620+ test-ubuntu-video-search.vala \
3621+ config-tests.vala \
3622+ $(top_srcdir)/src/remote-scope-globals.vala \
3623+ $(top_srcdir)/src/ubuntu-video-search.vala \
3624+ $(top_srcdir)/src/video-file.vala \
3625+ $(top_srcdir)/src/remote-scope.vala \
3626+ $(top_srcdir)/src/remote-uri.vala \
3627+ $(top_srcdir)/src/utils.vala \
3628+ $(NULL)
3629+
3630+nodist_test_locate_SOURCES = $(test_locate_VALASOURCES:.vala=.c)
3631+
3632+nodist_test_utils_SOURCES = $(test_utils_VALASOURCES:.vala=.c)
3633+
3634+nodist_test_ubuntu_video_search_SOURCES = $(test_ubuntu_video_search_VALASOURCES:.vala=.c)
3635+
3636+CLEANFILES = *.stamp \
3637+ *.c \
3638+ $(NULL)
3639+
3640+EXTRA_DIST = \
3641+ $(test_locate_VALASOURCES) \
3642+ $(test_utils_VALASOURCES) \
3643+ $(test_ubuntu_video_search_VALASOURCES) \
3644+ data/videosearch_input1.txt \
3645+ data/videosearch_input2.txt \
3646+ data/videosearch_details1.txt \
3647+ data/video1.avi \
3648+ data/video2.avi \
3649+ data/video3.avi \
3650+ data/video1.mpg \
3651+ data/video2.mpg \
3652+ data/video3.mpg \
3653+ $(NULL)
3654+
3655+BUILT_SOURCES = \
3656+ test-locate.vala.stamp \
3657+ test-utils.vala.stamp \
3658+ test-ubuntu-video-search.vala.stamp \
3659+ $(NULL)
3660+
3661+test-locate.vala.stamp: $(test_locate_VALASOURCES)
3662+ $(AM_V_GEN)$(VALAC) -C $(AM_VALAFLAGS) $(VALAFLAGS) $^
3663+ @touch $@
3664+
3665+test-utils.vala.stamp: $(test_utils_VALASOURCES)
3666+ $(AM_V_GEN)$(VALAC) -C $(AM_VALAFLAGS) $(VALAFLAGS) $^
3667+ @touch $@
3668+
3669+test-ubuntu-video-search.vala.stamp: $(test_ubuntu_video_search_VALASOURCES)
3670+ $(AM_V_GEN)$(VALAC) -C $(AM_VALAFLAGS) $(VALAFLAGS) $^
3671+ @touch $@
3672+
3673+# START HEADLESS TESTS
3674+if ENABLE_HEADLESS_TESTS
3675+test-headless:
3676+ $(XVFB) make test-nonrecursive; \
3677+ sleep 1;
3678+endif
3679+# END HEADLESS TESTS
3680
3681=== added file 'tests/unit/config-tests.vala.in'
3682--- tests/unit/config-tests.vala.in 1970-01-01 00:00:00 +0000
3683+++ tests/unit/config-tests.vala.in 2013-02-19 17:19:01 +0000
3684@@ -0,0 +1,6 @@
3685+namespace Config {
3686+ const string TESTDATADIR = "@abs_top_srcdir@/tests/unit/data";
3687+ const string LOCALEDIR = "@DATADIR@/locale";
3688+ const string PACKAGE = "@PACKAGE@";
3689+ const string VERSION = "@VERSION@";
3690+}
3691
3692=== added directory 'tests/unit/data'
3693=== added file 'tests/unit/data/video1.avi'
3694=== added file 'tests/unit/data/video1.mpg'
3695=== added file 'tests/unit/data/video2.avi'
3696=== added file 'tests/unit/data/video2.mpg'
3697=== added file 'tests/unit/data/video3.avi'
3698=== added file 'tests/unit/data/video3.mpg'
3699=== added file 'tests/unit/data/videosearch_details1.txt'
3700--- tests/unit/data/videosearch_details1.txt 1970-01-01 00:00:00 +0000
3701+++ tests/unit/data/videosearch_details1.txt 2013-02-19 17:19:01 +0000
3702@@ -0,0 +1,26 @@
3703+{
3704+ "browser_url": "http://browserurl1",
3705+ "genres": [
3706+ "genre1",
3707+ "genre2"
3708+ ],
3709+ "description": "a movie",
3710+ "title": "title1",
3711+ "price": 1,
3712+ "source": "source1",
3713+ "directors": [
3714+ "director1",
3715+ "director2"
3716+ ],
3717+ "details": "http://detailsurl1",
3718+ "starring": [
3719+ "star1",
3720+ "star2"
3721+ ],
3722+ "uploaded_by": "uploader1",
3723+ "duration": "5477",
3724+ "release_date": "2010-04-01T07:00:00.000Z",
3725+ "image": "http://image1",
3726+ "date_uploaded": "2012-06-06T21:55:12.000Z",
3727+ "formatted_price": "$1"
3728+}
3729
3730=== added file 'tests/unit/data/videosearch_input1.txt'
3731--- tests/unit/data/videosearch_input1.txt 1970-01-01 00:00:00 +0000
3732+++ tests/unit/data/videosearch_input1.txt 2013-02-19 17:19:01 +0000
3733@@ -0,0 +1,44 @@
3734+{
3735+ "other": [
3736+ {
3737+ "url": "http://url0",
3738+ "source": "source0",
3739+ "img": "http://image0",
3740+ "title": "title0"
3741+ },
3742+ {
3743+ "url": "http://url1",
3744+ "source": "source1",
3745+ "details": "http://details1",
3746+ "img": "http://image1",
3747+ "title": "title1"
3748+ }
3749+ ],
3750+ "treats": [
3751+ {
3752+ "img": "http://image2",
3753+ "title": "title2",
3754+ "url": "http://url2",
3755+ "price": 1,
3756+ "source": "source2",
3757+ "details": "http://details2",
3758+ "formatted_price": "1 USD"
3759+ },
3760+ {
3761+ "img": "http://image3",
3762+ "title": "title3",
3763+ "url": "http://url3",
3764+ "price": 0,
3765+ "source": "source3",
3766+ "details": "http://details3",
3767+ "formatted_price": "free"
3768+ },
3769+ {
3770+ "img": "http://image4",
3771+ "title": "title4",
3772+ "url": "http://url4",
3773+ "source": "source4",
3774+ "details": "http://details4"
3775+ }
3776+ ]
3777+}
3778
3779=== added file 'tests/unit/data/videosearch_input2.txt'
3780--- tests/unit/data/videosearch_input2.txt 1970-01-01 00:00:00 +0000
3781+++ tests/unit/data/videosearch_input2.txt 2013-02-19 17:19:01 +0000
3782@@ -0,0 +1,17 @@
3783+[
3784+ {
3785+ "url": "http://url0",
3786+ "source": "source0",
3787+ "img": "http://image0",
3788+ "title": "title0"
3789+ },
3790+ {
3791+ "url": "http://url1",
3792+ "source": "source1",
3793+ "details": "http://details1",
3794+ "img": "http://image1",
3795+ "title": "title1",
3796+ "price": 1,
3797+ "formatted_price": "1 USD"
3798+ }
3799+]
3800
3801=== added file 'tests/unit/test-locate.vala'
3802--- tests/unit/test-locate.vala 1970-01-01 00:00:00 +0000
3803+++ tests/unit/test-locate.vala 2013-02-19 17:19:01 +0000
3804@@ -0,0 +1,57 @@
3805+/*
3806+ * Copyright (C) 2012 Canonical Ltd
3807+ *
3808+ * This program is free software: you can redistribute it and/or modify
3809+ * it under the terms of the GNU General Public License version 3 as
3810+ * published by the Free Software Foundation.
3811+ *
3812+ * This program is distributed in the hope that it will be useful,
3813+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3814+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3815+ * GNU General Public License for more details.
3816+ *
3817+ * You should have received a copy of the GNU General Public License
3818+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3819+ *
3820+ * Authored by Pawel Stolowski <pawel.stolowski@canonical.com>
3821+ */
3822+
3823+namespace Unity.VideoLens
3824+{
3825+ private Thumbnailer thumbnailer;
3826+ private Locate locate;
3827+ private string video_dir;
3828+ private string user_videos_folder;
3829+
3830+ public static int main (string[] args)
3831+ {
3832+ video_dir = Config.TESTDATADIR;
3833+ user_videos_folder = GLib.Environment.get_user_special_dir (GLib.UserDirectory.VIDEOS);
3834+
3835+ locate = new Locate ("cache", video_dir);
3836+ thumbnailer = new Thumbnailer ();
3837+
3838+ Test.init (ref args);
3839+ Test.add_data_func ("/Locate/ParseLocateResults", test_parse);
3840+ Test.add_data_func ("/Locate/LocateQueryString", test_query_string);
3841+ Test.run ();
3842+ return 0;
3843+ }
3844+
3845+ internal static void test_parse ()
3846+ {
3847+ string input = @"$video_dir/video1.avi\n$video_dir/video2.avi\n$video_dir/video1.mpg\n$video_dir/video2.mpg\n";
3848+ var results = locate.parse_locate_results (input, 1000, thumbnailer, (path) => { return true; });
3849+ assert (results.size == 4);
3850+
3851+ /* Filter out all results */
3852+ results = locate.parse_locate_results (input, 1000, thumbnailer, (path) => { return false; });
3853+ assert (results.size == 0);
3854+ }
3855+
3856+ internal static void test_query_string ()
3857+ {
3858+ assert (locate.locate_query_string ("foo") == video_dir + "*foo*");
3859+ assert (locate.locate_query_string ("foo bar") == video_dir + "*foo*bar*");
3860+ }
3861+}
3862\ No newline at end of file
3863
3864=== added file 'tests/unit/test-ubuntu-video-search.vala'
3865--- tests/unit/test-ubuntu-video-search.vala 1970-01-01 00:00:00 +0000
3866+++ tests/unit/test-ubuntu-video-search.vala 2013-02-19 17:19:01 +0000
3867@@ -0,0 +1,329 @@
3868+/*
3869+ * Copyright (C) 2012 Canonical Ltd
3870+ *
3871+ * This program is free software: you can redistribute it and/or modify
3872+ * it under the terms of the GNU General Public License version 3 as
3873+ * published by the Free Software Foundation.
3874+ *
3875+ * This program is distributed in the hope that it will be useful,
3876+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3877+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3878+ * GNU General Public License for more details.
3879+ *
3880+ * You should have received a copy of the GNU General Public License
3881+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3882+ *
3883+ * Authored by Pawel Stolowski <pawel.stolowski@canonical.com>
3884+ *
3885+ */
3886+
3887+namespace Unity.VideoLens
3888+{
3889+ public static int main (string[] args)
3890+ {
3891+ /* Sort up locale to get translations but also sorting and
3892+ * punctuation right */
3893+ GLib.Intl.textdomain (Config.PACKAGE);
3894+ GLib.Intl.bindtextdomain (Config.PACKAGE, Config.LOCALEDIR);
3895+ GLib.Intl.bind_textdomain_codeset (Config.PACKAGE, "UTF-8");
3896+ GLib.Intl.setlocale(GLib.LocaleCategory.ALL, "");
3897+
3898+ Test.init (ref args);
3899+ Test.add_data_func ("/UbuntuVideoSearch/ProcessSourcesResults", test_process_sources_results);
3900+ Test.add_data_func ("/UbuntuVideoSearch/ProcessInvalidSourcesResults", test_process_invalid_sources_results);
3901+ Test.add_data_func ("/UbuntuVideoSearch/BuildSearchUri", test_build_search_uri);
3902+ Test.add_data_func ("/UbuntuVideoSearch/ProcessInvalidSearchResultsJson", test_invalid_search_results_json);
3903+ Test.add_data_func ("/UbuntuVideoSearch/ProcessSearchResults", test_process_search_results);
3904+ Test.add_data_func ("/UbuntuVideoSearch/ProcessSearchResultsTreatYourself", test_process_search_no_split_ty_category);
3905+ Test.add_data_func ("/UbuntuVideoSearch/ProcessSearchResultsOnline", test_process_search_no_split_online_category);
3906+ Test.add_data_func ("/UbuntuVideoSearch/JoinArray", test_join_array);
3907+ Test.add_data_func ("/UbuntuVideoSearch/ProcessInvalidDetailsResults", test_process_invalid_details_results);
3908+ Test.add_data_func ("/UbuntuVideoSearch/ProcessDetailsResults", test_process_details_results);
3909+
3910+ Test.run ();
3911+
3912+ return 0;
3913+ }
3914+
3915+ internal static void test_process_sources_results ()
3916+ {
3917+ assert (UbuntuVideoSearch.process_sources_results ("[]").size == 0);
3918+
3919+ var sources = UbuntuVideoSearch.process_sources_results ("[\"Foo\", \"Bar\"]");
3920+ assert (sources.size == 2);
3921+ assert (sources[0] == "Foo");
3922+ assert (sources[1] == "Bar");
3923+ }
3924+
3925+ internal static void test_process_invalid_sources_results ()
3926+ {
3927+ bool got_excp = false;
3928+ try
3929+ {
3930+ UbuntuVideoSearch.process_sources_results (";");
3931+ }
3932+ catch (Error e)
3933+ {
3934+ got_excp = true;
3935+ }
3936+ assert (got_excp == true);
3937+ }
3938+
3939+ internal static void test_build_search_uri ()
3940+ {
3941+ // test null sources
3942+ assert (UbuntuVideoSearch.build_search_uri ("foo", null) == "http://videosearch.ubuntu.com/v0/search?q=foo&split=true");
3943+
3944+ // test empty sources list
3945+ var sources = new Gee.ArrayList<string> (null);
3946+ assert (UbuntuVideoSearch.build_search_uri ("foo", sources) == "http://videosearch.ubuntu.com/v0/search?q=foo&split=true");
3947+
3948+ // test non-empty sources and uri escaping
3949+ sources.add ("Amazon");
3950+ sources.add ("BBC");
3951+ assert (UbuntuVideoSearch.build_search_uri ("foo!", sources) == "http://videosearch.ubuntu.com/v0/search?q=foo%21&split=true&sources=Amazon,BBC");
3952+ }
3953+
3954+ internal static void test_invalid_search_results_json ()
3955+ {
3956+ // ignore warnings
3957+ Test.log_set_fatal_handler (() => { return false; });
3958+
3959+ bool got_excp = false;
3960+ try
3961+ {
3962+ UbuntuVideoSearch.process_search_results (",", false);
3963+ }
3964+ catch (Error e)
3965+ {
3966+ got_excp = true;
3967+ }
3968+ assert (got_excp == true);
3969+ }
3970+
3971+ /**
3972+ * Test search results of search query with 'split=true' flag, resulting in search results
3973+ * being placed in CAT_INDEX_MORE (if from 'treats' results group) and CAT_INDEX_ONLINE category (if from 'other' group).
3974+ */
3975+ internal static void test_process_search_results ()
3976+ {
3977+ string datadir = Config.TESTDATADIR;
3978+ uint8[] contents;
3979+ assert (File.new_for_path (@"$datadir/videosearch_input1.txt").load_contents (null, out contents, null) == true);
3980+
3981+ var results = UbuntuVideoSearch.process_search_results ((string)contents, false);
3982+ assert (results.size == 5);
3983+
3984+ bool got_video[5] = {false, false, false, false, false};
3985+
3986+ // verify that all expected records are there
3987+ foreach (var res in results)
3988+ {
3989+ if (res.title == "title0")
3990+ {
3991+ got_video[0] = true;
3992+ assert (res.comment == "source0");
3993+ assert (res.uri == "http://url0");
3994+ assert (res.icon == "http://image0");
3995+ assert (res.details_uri == "");
3996+ assert (res.price == null);
3997+ assert (res.category == CAT_INDEX_ONLINE);
3998+ }
3999+ else if (res.title == "title1")
4000+ {
4001+ got_video[1] = true;
4002+ assert (res.comment == "source1");
4003+ assert (res.uri == "http://url1");
4004+ assert (res.icon == "http://image1");
4005+ assert (res.details_uri == "http://details1");
4006+ assert (res.price == null);
4007+ assert (res.category == CAT_INDEX_ONLINE);
4008+ }
4009+ else if (res.title == "title2")
4010+ {
4011+ got_video[2] = true;
4012+ assert (res.comment == "source2");
4013+ assert (res.uri == "http://url2");
4014+ assert (res.icon == "http://image2");
4015+ assert (res.details_uri == "http://details2");
4016+ assert (res.price == "1 USD");
4017+ assert (res.category == CAT_INDEX_MORE);
4018+ }
4019+ else if (res.title == "title3")
4020+ {
4021+ got_video[3] = true;
4022+ assert (res.comment == "source3");
4023+ assert (res.uri == "http://url3");
4024+ assert (res.icon == "http://image3");
4025+ assert (res.details_uri == "http://details3");
4026+ assert (res.price == _("Free"));
4027+ assert (res.category == CAT_INDEX_MORE);
4028+ }
4029+ else if (res.title == "title4")
4030+ {
4031+ got_video[4] = true;
4032+ assert (res.comment == "source4");
4033+ assert (res.uri == "http://url4");
4034+ assert (res.icon == "http://image4");
4035+ assert (res.details_uri == "http://details4");
4036+ assert (res.price == _("Free")); // null price of video4 turned into 'free' for 'Treat yourself category'
4037+ assert (res.category == CAT_INDEX_MORE);
4038+ }
4039+ else
4040+ {
4041+ assert (1 == 0); // this shouldn't happen
4042+ }
4043+ }
4044+
4045+ assert (got_video[0] == true);
4046+ assert (got_video[1] == true);
4047+ assert (got_video[2] == true);
4048+ assert (got_video[3] == true);
4049+ assert (got_video[4] == true);
4050+ }
4051+
4052+ /**
4053+ * Test search results of search query with no 'split' option and is_treat_yourself=true, resulting in all search results
4054+ * being placed in CAT_INDEX_MORE category.
4055+ */
4056+ internal static void test_process_search_no_split_ty_category ()
4057+ {
4058+ string datadir = Config.TESTDATADIR;
4059+ uint8[] contents;
4060+ assert (File.new_for_path (@"$datadir/videosearch_input2.txt").load_contents (null, out contents, null) == true);
4061+
4062+ var results = UbuntuVideoSearch.process_search_results ((string)contents, /* is_treat_yourself */ true);
4063+ assert (results.size == 2);
4064+
4065+ bool got_video[2] = {false, false};
4066+
4067+ // verify that all expected records are there
4068+ foreach (var res in results)
4069+ {
4070+ if (res.title == "title0")
4071+ {
4072+ got_video[0] = true;
4073+ assert (res.comment == "source0");
4074+ assert (res.uri == "http://url0");
4075+ assert (res.icon == "http://image0");
4076+ assert (res.details_uri == "");
4077+ assert (res.price == _("Free"));
4078+ assert (res.category == CAT_INDEX_MORE);
4079+ }
4080+ else if (res.title == "title1")
4081+ {
4082+ got_video[1] = true;
4083+ assert (res.comment == "source1");
4084+ assert (res.uri == "http://url1");
4085+ assert (res.icon == "http://image1");
4086+ assert (res.details_uri == "http://details1");
4087+ assert (res.price == "1 USD");
4088+ assert (res.category == CAT_INDEX_MORE);
4089+ }
4090+ else
4091+ {
4092+ assert (1 == 0); // this shouldn't happen
4093+ }
4094+ }
4095+
4096+ assert (got_video[0] == true);
4097+ assert (got_video[1] == true);
4098+ }
4099+
4100+ /**
4101+ * Test search results of search query with no 'split' option and is_treat_yourself=false, resulting in all search results
4102+ * being placed in CAT_INDEX_ONLINE
4103+ */
4104+ internal static void test_process_search_no_split_online_category ()
4105+ {
4106+ string datadir = Config.TESTDATADIR;
4107+ uint8[] contents;
4108+ assert (File.new_for_path (@"$datadir/videosearch_input2.txt").load_contents (null, out contents, null) == true);
4109+
4110+ var results = UbuntuVideoSearch.process_search_results ((string)contents, /* is_treat_yourself */ false);
4111+ assert (results.size == 2);
4112+
4113+ bool got_video[2] = {false, false};
4114+
4115+ // verify that all expected records are there
4116+ foreach (var res in results)
4117+ {
4118+ if (res.title == "title0")
4119+ {
4120+ got_video[0] = true;
4121+ assert (res.comment == "source0");
4122+ assert (res.uri == "http://url0");
4123+ assert (res.icon == "http://image0");
4124+ assert (res.details_uri == "");
4125+ assert (res.price == null);
4126+ assert (res.category == CAT_INDEX_ONLINE);
4127+ }
4128+ else if (res.title == "title1")
4129+ {
4130+ got_video[1] = true;
4131+ assert (res.comment == "source1");
4132+ assert (res.uri == "http://url1");
4133+ assert (res.icon == "http://image1");
4134+ assert (res.details_uri == "http://details1");
4135+ assert (res.price == null);
4136+ assert (res.category == CAT_INDEX_ONLINE);
4137+ }
4138+ else
4139+ {
4140+ assert (1 == 0); // this shouldn't happen
4141+ }
4142+ }
4143+
4144+ assert (got_video[0] == true);
4145+ assert (got_video[1] == true);
4146+ }
4147+
4148+ internal static void test_join_array ()
4149+ {
4150+ var array = new Json.Array ();
4151+ array.add_string_element ("abc");
4152+ array.add_string_element ("def");
4153+
4154+ assert (UbuntuVideoSearch.join_array (array, ", ") == "abc, def");
4155+ }
4156+
4157+ internal static void test_process_invalid_details_results ()
4158+ {
4159+ bool got_excp = false;
4160+ try
4161+ {
4162+ UbuntuVideoSearch.process_details_results (",");
4163+ }
4164+ catch (Error e)
4165+ {
4166+ got_excp = true;
4167+ }
4168+ assert (got_excp == true);
4169+ }
4170+
4171+ internal static void test_process_details_results ()
4172+ {
4173+ string datadir = Config.TESTDATADIR;
4174+ uint8[] contents;
4175+ assert (File.new_for_path (@"$datadir/videosearch_details1.txt").load_contents (null, out contents, null) == true);
4176+
4177+ var video = UbuntuVideoSearch.process_details_results ((string)contents);
4178+
4179+ assert (video.title == "title1");
4180+ assert (video.description == "a movie");
4181+ assert (video.image == "http://image1");
4182+ assert (video.directors.length == 2);
4183+ assert (video.directors[0] == "director1");
4184+ assert (video.directors[1] == "director2");
4185+ assert (video.duration == 91);
4186+ assert (video.genres.length == 2);
4187+ assert (video.genres[0] == "genre1");
4188+ assert (video.genres[1] == "genre2");
4189+ assert (video.starring == "star1, star2");
4190+ assert (video.source == "source1");
4191+ assert (video.release_date == "2010");
4192+ assert (video.uploaded_by == "uploader1");
4193+ assert (video.price == "$1");
4194+ assert (video.date_uploaded == "2012-06-06T21:55:12.000Z");
4195+ }
4196+}
4197
4198=== added file 'tests/unit/test-utils.vala'
4199--- tests/unit/test-utils.vala 1970-01-01 00:00:00 +0000
4200+++ tests/unit/test-utils.vala 2013-02-19 17:19:01 +0000
4201@@ -0,0 +1,60 @@
4202+/*
4203+ * Copyright (C) 2012 Canonical Ltd
4204+ *
4205+ * This program is free software: you can redistribute it and/or modify
4206+ * it under the terms of the GNU General Public License version 3 as
4207+ * published by the Free Software Foundation.
4208+ *
4209+ * This program is distributed in the hope that it will be useful,
4210+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4211+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4212+ * GNU General Public License for more details.
4213+ *
4214+ * You should have received a copy of the GNU General Public License
4215+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4216+ *
4217+ * Authored by Pawel Stolowski <pawel.stolowski@canonical.com>
4218+ */
4219+
4220+namespace Unity.VideoLens
4221+{
4222+ public static int main (string[] args)
4223+ {
4224+ Test.init (ref args);
4225+ Test.add_data_func ("/Utils/Gcd", test_gcd);
4226+ Test.add_data_func ("/Utils/IsRegularFile", test_is_regular_file);
4227+ Test.add_data_func ("/Utils/GetName", test_get_name);
4228+ Test.add_data_func ("/Utils/IsVideo", test_is_video);
4229+ Test.run ();
4230+ return 0;
4231+ }
4232+
4233+ internal static void test_gcd ()
4234+ {
4235+ assert (Utils.gcd (1, 1) == 1);
4236+ assert (Utils.gcd (2, 1) == 1);
4237+ assert (Utils.gcd (10, 2) == 2);
4238+ assert (Utils.gcd (2, 10) == 2);
4239+ assert (Utils.gcd (20, 15) == 5);
4240+ }
4241+
4242+ internal static void test_is_regular_file ()
4243+ {
4244+ assert (Utils.is_regular_file ("/etc/passwd") == true);
4245+ assert (Utils.is_regular_file ("/dev/null") == false);
4246+ assert (Utils.is_regular_file ("/non-existing-file") == false);
4247+ }
4248+
4249+ internal static void test_get_name ()
4250+ {
4251+ assert (Utils.get_name ("/etc/passwd") == "passwd");
4252+ }
4253+
4254+ internal static void test_is_video ()
4255+ {
4256+ var video_dir = Config.TESTDATADIR;
4257+
4258+ assert (Utils.is_video ("/etc/passwd") == false);
4259+ assert (Utils.is_video (@"$video_dir/video1.avi") == true);
4260+ }
4261+}
4262\ No newline at end of file
4263
4264=== added file 'tests/unit/thumbnailer-mock.vala'
4265--- tests/unit/thumbnailer-mock.vala 1970-01-01 00:00:00 +0000
4266+++ tests/unit/thumbnailer-mock.vala 2013-02-19 17:19:01 +0000
4267@@ -0,0 +1,29 @@
4268+/*
4269+ * Copyright (C) 2012 Canonical Ltd
4270+ *
4271+ * This program is free software: you can redistribute it and/or modify
4272+ * it under the terms of the GNU General Public License version 3 as
4273+ * published by the Free Software Foundation.
4274+ *
4275+ * This program is distributed in the hope that it will be useful,
4276+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4277+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4278+ * GNU General Public License for more details.
4279+ *
4280+ * You should have received a copy of the GNU General Public License
4281+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4282+ *
4283+ * Authored by Pawel Stolowski <pawel.stolowski@canonical.com>
4284+ *
4285+ */
4286+
4287+namespace Unity.VideoLens
4288+{
4289+ public class Thumbnailer
4290+ {
4291+ public string get_icon (string video_file)
4292+ {
4293+ return "mock-icon";
4294+ }
4295+ }
4296+}
4297\ No newline at end of file
4298
4299=== added directory 'vapi'
4300=== added file 'vapi/Makefile.am'
4301--- vapi/Makefile.am 1970-01-01 00:00:00 +0000
4302+++ vapi/Makefile.am 2013-02-19 17:19:01 +0000
4303@@ -0,0 +1,5 @@
4304+NULL =
4305+BUILT_SOURCES =
4306+CLEANFILES =
4307+EXTRA_DIST = libsoup-gnome-2.4.vapi
4308+
4309
4310=== added file 'vapi/libsoup-gnome-2.4.vapi'
4311--- vapi/libsoup-gnome-2.4.vapi 1970-01-01 00:00:00 +0000
4312+++ vapi/libsoup-gnome-2.4.vapi 2013-02-19 17:19:01 +0000
4313@@ -0,0 +1,8 @@
4314+[CCode (cprefix = "Soup", gir_namespace = "SoupGNOME", gir_version = "2.4", lower_case_cprefix = "soup_")]
4315+namespace SoupGNOME {
4316+ [CCode (cheader_filename = "libsoup/soup-gnome.h", type_id = "soup_proxy_resolver_gnome_get_type ()")]
4317+ public class ProxyResolverGNOME : Soup.ProxyResolverDefault, Soup.ProxyURIResolver, Soup.SessionFeature {
4318+ [CCode (has_construct_function = false)]
4319+ protected ProxyResolverGNOME ();
4320+ }
4321+}

Subscribers

People subscribed via source and target branches