Merge lp:~gerboland/qtmir/desktopFileReader into lp:qtmir/rtm-14.09

Proposed by Michał Sawicz
Status: Merged
Approved by: Gerry Boland
Approved revision: 268
Merged at revision: 270
Proposed branch: lp:~gerboland/qtmir/desktopFileReader
Merge into: lp:qtmir/rtm-14.09
Diff against target: 946 lines (+678/-158)
8 files modified
src/modules/Unity/Application/Application.pro (+3/-2)
src/modules/Unity/Application/desktopfilereader.cpp (+169/-115)
src/modules/Unity/Application/desktopfilereader.h (+22/-40)
src/modules/Unity/Application/gscopedpointer.h (+113/-0)
tests/modules/DesktopFileReader/DesktopFileReader.pro (+15/-0)
tests/modules/DesktopFileReader/calculator.desktop (+227/-0)
tests/modules/DesktopFileReader/desktopfilereader_test.cpp (+128/-0)
tests/modules/modules.pro (+1/-1)
To merge this branch: bzr merge lp:~gerboland/qtmir/desktopFileReader
Reviewer Review Type Date Requested Status
Gerry Boland (community) Approve
Review via email: mp+238114@code.launchpad.net

Commit message

Rewrite DesktopFileReader to use GDesktopAppInfo, enables reading localized keys

Description of the change

Rewrite DesktopFileReader to use GDesktopAppInfo, enables reading localized keys

 * Are there any related MPs required for this MP to build/function as expected? Please list.
N
 * Did you perform an exploratory manual test run of your code change and any related functionality?
Y
 * If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
N/A

To post a comment you must log in.
Revision history for this message
Gerry Boland (gerboland) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/modules/Unity/Application/Application.pro'
2--- src/modules/Unity/Application/Application.pro 2014-09-11 16:18:40 +0000
3+++ src/modules/Unity/Application/Application.pro 2014-10-13 09:06:32 +0000
4@@ -12,7 +12,7 @@
5 QMAKE_CXXFLAGS = -std=c++11 -Werror -Wall
6 QMAKE_LFLAGS = -std=c++11 -Wl,-no-undefined
7
8-PKGCONFIG += mirserver glib-2.0 process-cpp ubuntu-app-launch-2
9+PKGCONFIG += mirserver glib-2.0 gio-unix-2.0 process-cpp ubuntu-app-launch-2
10
11 INCLUDEPATH += ../../../platforms/mirserver ../../../common
12 LIBS += -L../../../platforms/mirserver -lqpa-mirserver
13@@ -65,7 +65,8 @@
14 session.h \
15 session_interface.h \
16 sessionmodel.h \
17- upstart/applicationcontroller.h
18+ upstart/applicationcontroller.h \
19+ gscopedpointer.h
20
21 LTTNG_TP_FILES += tracepoints.tp
22
23
24=== modified file 'src/modules/Unity/Application/desktopfilereader.cpp'
25--- src/modules/Unity/Application/desktopfilereader.cpp 2014-08-07 18:15:45 +0000
26+++ src/modules/Unity/Application/desktopfilereader.cpp 2014-10-13 09:06:32 +0000
27@@ -16,142 +16,196 @@
28
29 // local
30 #include "desktopfilereader.h"
31+#include "gscopedpointer.h"
32 #include "logging.h"
33
34 // Qt
35 #include <QFile>
36+#include <QLocale>
37+
38+// GIO
39+#include <gio/gdesktopappinfo.h>
40
41 namespace qtmir
42 {
43
44-DesktopFileReader::Factory::Factory()
45-{
46-}
47-
48-DesktopFileReader::Factory::~Factory()
49-{
50-}
51
52 DesktopFileReader* DesktopFileReader::Factory::createInstance(const QString &appId, const QFileInfo& fi)
53 {
54 return new DesktopFileReader(appId, fi);
55 }
56
57-// Retrieves the size of an array at compile time.
58-#define ARRAY_SIZE(a) \
59- ((sizeof(a) / sizeof(*(a))) / static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))))
60+typedef GObjectScopedPointer<GAppInfo> GAppInfoPointer;
61+
62+struct DesktopFileReaderPrivate
63+{
64+ DesktopFileReaderPrivate(DesktopFileReader *parent):
65+ q_ptr( parent )
66+ {}
67+
68+ QString getKey(const char *key) const
69+ {
70+ if (!loaded()) return QString();
71+
72+ return QString::fromUtf8(g_desktop_app_info_get_string((GDesktopAppInfo*)appInfo.data(), key));
73+ }
74+
75+ bool loaded() const
76+ {
77+ return !appInfo.isNull();
78+ }
79+
80+ DesktopFileReader * const q_ptr;
81+ Q_DECLARE_PUBLIC(DesktopFileReader)
82+
83+ QString appId;
84+ QString file;
85+ GAppInfoPointer appInfo; // GAppInfo is actually implemented by GDesktopAppInfo
86+};
87+
88
89 DesktopFileReader::DesktopFileReader(const QString &appId, const QFileInfo &desktopFile)
90- : appId_(appId)
91- , entries_(DesktopFileReader::kNumberOfEntries, "")
92+ : d_ptr(new DesktopFileReaderPrivate(this))
93 {
94- qCDebug(QTMIR_APPLICATIONS) << "DesktopFileReader::DesktopFileReader - this=" << this << "appId=" << appId;
95-
96- file_ = desktopFile.absoluteFilePath();
97- loaded_ = loadDesktopFile(file_);
98+ Q_D(DesktopFileReader);
99+
100+ d->appId = appId;
101+ d->file = desktopFile.absoluteFilePath();
102+ d->appInfo.reset((GAppInfo*) g_desktop_app_info_new_from_filename(d->file.toUtf8().constData()));
103+
104+ if (!d->loaded()) {
105+ if (!desktopFile.exists()) {
106+ qCWarning(QTMIR_APPLICATIONS) << "Desktop file for appId:" << appId << "at:" << d->file
107+ << "does not exist";
108+ } else {
109+ qCWarning(QTMIR_APPLICATIONS) << "Desktop file for appId:" << appId << "at:" << d->file
110+ << "is not valid - check its syntax, and that the binary specified"
111+ << "by the Exec line is installed!";
112+ }
113+ }
114 }
115
116 DesktopFileReader::~DesktopFileReader()
117 {
118- qCDebug(QTMIR_APPLICATIONS) << "DesktopFileReader::~DesktopFileReader";
119- entries_.clear();
120-}
121-
122-bool DesktopFileReader::loadDesktopFile(QString desktopFile)
123-{
124- qCDebug(QTMIR_APPLICATIONS) << "DesktopFileReader::loadDesktopFile - this=" << this << "desktopFile=" << desktopFile;
125-
126- if (this->file().isNull() || this->file().isEmpty()) {
127- qCritical() << "No desktop file found for appId:" << appId_;
128- return false;
129- }
130-
131- Q_ASSERT(desktopFile != NULL);
132- const struct { const char* const name; int size; unsigned int flag; } kEntryNames[] = {
133- { "Name=", sizeof("Name=") - 1, 1 << DesktopFileReader::kNameIndex },
134- { "Comment=", sizeof("Comment=") - 1, 1 << DesktopFileReader::kCommentIndex },
135- { "Icon=", sizeof("Icon=") - 1, 1 << DesktopFileReader::kIconIndex },
136- { "Exec=", sizeof("Exec=") - 1, 1 << DesktopFileReader::kExecIndex },
137- { "Path=", sizeof("Path=") - 1, 1 << DesktopFileReader::kPathIndex },
138- { "X-Ubuntu-StageHint=", sizeof("X-Ubuntu-StageHint=") - 1, 1 << DesktopFileReader::kStageHintIndex },
139- { "X-Ubuntu-Splash-Title=", sizeof("X-Ubuntu-Splash-Title=") - 1, 1 << DesktopFileReader::kSplashTitleIndex },
140- { "X-Ubuntu-Splash-Image=", sizeof("X-Ubuntu-Splash-Image=") - 1, 1 << DesktopFileReader::kSplashImageIndex },
141- { "X-Ubuntu-Splash-Show-Header=", sizeof("X-Ubuntu-Splash-Show-Header=") - 1, 1 << DesktopFileReader::kSplashShowHeaderIndex },
142- { "X-Ubuntu-Splash-Color=", sizeof("X-Ubuntu-Splash-Color=") - 1, 1 << DesktopFileReader::kSplashColorIndex },
143- { "X-Ubuntu-Splash-Color-Header=", sizeof("X-Ubuntu-Splash-Color-Header=") - 1, 1 << DesktopFileReader::kSplashColorHeaderIndex },
144- { "X-Ubuntu-Splash-Color-Footer=", sizeof("X-Ubuntu-Splash-Color-Footer=") - 1, 1 << DesktopFileReader::kSplashColorFooterIndex }
145- };
146- const unsigned int kAllEntriesMask =
147- (1 << DesktopFileReader::kNameIndex) | (1 << DesktopFileReader::kCommentIndex)
148- | (1 << DesktopFileReader::kIconIndex) | (1 << DesktopFileReader::kExecIndex)
149- | (1 << DesktopFileReader::kPathIndex) | (1 << DesktopFileReader::kStageHintIndex)
150- | (1 << DesktopFileReader::kSplashTitleIndex) | (1 << DesktopFileReader::kSplashImageIndex)
151- | (1 << DesktopFileReader::kSplashShowHeaderIndex) | (1 << DesktopFileReader::kSplashColorIndex)
152- | (1 << DesktopFileReader::kSplashColorHeaderIndex) | (1 << DesktopFileReader::kSplashColorFooterIndex);
153- const unsigned int kMandatoryEntriesMask =
154- (1 << DesktopFileReader::kNameIndex) | (1 << DesktopFileReader::kIconIndex)
155- | (1 << DesktopFileReader::kExecIndex);
156- const int kEntriesCount = ARRAY_SIZE(kEntryNames);
157- const int kBufferSize = 256;
158- static char buffer[kBufferSize];
159- QFile file(desktopFile);
160-
161- // Open file.
162- if (!file.open(QFile::ReadOnly | QIODevice::Text)) {
163- qWarning() << "Can't open file:" << file.errorString();
164- return false;
165- }
166-
167- // Validate "magic key" (standard group header).
168- if (file.readLine(buffer, kBufferSize) != -1) {
169- if (strncmp(buffer, "[Desktop Entry]", sizeof("[Desktop Entry]") - 1)) {
170- qWarning() << "not a desktop file, unable to read it";
171- return false;
172- }
173- }
174-
175- int length;
176- unsigned int entryFlags = 0;
177- while ((length = file.readLine(buffer, kBufferSize)) != -1) {
178- // Skip empty lines.
179- if (length > 1) {
180- // Stop when reaching unsupported next group header.
181- if (buffer[0] == '[') {
182- qWarning() << "reached next group header, leaving loop";
183- break;
184- }
185- // Lookup entries ignoring duplicates if any.
186- for (int i = 0; i < kEntriesCount; i++) {
187- if (!strncmp(buffer, kEntryNames[i].name, kEntryNames[i].size)) {
188- if (~entryFlags & kEntryNames[i].flag) {
189- buffer[length-1] = '\0';
190- entries_[i] = QString::fromUtf8(&buffer[kEntryNames[i].size]);
191- entryFlags |= kEntryNames[i].flag;
192- break;
193- }
194- }
195- }
196- // Stop when matching the right number of entries.
197- if (entryFlags == kAllEntriesMask) {
198- break;
199- }
200- }
201- }
202-
203- // Check that the mandatory entries are set.
204- if ((entryFlags & kMandatoryEntriesMask) == kMandatoryEntriesMask) {
205- qDebug("loaded desktop file with name='%s', comment='%s', icon='%s', exec='%s', path='%s', stagehint='%s'",
206- qPrintable(entries_[DesktopFileReader::kNameIndex]),
207- qPrintable(entries_[DesktopFileReader::kCommentIndex]),
208- qPrintable(entries_[DesktopFileReader::kIconIndex]),
209- qPrintable(entries_[DesktopFileReader::kExecIndex]),
210- qPrintable(entries_[DesktopFileReader::kPathIndex]),
211- qPrintable(entries_[DesktopFileReader::kStageHintIndex]));
212- return true;
213- } else {
214- qWarning() << "not a valid desktop file, missing mandatory entries in the standard group header";
215- return false;
216- }
217+ delete d_ptr;
218+}
219+
220+QString DesktopFileReader::file() const
221+{
222+ Q_D(const DesktopFileReader);
223+ return d->file;
224+}
225+
226+QString DesktopFileReader::appId() const
227+{
228+ Q_D(const DesktopFileReader);
229+ return d->appId;
230+}
231+
232+QString DesktopFileReader::name() const
233+{
234+ Q_D(const DesktopFileReader);
235+ if (!d->loaded()) return QString();
236+
237+ return QString::fromUtf8(g_app_info_get_name(d->appInfo.data()));
238+}
239+
240+QString DesktopFileReader::comment() const
241+{
242+ Q_D(const DesktopFileReader);
243+ if (!d->loaded()) return QString();
244+
245+ return QString::fromUtf8(g_app_info_get_description(d->appInfo.data()));
246+}
247+
248+QString DesktopFileReader::icon() const
249+{
250+ Q_D(const DesktopFileReader);
251+ return d->getKey("Icon");
252+}
253+
254+QString DesktopFileReader::exec() const
255+{
256+ Q_D(const DesktopFileReader);
257+ if (!d->loaded()) return QString();
258+
259+ return QString::fromUtf8(g_app_info_get_commandline(d->appInfo.data()));
260+}
261+
262+QString DesktopFileReader::path() const
263+{
264+ Q_D(const DesktopFileReader);
265+ return d->getKey("Path");
266+}
267+
268+QString DesktopFileReader::stageHint() const
269+{
270+ Q_D(const DesktopFileReader);
271+ return d->getKey("X-Ubuntu-StageHint");
272+}
273+
274+QString DesktopFileReader::splashTitle() const
275+{
276+ Q_D(const DesktopFileReader);
277+ if (!d->loaded()) return QString();
278+
279+ /* Sadly GDesktopAppInfo only considers Name, GenericName, Comments and Keywords to be keys
280+ * which can have locale-specific entries. So we need to work to make X-Ubuntu-Splash-Title
281+ * locale-aware, by generating a locale-correct key name and seeing if that exists. If yes,
282+ * get the value and return it. Else fallback to the non-localized value.
283+ */
284+ GDesktopAppInfo *info = (GDesktopAppInfo*)d->appInfo.data();
285+ QLocale defaultLocale;
286+ QStringList locales = defaultLocale.uiLanguages();
287+
288+ QString keyTemplate("X-Ubuntu-Splash-Title[%1]");
289+ for (QString locale: locales) {
290+ // Desktop files use local specifiers with underscore separators but Qt uses hyphens
291+ locale = locale.replace('-', '_');
292+ const char* key = keyTemplate.arg(locale).toUtf8().constData();
293+ if (g_desktop_app_info_has_key(info, key)) {
294+ return d->getKey(key);
295+ }
296+ }
297+
298+ // Fallback to the non-localized string, if available
299+ return d->getKey("X-Ubuntu-Splash-Title");
300+}
301+
302+QString DesktopFileReader::splashImage() const
303+{
304+ Q_D(const DesktopFileReader);
305+ return d->getKey("X-Ubuntu-Splash-Image");
306+}
307+
308+QString DesktopFileReader::splashShowHeader() const
309+{
310+ Q_D(const DesktopFileReader);
311+ return d->getKey("X-Ubuntu-Splash-Show-Header");
312+}
313+
314+QString DesktopFileReader::splashColor() const
315+{
316+ Q_D(const DesktopFileReader);
317+ return d->getKey("X-Ubuntu-Splash-Color");
318+}
319+
320+QString DesktopFileReader::splashColorHeader() const
321+{
322+ Q_D(const DesktopFileReader);
323+ return d->getKey("X-Ubuntu-Splash-Color-Header");
324+}
325+
326+QString DesktopFileReader::splashColorFooter() const
327+{
328+ Q_D(const DesktopFileReader);
329+ return d->getKey("X-Ubuntu-Splash-Color-Footer");
330+}
331+
332+bool DesktopFileReader::loaded() const
333+{
334+ Q_D(const DesktopFileReader);
335+ return d->loaded();
336 }
337
338 } // namespace qtmir
339
340=== modified file 'src/modules/Unity/Application/desktopfilereader.h'
341--- src/modules/Unity/Application/desktopfilereader.h 2014-08-07 18:15:45 +0000
342+++ src/modules/Unity/Application/desktopfilereader.h 2014-10-13 09:06:32 +0000
343@@ -18,20 +18,19 @@
344 #define DESKTOPFILEREADER_H
345
346 #include <QString>
347-#include <QVector>
348 #include <QFileInfo>
349
350 namespace qtmir
351 {
352
353+class DesktopFileReaderPrivate;
354 class DesktopFileReader {
355 public:
356 class Factory
357 {
358 public:
359- Factory();
360+ Factory() = default;
361 Factory(const Factory&) = delete;
362- virtual ~Factory();
363
364 Factory& operator=(const Factory&) = delete;
365
366@@ -40,48 +39,31 @@
367
368 virtual ~DesktopFileReader();
369
370- virtual QString file() const { return file_; }
371- virtual QString appId() const { return appId_; }
372- virtual QString name() const { return entries_[kNameIndex]; }
373- virtual QString comment() const { return entries_[kCommentIndex]; }
374- virtual QString icon() const { return entries_[kIconIndex]; }
375- virtual QString exec() const { return entries_[kExecIndex]; }
376- virtual QString path() const { return entries_[kPathIndex]; }
377- virtual QString stageHint() const { return entries_[kStageHintIndex]; }
378- virtual QString splashTitle() const { return entries_[kSplashTitleIndex]; }
379- virtual QString splashImage() const { return entries_[kSplashImageIndex]; }
380- virtual QString splashShowHeader() const { return entries_[kSplashShowHeaderIndex]; }
381- virtual QString splashColor() const { return entries_[kSplashColorIndex]; }
382- virtual QString splashColorHeader() const { return entries_[kSplashColorHeaderIndex]; }
383- virtual QString splashColorFooter() const { return entries_[kSplashColorFooterIndex]; }
384- virtual bool loaded() const { return loaded_; }
385+ virtual QString file() const;
386+ virtual QString appId() const;
387+ virtual QString name() const;
388+ virtual QString comment() const;
389+ virtual QString icon() const;
390+ virtual QString exec() const;
391+ virtual QString path() const;
392+ virtual QString stageHint() const;
393+ virtual QString splashTitle() const;
394+ virtual QString splashImage() const;
395+ virtual QString splashShowHeader() const;
396+ virtual QString splashColor() const;
397+ virtual QString splashColorHeader() const;
398+ virtual QString splashColorFooter() const;
399+ virtual bool loaded() const;
400
401 protected:
402+ DesktopFileReader(const QString &appId, const QFileInfo &desktopFile);
403+
404+ DesktopFileReaderPrivate * const d_ptr;
405+
406 friend class DesktopFileReaderFactory;
407
408- DesktopFileReader(const QString &appId, const QFileInfo &desktopFile);
409-
410 private:
411- static const int kNameIndex = 0,
412- kCommentIndex = 1,
413- kIconIndex = 2,
414- kExecIndex = 3,
415- kPathIndex = 4,
416- kStageHintIndex = 5,
417- kSplashTitleIndex = 6,
418- kSplashImageIndex = 7,
419- kSplashShowHeaderIndex = 8,
420- kSplashColorIndex = 9,
421- kSplashColorHeaderIndex = 10,
422- kSplashColorFooterIndex = 11,
423- kNumberOfEntries = 12;
424-
425- virtual bool loadDesktopFile(QString desktopFile);
426-
427- QString appId_;
428- QString file_;
429- QVector<QString> entries_;
430- bool loaded_;
431+ Q_DECLARE_PRIVATE(DesktopFileReader)
432 };
433
434 } // namespace qtmir
435
436=== added file 'src/modules/Unity/Application/gscopedpointer.h'
437--- src/modules/Unity/Application/gscopedpointer.h 1970-01-01 00:00:00 +0000
438+++ src/modules/Unity/Application/gscopedpointer.h 2014-10-13 09:06:32 +0000
439@@ -0,0 +1,113 @@
440+/*
441+ * Copyright (C) 2011-2014 Canonical, Ltd.
442+ *
443+ * This program is free software: you can redistribute it and/or modify it under
444+ * the terms of the GNU Lesser General Public License version 3, as published by
445+ * the Free Software Foundation.
446+ *
447+ * This program is distributed in the hope that it will be useful, but WITHOUT
448+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
449+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
450+ * Lesser General Public License for more details.
451+ *
452+ * You should have received a copy of the GNU Lesser General Public License
453+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
454+ *
455+ * Authors:
456+ * - Aurélien Gâteau <aurelien.gateau@canonical.com>
457+ */
458+
459+#ifndef GSCOPEDPOINTER_H
460+#define GSCOPEDPOINTER_H
461+
462+// Local
463+
464+// Qt
465+#include <QScopedPointer>
466+
467+// GLib
468+#include <glib-object.h>
469+
470+/**
471+ * Helper class for GScopedPointer
472+ */
473+template <class T, void (*cleanup_fcn)(T*)>
474+class GScopedPointerDeleter
475+{
476+public:
477+ static void cleanup(T* ptr)
478+ {
479+ if (ptr) {
480+ cleanup_fcn(ptr);
481+ }
482+ }
483+};
484+
485+/**
486+ * A GScopedPointer works like a QScopedPointer, except the cleanup static
487+ * method is replaced with a cleanup function, making it useful to handle C
488+ * structs whose cleanup function prototype is "void cleanup(T*)"
489+ *
490+ * Best way to use it is to define a typedef for your C struct, like this:
491+ *
492+ * typedef GScopedPointer<Foo, foo_free> GFooPointer;
493+ */
494+template <class T, void (*cleanup)(T*)>
495+class GScopedPointer : public QScopedPointer<T, GScopedPointerDeleter<T, cleanup> >
496+{
497+public:
498+ GScopedPointer(T* ptr = 0)
499+ : QScopedPointer<T, GScopedPointerDeleter<T, cleanup> >(ptr)
500+ {}
501+};
502+
503+
504+/**
505+ * Helper class for GObjectScopedPointer
506+ */
507+template <class T, void (*cleanup_fcn)(gpointer)>
508+class GObjectScopedPointerDeleter
509+{
510+public:
511+ static void cleanup(T* ptr)
512+ {
513+ if (ptr) {
514+ cleanup_fcn(ptr);
515+ }
516+ }
517+};
518+
519+/**
520+ * A GObjectScopedPointer is similar to a GScopedPointer. The only difference
521+ * is its cleanup function signature is "void cleanup(gpointer)" and defaults to
522+ * g_object_unref(), making it useful for GObject-based classes.
523+ *
524+ * You can use it directly like this:
525+ *
526+ * GObjectScopedPointer<GFoo> foo;
527+ *
528+ * Or define a typedef for your class:
529+ *
530+ * typedef GObjectScopedPointer<GFoo> GFooPointer;
531+ *
532+ * Note: GObjectScopedPointer does *not* call gobject_ref() when assigned a
533+ * pointer.
534+ */
535+template <class T, void (*cleanup)(gpointer) = g_object_unref>
536+class GObjectScopedPointer : public QScopedPointer<T, GObjectScopedPointerDeleter<T, cleanup> >
537+{
538+public:
539+ GObjectScopedPointer(T* ptr = 0)
540+ : QScopedPointer<T, GObjectScopedPointerDeleter<T, cleanup> >(ptr)
541+ {}
542+};
543+
544+// A Generic GObject pointer
545+typedef GObjectScopedPointer<GObject> GObjectPointer;
546+
547+// Take advantage of the cleanup signature of GObjectScopedPointer to define
548+// a GCharPointer
549+typedef GObjectScopedPointer<gchar, g_free> GCharPointer;
550+
551+#endif /* GSCOPEDPOINTER_H */
552+
553
554=== added directory 'tests/modules/DesktopFileReader'
555=== added file 'tests/modules/DesktopFileReader/DesktopFileReader.pro'
556--- tests/modules/DesktopFileReader/DesktopFileReader.pro 1970-01-01 00:00:00 +0000
557+++ tests/modules/DesktopFileReader/DesktopFileReader.pro 2014-10-13 09:06:32 +0000
558@@ -0,0 +1,15 @@
559+include(../../test-includes.pri)
560+include(../common/common.pri)
561+
562+TARGET = desktopfilereader_test
563+
564+SOURCES += \
565+ desktopfilereader_test.cpp
566+
567+OTHER_FILES += \
568+ calculator.desktop
569+
570+# Copy to build directory
571+for(FILE, OTHER_FILES){
572+ QMAKE_POST_LINK += $$quote(cp $${PWD}/$${FILE} $${OUT_PWD}$$escape_expand(\\n\\t))
573+}
574
575=== added file 'tests/modules/DesktopFileReader/calculator.desktop'
576--- tests/modules/DesktopFileReader/calculator.desktop 1970-01-01 00:00:00 +0000
577+++ tests/modules/DesktopFileReader/calculator.desktop 2014-10-13 09:06:32 +0000
578@@ -0,0 +1,227 @@
579+[Desktop Entry]
580+Name=Calculator
581+Name[am]=መደመሪያ
582+Name[ar]=الآلة الحاسبة
583+Name[ast]=Calculadora
584+Name[az]=Kalkulyator
585+Name[be]=Калькулятар
586+Name[bg]=Калкулатор
587+Name[br]=Jederez
588+Name[bs]=Kalkulator
589+Name[ca]=Calculadora
590+Name[cs]=Kalkulačka
591+Name[cy]=Cyfrifiannell
592+Name[da]=Lommeregner
593+Name[de]=Taschenrechner
594+Name[el]=Αριθμομηχανή
595+Name[en_AU]=Calculator
596+Name[en_CA]=Calculator
597+Name[en_GB]=Calculator
598+Name[es]=Calculadora
599+Name[eu]=Kalkulagailua
600+Name[fa]=ماشین‌حساب
601+Name[fi]=Laskin
602+Name[fr]=Calculatrice
603+Name[gd]=An t-àireamhair
604+Name[gl]=Calculadora
605+Name[gu]=કેલ્ક્યુલેટર
606+Name[he]=מחשבון
607+Name[hi]=कैलकुलेटर
608+Name[hu]=Számológép
609+Name[id]=Kalkulator
610+Name[is]=Reiknivél
611+Name[it]=Calcolatrice
612+Name[ja]=電卓
613+Name[km]=ម៉ាស៊ីន​គិត​លេខ
614+Name[ko]=계산기
615+Name[lv]=Kalkulators
616+Name[mr]=गणनयंत्र
617+Name[ms]=Kalkulator
618+Name[my]=ဂဏန်းတွက်စက်
619+Name[nb]=Kalkulator
620+Name[ne]=क्याल्कुलेटर
621+Name[nl]=Rekenmachine
622+Name[pa]=ਕੈਲਕੂਲੇਟਰ
623+Name[pl]=Kalkulator
624+Name[pt]=Calculadora
625+Name[pt_BR]=Calculadora
626+Name[ro]=Calculator
627+Name[ru]=Калькулятор
628+Name[sa]=सङ्कलकम्
629+Name[shn]=ၸၢၵ်ႈၼပ့်သွၼ်
630+Name[sl]=Računalo
631+Name[sr]=Калкулатор
632+Name[sv]=Kalkylator
633+Name[ta]=கணிப்பான்
634+Name[te]=గణన పరికరం
635+Name[tr]=Hesap Makinesi
636+Name[ug]=ھېسابلىغۇچ
637+Name[uk]=Калькулятор
638+Name[zh_CN]=计算器
639+Name[zh_HK]=計數機
640+Name[zh_TW]=計算機
641+Comment=A simple calculator for Ubuntu.
642+Comment[am]=ለ ኡቡንቱ ቀላል መደመሪያ
643+Comment[ar]=آلة حاسبة بسيطة لأوبونتو.
644+Comment[ast]=Una calculadora cenciella pa Ubuntu.
645+Comment[az]=Ubuntu üçün sadә kalkulyator.
646+Comment[br]=Ur jederez eeun evit Ubuntu.
647+Comment[ca]=Una calculadora simple per a l'Ubuntu.
648+Comment[cy]=Cyfrifiannell hawdd ar gyfer Ubuntu.
649+Comment[da]=En simpel lommeregner til Ubuntu.
650+Comment[de]=Ein einfach Tachenrechner für Ubuntu.
651+Comment[el]=Μια απλή αριθμομηχανή για το Ubuntu
652+Comment[en_AU]=A simple calculator for Ubuntu.
653+Comment[en_GB]=A simple calculator for Ubuntu.
654+Comment[es]=Una calculadora sencilla para Ubuntu.
655+Comment[eu]=Ubunturako kalkulagailu sinplea.
656+Comment[fa]=یک ماشین حساب ساده برای اوبونتو.
657+Comment[fi]=Laskin Ubuntulle.
658+Comment[fr]=Une calculatrice simple pour Ubuntu.
659+Comment[gd]=Àireamhair simplidh airson Ubuntu.
660+Comment[gl]=Calculadora sinxela para Ubuntu.
661+Comment[he]=מחשבון פשוט לאובונטו.
662+Comment[hu]=Egyszerű számológép Ubuntuhoz.
663+Comment[is]=Einföld reiknivél
664+Comment[it]=Una semplice calcolatrice per Ubuntu.
665+Comment[ja]=Ubuntu用のシンプルな電卓です。
666+Comment[km]=ម៉ាស៊ីន​គិត​លេខ​ធម្មតា​សម្រាប់ Ubuntu ។
667+Comment[ko]=우분투를 위한 간단한 계산기
668+Comment[lv]=Vienkāršs kalkulators priekš Ubuntu
669+Comment[nb]=En enkel kalkulator for Ubuntu.
670+Comment[ne]=Ubuntu को लागि एक सरल कैलकुलेटर।
671+Comment[nl]=Een eenvoudige rekenmachine voor Ubuntu.
672+Comment[pa]=ਉਬੰਤੂ ਲਈ ਇੱਕ ਸਧਾਰਨ ਕੈਲਕੂਲੇਟਰ
673+Comment[pl]=Prosty kalkulator dla Ubuntu
674+Comment[pt]=Uma calculadora simples para o Ubuntu.
675+Comment[pt_BR]=Uma simples calculadora para Ubuntu
676+Comment[ro]=Un calculator simplu pentru Ubuntu.
677+Comment[ru]=Простой калькулятор для Ubuntu
678+Comment[sa]=Ubuntu इत्यस्मा एकं सरलं सङ्कलकम्।
679+Comment[sl]=Preprost kalkulator za Ubuntu.
680+Comment[sr]=Једноставан калкулатор за Убунту
681+Comment[sv]=En enkel kalkylator för Ubuntu.
682+Comment[tr]=Ubuntu için sade bir hesap makinesi.
683+Comment[ug]=ئاددىي ھېسابلىغۇچى
684+Comment[uk]=Простий калькулятор для Ubuntu.
685+Comment[zh_CN]=Ubuntu 简易计算器
686+Comment[zh_TW]=Ubuntu 簡易計算機。
687+Keywords=math;addition;subtraction;multiplication;division;
688+Keywords[am]=ሂሳብ;መደመሪያ;መቀነሻ;ማባዣ;ማካፈያ;
689+Keywords[ar]=رياضة;ياضيات;حساب;حسابات;جمع;طرح;ضرب;قسمة;
690+Keywords[ast]=matemática;suma;resta;multiplicación;división;
691+Keywords[az]=riyaziyyat;әlavә etmә;çıxma;vurma;bölmә;toplama;
692+Keywords[br]=matematikoù;sammadenn;lamadenn;lieskementadenn;rannadenn;
693+Keywords[ca]=suma;resta;calculadora;multiplica;divideix
694+Keywords[da]=matematik;plus;minus;gange;dividere;beregning;lommeregner;
695+Keywords[de]=Mathe;Mathematik;Multiplikation;Subtraktion;Addition;Division;
696+Keywords[en_AU]=math;addition;subtraction;multiplication;division;
697+Keywords[en_GB]=maths;addition;subtraction;multiplication;division;
698+Keywords[es]=matemática;suma;resta;multiplicación;división;
699+Keywords[eu]=matematikak;gehitu;kendu;biderkatu;zatitu;
700+Keywords[fa]=حساب;جمع;تفریق;ضرب;تقسیم
701+Keywords[fi]=math;addition;subtraction;multiplication;division;matematiikka;lisäys;vähennys;kertaus;jakaminen;
702+Keywords[fr]=math;addition;soustraction;multiplication;division;
703+Keywords[gd]=math;addition;subtraction;multiplication;division;matamataig;roinneadh;toirt air falbh;cur ris;iomadadh
704+Keywords[gl]=matemáticas;suma;resta;multiplicación;división;
705+Keywords[he]=מתמטיקה;חשבון;חיבור;חיסור;כפל;חילוק;
706+Keywords[hu]=számolás;összeadás;kivonás;szorzás;osztás;
707+Keywords[is]=reikna;samlagning;frádráttur;margföldun;deiling
708+Keywords[it]=matematica;addizioni;sottrazioni;moltiplicazioni;divisioni;
709+Keywords[ja]=math;addition;subtraction;multiplication;division;計算;電卓;足し算;引き算;かけ算;わり算;
710+Keywords[km]=math;addition;subtraction;multiplication;division;
711+Keywords[ko]=수학;덧셈;뺄셈;곱셈;나눗셈;
712+Keywords[nb]=matte;addisjon;subtraksjon;multiplikasjon;divisjon;
713+Keywords[ne]=गणित; जोड्; घटाउ; गुणन; विभाजन;
714+Keywords[nl]=math;addition;subtraction;multiplication;division;optellen;aftrekken;vermenigvuldigen;delen;
715+Keywords[pa]=ਹਿਸਾਬ;ਜੋੜ;ਘਟਾਉ;ਗੁਣਾ;ਭਾਗ;
716+Keywords[pl]=liczenie;dodawanie;odejmowanie;mnożenie;dzielenie;
717+Keywords[pt]=matemática;soma;subtracção;multiplicação;divisão;
718+Keywords[pt_BR]=matemática;adição;subtração;multiplicação;divisão
719+Keywords[ro]=matematică;adunare;scădere;înmulțire;împărțire
720+Keywords[ru]=математика;сложение;умножение;деление;
721+Keywords[sa]=गणितम्;योजनम्;वियोजनम्;गुणनम्;विभाजनम्;
722+Keywords[sl]=matematika;seštevanje;odštevanje;množenje;deljenje;
723+Keywords[sr]=математика;сабирање;одузимање;множење;дељење;рачунање;дигитрон;
724+Keywords[sv]=matematik;addition;substraktion;multiplikation;division
725+Keywords[tr]=matematik;ekleme;çıkarma;çarpma;bölme;toplama
726+Keywords[ug]=ماتېماتىكا;قوشۇش;ئېلىش;كۆپەيتىش;بۆلۈش;
727+Keywords[uk]=math;addition;subtraction;multiplication;division;математика;додавання;віднімання;множення;ділення;калькулятор;
728+Keywords[zh_CN]=数学;加;减;乘;除;
729+Keywords[zh_TW]=math;addition;subtraction;multiplication;division;數學;加;減;乘;除;
730+Type=Application
731+Terminal=false
732+Exec=yes -p com.ubuntu.calculator_calculator_1.3.329 -- qmlscene -qt5 ubuntu-calculator-app.qml
733+Icon=/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator/calculator-app@30.png
734+X-Ubuntu-Touch=true
735+X-Ubuntu-StageHint=SideStage
736+X-Ubuntu-Default-Department-ID=accessories
737+Path=/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator
738+X-Ubuntu-Old-Icon=calculator-app@30.png
739+X-Ubuntu-Application-ID=com.ubuntu.calculator_calculator_1.3.329
740+X-Ubuntu-Splash-Show-Header=True
741+X-Ubuntu-Splash-Color=#aabbcc
742+X-Ubuntu-Splash-Color-Header=purple
743+X-Ubuntu-Splash-Color-Footer=#deadbeefda
744+X-Ubuntu-Splash-Image=/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator/calculator-app@30.png
745+X-Ubuntu-Splash-Title=Calculator 2.0
746+X-Ubuntu-Splash-Title[am]=መደመሪያ 2.0
747+X-Ubuntu-Splash-Title[ar]=الآلة 2.0الحاسبة
748+X-Ubuntu-Splash-Title[ast]=Calculadora 2.0
749+X-Ubuntu-Splash-Title[az]=Kalkulyator 2.0
750+X-Ubuntu-Splash-Title[be]=Калькулятар 2.0
751+X-Ubuntu-Splash-Title[bg]=Калкулатор 2.0
752+X-Ubuntu-Splash-Title[br]=Jederez 2.0
753+X-Ubuntu-Splash-Title[bs]=Kalkulator 2.0
754+X-Ubuntu-Splash-Title[ca]=Calculadora 2.0
755+X-Ubuntu-Splash-Title[cs]=Kalkulačka 2.0
756+X-Ubuntu-Splash-Title[cy]=Cyfrifiannell 2.0
757+X-Ubuntu-Splash-Title[da]=Lommeregner 2.0
758+X-Ubuntu-Splash-Title[de]=Taschenrechner 2.0
759+X-Ubuntu-Splash-Title[el]=Αριθμομηχανή 2.0
760+X-Ubuntu-Splash-Title[en_AU]=Calculator 2.0
761+X-Ubuntu-Splash-Title[en_CA]=Calculator 2.0
762+X-Ubuntu-Splash-Title[en_GB]=Calculator 2.0
763+X-Ubuntu-Splash-Title[es]=Calculadora 2.0
764+X-Ubuntu-Splash-Title[eu]=Kalkulagailua 2.0
765+X-Ubuntu-Splash-Title[fa]= 2.0ماشین‌حساب
766+X-Ubuntu-Splash-Title[fi]=Laskin 2.0
767+X-Ubuntu-Splash-Title[fr]=Calculatrice 2.0
768+X-Ubuntu-Splash-Title[gd]=An t-àireamhair 2.0
769+X-Ubuntu-Splash-Title[gl]=Calculadora 2.0
770+X-Ubuntu-Splash-Title[gu]=કેલ્ક્યુલેટર 2.0
771+X-Ubuntu-Splash-Title[he]= 2.0מחשבון
772+X-Ubuntu-Splash-Title[hi]=कैलकुलेटर 2.0
773+X-Ubuntu-Splash-Title[hu]=Számológép 2.0
774+X-Ubuntu-Splash-Title[id]=Kalkulator 2.0
775+X-Ubuntu-Splash-Title[is]=Reiknivél 2.0
776+X-Ubuntu-Splash-Title[it]=Calcolatrice 2.0
777+X-Ubuntu-Splash-Title[ja]=電卓 2.0
778+X-Ubuntu-Splash-Title[km]=ម៉ាស៊ីន​គិត​លេខ 2.0
779+X-Ubuntu-Splash-Title[ko]=계산기 2.0
780+X-Ubuntu-Splash-Title[lv]=Kalkulators 2.0
781+X-Ubuntu-Splash-Title[mr]=गणनयंत्र 2.0
782+X-Ubuntu-Splash-Title[ms]=Kalkulator 2.0
783+X-Ubuntu-Splash-Title[my]=ဂဏန်းတွက်စက် 2.0
784+X-Ubuntu-Splash-Title[nb]=Kalkulator 2.0
785+X-Ubuntu-Splash-Title[ne]=क्याल्कुलेटर 2.0
786+X-Ubuntu-Splash-Title[nl]=Rekenmachine 2.0
787+X-Ubuntu-Splash-Title[pa]=ਕੈਲਕੂਲੇਟਰ 2.0
788+X-Ubuntu-Splash-Title[pl]=Kalkulator 2.0
789+X-Ubuntu-Splash-Title[pt]=Calculadora 2.0
790+X-Ubuntu-Splash-Title[pt_BR]=Calculadora 2.0
791+X-Ubuntu-Splash-Title[ro]=Calculator 2.0
792+X-Ubuntu-Splash-Title[ru]=Калькулятор 2.0
793+X-Ubuntu-Splash-Title[sa]=सङ्कलकम् 2.0
794+X-Ubuntu-Splash-Title[shn]=ၸၢၵ်ႈၼပ့်သွၼ် 2.0
795+X-Ubuntu-Splash-Title[sl]=Računalo 2.0
796+X-Ubuntu-Splash-Title[sr]=Калкулатор 2.0
797+X-Ubuntu-Splash-Title[sv]=Kalkylator 2.0
798+X-Ubuntu-Splash-Title[ta]=கணிப்பான் 2.0
799+X-Ubuntu-Splash-Title[te]=గణన పరికరం 2.0
800+X-Ubuntu-Splash-Title[tr]=Hesap Makinesi 2.0
801+X-Ubuntu-Splash-Title[ug]= 2.0ھېسابلىغۇچ
802+X-Ubuntu-Splash-Title[uk]=Калькулятор 2.0
803+X-Ubuntu-Splash-Title[zh_CN]=计算器 2.0
804+X-Ubuntu-Splash-Title[zh_HK]=計數機 2.0
805+X-Ubuntu-Splash-Title[zh_TW]=計算機 2.0
806
807=== added file 'tests/modules/DesktopFileReader/desktopfilereader_test.cpp'
808--- tests/modules/DesktopFileReader/desktopfilereader_test.cpp 1970-01-01 00:00:00 +0000
809+++ tests/modules/DesktopFileReader/desktopfilereader_test.cpp 2014-10-13 09:06:32 +0000
810@@ -0,0 +1,128 @@
811+/*
812+ * Copyright (C) 2014 Canonical, Ltd.
813+ *
814+ * This program is free software: you can redistribute it and/or modify it under
815+ * the terms of the GNU Lesser General Public License version 3, as published by
816+ * the Free Software Foundation.
817+ *
818+ * This program is distributed in the hope that it will be useful, but WITHOUT
819+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
820+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
821+ * Lesser General Public License for more details.
822+ *
823+ * You should have received a copy of the GNU Lesser General Public License
824+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
825+ *
826+ */
827+
828+#include <Unity/Application/desktopfilereader.h>
829+
830+#include <gtest/gtest.h>
831+
832+#include <QDir>
833+#include <QFileInfo>
834+#include <QLocale>
835+#include <QtGlobal>
836+#include <QString>
837+
838+using namespace qtmir;
839+
840+namespace {
841+ static void setLocale(const char *locale)
842+ {
843+ qputenv("LANGUAGE", locale);
844+ qputenv("LC_ALL", locale); // set these for GIO
845+ QLocale::setDefault(QString(locale)); // set for Qt
846+ }
847+}
848+
849+TEST(DesktopFileReader, testReadsDesktopFile)
850+{
851+ using namespace ::testing;
852+ setLocale("C");
853+
854+ QFileInfo fileInfo(QDir::currentPath() + "/calculator.desktop");
855+ DesktopFileReader::Factory readerFactory;
856+ DesktopFileReader *reader = readerFactory.createInstance("calculator", fileInfo);
857+
858+ EXPECT_EQ(reader->loaded(), true);
859+ EXPECT_EQ(reader->appId(), "calculator");
860+ EXPECT_EQ(reader->name(), "Calculator");
861+ EXPECT_EQ(reader->exec(), "yes -p com.ubuntu.calculator_calculator_1.3.329 -- qmlscene -qt5 ubuntu-calculator-app.qml");
862+ EXPECT_EQ(reader->icon(), "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator/calculator-app@30.png");
863+ EXPECT_EQ(reader->path(), "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator");
864+ EXPECT_EQ(reader->comment(), "A simple calculator for Ubuntu.");
865+ EXPECT_EQ(reader->stageHint(), "SideStage");
866+ EXPECT_EQ(reader->splashColor(), "#aabbcc");
867+ EXPECT_EQ(reader->splashColorFooter(), "#deadbeefda");
868+ EXPECT_EQ(reader->splashColorHeader(), "purple");
869+ EXPECT_EQ(reader->splashImage(), "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator/calculator-app@30.png");
870+ EXPECT_EQ(reader->splashShowHeader(), "True");
871+ EXPECT_EQ(reader->splashTitle(), "Calculator 2.0");
872+}
873+
874+TEST(DesktopFileReader, testReadsLocalizedDesktopFile)
875+{
876+ using namespace ::testing;
877+ setLocale("de");
878+
879+ QFileInfo fileInfo(QDir::currentPath() + "/calculator.desktop");
880+ DesktopFileReader::Factory readerFactory;
881+ DesktopFileReader *reader = readerFactory.createInstance("calculator", fileInfo);
882+
883+ EXPECT_EQ(reader->loaded(), true);
884+ EXPECT_EQ(reader->appId(), "calculator");
885+ EXPECT_EQ(reader->name(), "Taschenrechner");
886+ EXPECT_EQ(reader->exec(), "yes -p com.ubuntu.calculator_calculator_1.3.329 -- qmlscene -qt5 ubuntu-calculator-app.qml");
887+ EXPECT_EQ(reader->icon(), "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator/calculator-app@30.png");
888+ EXPECT_EQ(reader->path(), "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator");
889+ EXPECT_EQ(reader->comment(), "Ein einfach Tachenrechner für Ubuntu.");
890+ EXPECT_EQ(reader->stageHint(), "SideStage");
891+ EXPECT_EQ(reader->splashColor(), "#aabbcc");
892+ EXPECT_EQ(reader->splashColorFooter(), "#deadbeefda");
893+ EXPECT_EQ(reader->splashColorHeader(), "purple");
894+ EXPECT_EQ(reader->splashImage(), "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator/calculator-app@30.png");
895+ EXPECT_EQ(reader->splashShowHeader(), "True");
896+ EXPECT_EQ(reader->splashTitle(), "Taschenrechner 2.0");
897+}
898+
899+TEST(DesktopFileReader, testMissingDesktopFile)
900+{
901+ using namespace ::testing;
902+ setLocale("C");
903+
904+ QFileInfo fileInfo(QDir::currentPath() + "/missing.desktop");
905+ DesktopFileReader::Factory readerFactory;
906+ DesktopFileReader *reader = readerFactory.createInstance("calculator", fileInfo);
907+
908+ EXPECT_EQ(reader->loaded(), false);
909+ EXPECT_EQ(reader->appId(), "calculator");
910+ EXPECT_EQ(reader->name(), "");
911+ EXPECT_EQ(reader->exec(), "");
912+ EXPECT_EQ(reader->icon(), "");
913+ EXPECT_EQ(reader->path(), "");
914+ EXPECT_EQ(reader->comment(), "");
915+ EXPECT_EQ(reader->stageHint(), "");
916+ EXPECT_EQ(reader->splashColor(), "");
917+ EXPECT_EQ(reader->splashColorFooter(), "");
918+ EXPECT_EQ(reader->splashColorHeader(), "");
919+ EXPECT_EQ(reader->splashImage(), "");
920+ EXPECT_EQ(reader->splashShowHeader(), "");
921+ EXPECT_EQ(reader->splashTitle(), "");
922+}
923+
924+TEST(DesktopFileReader, testUTF8Characters)
925+{
926+ using namespace ::testing;
927+ setLocale("zh_CN");
928+
929+ QFileInfo fileInfo(QDir::currentPath() + "/calculator.desktop");
930+ DesktopFileReader::Factory readerFactory;
931+ DesktopFileReader *reader = readerFactory.createInstance("calculator", fileInfo);
932+
933+ EXPECT_EQ(reader->loaded(), true);
934+ EXPECT_EQ(reader->appId(), "calculator");
935+ EXPECT_EQ(reader->name(), "计算器");
936+ EXPECT_EQ(reader->comment(), "Ubuntu 简易计算器");
937+ EXPECT_EQ(reader->splashTitle(), "计算器 2.0");
938+}
939
940=== modified file 'tests/modules/modules.pro'
941--- tests/modules/modules.pro 2014-09-11 16:18:40 +0000
942+++ tests/modules/modules.pro 2014-10-13 09:06:32 +0000
943@@ -1,2 +1,2 @@
944 TEMPLATE = subdirs
945-SUBDIRS = ApplicationManager General MirSurfaceItem SessionManager TaskController
946+SUBDIRS = ApplicationManager General MirSurfaceItem SessionManager TaskController DesktopFileReader

Subscribers

People subscribed via source and target branches