I have made the following changes intended for : CE:Apps / apps-client Please review and accept or decline. BOSS has already run some checks on this request. See the "Messages from BOSS" section below.
https://build.pub.meego.com//request/show/4366 Thank You, - [This message was auto-generated] --- Request # 4366: Messages from BOSS: WARNING check_package_is_complete_sources (apps-client) failed: No dsc file foundExtra source files: boss.conf. State: review at 2012-02-16T09:43:45 by bossbot Reviews: accepted by bossbot : Prechecks succeeded. new for CE-maintainers : Please replace this text with a review and approve/reject the review (not the SR). BOSS will take care of the rest Changes: submit: home:xfade:NemoApps / apps-client -> CE:Apps / apps-client changes files: -------------- --- apps-client.changes +++ apps-client.changes @@ -0,0 +1,3 @@ +* Thu Feb 16 2012 Niels Breet <[email protected]> - 0.9.18 +- Add rating + old: ---- apps-client-0.9.16.tar.gz new: ---- apps-client-0.9.18.tar.gz boss.conf spec files: ----------- --- apps-client.spec +++ apps-client.spec @@ -1,13 +1,13 @@ # # Do NOT Edit the Auto-generated Part! -# Generated by: spectacle version 0.23 +# Generated by: spectacle version 0.22 # # >> macros # << macros Name: apps-client Summary: Apps for Nemo client -Version: 0.9.16 +Version: 0.9.18 Release: 1 Group: Applications/System License: GPLv2 other changes: -------------- ++++++ apps-client-0.9.16.tar.gz -> apps-client-0.9.18.tar.gz --- appsformeego/qml-harmattan/ApplicationPage.qml +++ appsformeego/qml-harmattan/ApplicationPage.qml @@ -26,7 +26,11 @@ for (var i = 0; i < comments.length; i++) { console.log("adding comment: " + comments[i]); - commentModel.append({"comment": comments[i]}); + var parts = gears.splitComment(comments[i]); + commentModel.append({"user": parts[0], + "date": parts[1], + "version": parts[2], + "comment": parts[3]}); } return commentModel; @@ -108,11 +112,28 @@ anchors.left: parent.left anchors.right: parent.right anchors.margins: 25 - height: 48 + height: 36 filled: "star-filled.png" unfilled: "star.png" maximum: 5 percents: gears.application.appRating + interactive: false + + MouseArea { + anchors.fill: parent + onClicked: { + if (gears.ocsProvider.loggedIn) + { + commentPage.haveComments = false; + pageStack.push(commentPage); + } + else + { + errorDialog.message = qsTr("Please login to rate this application."); + errorDialog.open(); + } + } + } } }// black half @@ -242,15 +263,11 @@ Button { id: btnComment width: parent.width - visible: gears.isLoggedIn + visible: gears.ocsProvider.loggedIn text: qsTr("Write a Comment") - Component { - id: commentPage - CommentPage { } - } - onClicked: { + commentPage.haveComments = true; pageStack.push(commentPage); } } @@ -265,13 +282,46 @@ Repeater { id: commentRepeater model: makeCommentModel(gears.application.appComments) - delegate: Text { - text: comment + delegate: Column { width: commentList.width - font.pixelSize: 20 - color: "#000000" - wrapMode: Text.WrapAtWordBoundaryOrAnywhere + spacing: 4 + + Text { + text: user + " (version " + version + ")" + width: parent.width + font.pixelSize: 20 + font.bold: true + color: "#313131" + } + + Rectangle { + radius: 6 + color: "#e0e0e0" + width: parent.width + height: childrenRect.height + 8 + + Text { + x: 4 + y: 4 + text: comment + width: parent.width - 8 + font.pixelSize: 20 + color: "#000000" + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + } + } + + Text { + text: date + width: parent.width + horizontalAlignment: Text.AlignRight + font.pixelSize: 16 + font.italic: true + color: "#313131" + } + } + } } @@ -299,6 +349,11 @@ } + CommentPage { + id: commentPage + } + + Page { id: screenshotsPage orientationLock: PageOrientation.LockPortrait @@ -351,4 +406,39 @@ } } } + + + // TODO: this dialog should be shared among the pages + Dialog { + id: errorDialog + property alias message: lblMessage.text + + title: Text { + width: parent.width + height: 72 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignTop + text: "" + color: "white" + font.pixelSize: 36 + font.bold: true + } + + content: Text { + id: lblMessage + width: parent.width + height: 72 + color: "white" + horizontalAlignment: Text.AlignHCenter + font.pixelSize: 24 + } + + buttons: Button { + text: "OK" + onClicked: { + errorDialog.accept(); + } + } + } + } --- appsformeego/qml-harmattan/CommentPage.qml +++ appsformeego/qml-harmattan/CommentPage.qml @@ -3,6 +3,8 @@ Page { + property bool haveComments: true + orientationLock: PageOrientation.LockPortrait anchors.fill: parent @@ -12,29 +14,69 @@ spacing: 12 - // TODO: place rating here + Label { + width: parent.width + font.bold: true + text: "Rating" + } + + Rater { + id: rater + + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 25 + height: 48 + filled: "star-filled.png" + unfilled: "star.png" + maximum: 5 + percents: 0 + interactive: true + + Text { + anchors.centerIn: parent + text: qsTr("unrated") + font.pixelSize: 32 + font.bold: true + font.italic: true + visible: parent.percents == 0 + } + } Label { width: parent.width font.bold: true - text: "Your Comment" + text: "Comment" + visible: haveComments } TextArea { id: commentBox width: parent.width height: 200 + placeholderText: qsTr("Please enter a comment") + visible: haveComments } Button { - id: btnSendComment + id: btnCommit width: parent.width - text: qsTr("Send Comment") + text: qsTr("Commit") + enabled: rater.percents > 0 || commentBox.text != "" onClicked: { - gears.application.sendComment(gears.configuration.loginUser, - commentBox.text); + if (commentBox.text != "") + { + gears.application.sendComment(gears.configuration.loginUser, + commentBox.text); + } + if (rater.percents > 0) + { + gears.application.sendRating(rater.percents); + } + pageStack.pop(); + rater.percents = 0; commentBox.text = ""; } } @@ -47,6 +89,7 @@ iconId: "icon-m-toolbar-back" onClicked:{ pageStack.pop(); + rater.percents = 0; commentBox.text = ""; } } --- appsformeego/qml-harmattan/SettingsPage.qml +++ appsformeego/qml-harmattan/SettingsPage.qml @@ -6,6 +6,20 @@ id: settingsPage orientationLock: PageOrientation.LockPortrait + Component.onCompleted: { + entryUser.text = gears.configuration.loginUser; + entryPassword.text = gears.configuration.loginPassword; + } + + + Connections { + target: gears.ocsProvider + onLoginError: { + errorDialog.message = message; + errorDialog.open(); + } + } + // TODO: maybe this should be an extra component AppsForMeegoBackground Rectangle { id: background @@ -43,8 +57,14 @@ height: 1 } + + // Login Section + // Item { id: sectionLogin + + property bool isLoggingIn: false + width: parent.width height: childrenRect.height @@ -86,8 +106,6 @@ anchors.left: lblUser.right anchors.right: parent.right anchors.topMargin: 10 - - text: gears.configuration.loginUser } TextField { @@ -99,8 +117,6 @@ anchors.left: lblPassword.right anchors.right: parent.right anchors.topMargin: 10 - - text: gears.configuration.loginPassword } Column { @@ -111,13 +127,16 @@ spacing: 20 Button { - text: gears.isLoggedIn ? qsTr("Log Out") - : qsTr("Log In") + text: gears.ocsProvider.loggedIn ? qsTr("Log Out") + : qsTr("Log In") anchors.left: parent.left anchors.right: parent.right + visible: ! sectionLogin.isLoggingIn onClicked: { - if (gears.isLoggedIn) + sectionLogin.isLoggingIn = true; + + if (gears.ocsProvider.loggedIn) { gears.logout(); } @@ -132,11 +151,26 @@ text: qsTr("Register") anchors.left: parent.left anchors.right: parent.right + visible: ! sectionLogin.isLoggingIn && ! gears.ocsProvider.loggedIn onClicked: { Qt.openUrlExternally("https://meego.com/user/register"); } } + + ProgressBar { + width: parent.width + indeterminate: true + visible: sectionLogin.isLoggingIn + } + + Connections { + target: gears.ocsProvider + onLoginChanged: { + sectionLogin.isLoggingIn = false; + } + } + }//Login Buttons Box }//Section @@ -147,53 +181,77 @@ color: "#313131" } + + // Testing Section + // Item { id: sectionTesting + + property bool isTogglingTesting: false + width: parent.width height: childrenRect.height + visible: gears.configuration.haveTestingMode - Button { - id: btnTesting + Column { width: parent.width - text: gears.configuration.showTesting ? qsTr("Disable Review Mode") - : qsTr("Enable Review Mode") - onClicked: { - testingEnablerDialog.open(); - enableTestingTimer.start(); - } + spacing: 20 - Timer { - id: enableTestingTimer - repeat: false - interval: 500 + Button { + id: btnTesting + width: parent.width + text: gears.configuration.showTesting ? qsTr("Disable Review Mode") + : qsTr("Enable Review Mode") + enabled: ! gears.packageDB.installing + visible: ! sectionTesting.isTogglingTesting - onTriggered: { + onClicked: { + sectionTesting.isTogglingTesting = true; if (gears.configuration.showTesting) { gears.packageDB.enableTesting(false); - gears.configuration.showTesting = false; } else { gears.packageDB.enableTesting(true); - gears.configuration.showTesting = true; } - // reload list models to reflect the change immediately - gears.appListModel.load(); - gears.appSearchModel.load(); + } + + } + + ProgressBar { + width: parent.width + indeterminate: true + visible: sectionTesting.isTogglingTesting + } + + Label { + anchors.top: btnTesting.bottom + anchors.topMargin: 12 + width: parent.width + + text: qsTr("Review Mode shows you apps waiting for " + + "review and allows you to help reviewing.\n" + + "Please note that apps waiting for review may " + + "be untested and may harm your device.") } } - Label { - anchors.top: btnTesting.bottom - anchors.topMargin: 12 - width: parent.width + Connections { + target: gears.packageDB + onEndOfTransaction: { + console.log("END OF TRANSACTION"); + if (sectionTesting.isTogglingTesting) + { + sectionTesting.isTogglingTesting = false; - text: qsTr("Review Mode shows you apps waiting for " + - "review and allows you to help reviewing.\n" + - "Please note that apps waiting for review may " + - "be untested and may harm your device.") + // reload list models to reflect the change immediately + gears.configuration.showTesting = true; + gears.appListModel.load(); + gears.appSearchModel.load(); + } + } } }//Section @@ -202,8 +260,12 @@ width: parent.width height: 2 color: "#313131" + visible: sectionTesting.visible } + + // About Section + // Item { id: sectionAbout width: parent.width @@ -233,15 +295,35 @@ ScrollDecorator { flickableItem: contentFlickable } Dialog { - id: testingEnablerDialog + id: errorDialog + property alias message: lblMessage.text - content: Text { - text: qsTr("Please Wait") + title: Text { + width: parent.width + height: 72 horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignTop + text: "Login Error" color: "white" font.pixelSize: 36 font.bold: true } + + content: Text { + id: lblMessage + width: parent.width + height: 72 + color: "white" + horizontalAlignment: Text.AlignHCenter + font.pixelSize: 24 + } + + buttons: Button { + text: "OK" + onClicked: { + errorDialog.accept(); + } + } } Dialog { --- appsformeego/qml/AppItem.qml +++ appsformeego/qml/AppItem.qml @@ -79,7 +79,7 @@ anchors.top: parent.top anchors.left: iconBox.right anchors.right: parent.right - anchors.topMargin: 24 + anchors.topMargin: 8 anchors.leftMargin: 12 anchors.rightMargin: 48 height: childrenRect.height @@ -97,11 +97,10 @@ id: rater width: 5 * 26 height: 24 - //visible: stage == "stable" - filled: "star-filled.png" unfilled: "star.png" maximum: 5 + interactive: false } /* --- appsformeego/qml/Gears.qml +++ appsformeego/qml/Gears.qml @@ -12,7 +12,6 @@ property alias appSearchModel: myAppSearchModel property alias application: myApplication - property bool isLoggedIn: false /* Returns the status of the given package as known to the package DB. */ @@ -44,11 +43,8 @@ { console.log("user: " + user); - if (user != "" && password != "") - { - myOcsProvider.login(user, password); - isLoggedIn = true; - } + myOcsProvider.login(user, password); + if (storeCredentials) { myConfiguration.loginUser = user; @@ -61,7 +57,26 @@ myOcsProvider.login("", "", true); myConfiguration.loginUser = ""; myConfiguration.loginPassword = ""; - isLoggedIn = false; + } + + function splitComment(data) + { + var pos1 = data.indexOf("\n"); + var user = data.substring(0, pos1); + var pos2 = data.indexOf("\n", pos1 + 1); + var date = data.substring(pos1 + 1, pos2); + var pos3 = data.indexOf("\n", pos2 + 1); + var version = data.substring(pos2 + 1, pos3); + var comment = data.substring(pos3 + 1, data.length); + + /* + console.log(pos1 + " " + pos2 + " " + pos3); + console.log("User: " + user); + console.log("Date: " + date); + console.log("Version: " + version); + console.log("Comment: " + comment); + */ + return [user, date, version, comment]; } Component.onCompleted: { --- appsformeego/qml/Rater.qml +++ appsformeego/qml/Rater.qml @@ -6,6 +6,7 @@ property url unfilled property int maximum: 5 property int percents: 0 + property bool interactive: false id: rater @@ -21,9 +22,18 @@ sourceSize.width: width sourceSize.height: rater.height smooth: false - fillMode: Image.Tile + fillMode: Image.Stretch source: (index < percents / 100.0 * maximum) ? filled : unfilled + + MouseArea { + anchors.fill: parent + enabled: rater.interactive + + onEntered: { + rater.percents = ((index + 1) / rater.maximum) * 100.0 + } + } } } } --- appsformeego/src/application.cpp +++ appsformeego/src/application.cpp @@ -56,6 +56,29 @@ QProcess::startDetached(execResolved, QStringList(), path); } +QString parseComment(Attica::Comment &comment) +{ + QString version = ""; + QString subject = comment.subject(); + int pos = comment.subject().indexOf(':'); + if (pos != -1) + { + version = comment.subject().left(pos); + subject = comment.subject().mid(pos + 1); + } + + // burn those useless CRs used by descendants of the CP/M OS + QString commentText = comment.text(); + commentText.replace('\r', ""); + + QString out = comment.user() + "\n" + + comment.date().toLocalTime().toString(Qt::SystemLocaleLongDate) + "\n" + + version + "\n" + + commentText; + + return out; +} + } Application::Application(QObject *parent) @@ -121,11 +144,7 @@ qDebug() << "COMMENT"; qDebug() << comment.user(); qDebug() << comment.text(); - myAppComments.prepend(comment.user() + - "\n" + - comment.date().toLocalTime().toString(Qt::SystemLocaleLongDate) + - "\n" + - comment.text()); + myAppComments.prepend(parseComment(comment)); foreach (Attica::Comment child, comment.children()) { @@ -157,6 +176,19 @@ } } +void Application::sendRating(uint score) +{ + if (score > 100) + { + score = 100; + } + Attica::PostJob* raterJob = + myProvider->get().voteForContent(myAppId, score); + connect(raterJob, SIGNAL(finished(Attica::BaseJob*)), + this, SLOT(slotRatingFinished(Attica::BaseJob*))); + raterJob->start(); +} + void Application::sendComment(QString user, QString comment) { qDebug() << "sending comment" << comment; @@ -170,7 +202,7 @@ addCommentJob->start(); // make new comment available instantly - myAppComments.prepend(user + "\n" + "just now" + "\n" + comment); + myAppComments.prepend(user + "\n" + "just now" + "\n" + myAppVersion + "\n" + comment); emit commentsChanged(); } @@ -296,6 +328,18 @@ } } +void Application::slotRatingFinished(Attica::BaseJob *job) +{ + if (job->metadata().error() == Attica::Metadata::NoError) + { + qDebug() << "rating sent"; + } + else + { + qDebug() << "error sending rating:" << job->metadata().message(); + } +} + QString Application::decodeOct(const QString& data) { QByteArray output; --- appsformeego/src/application.h +++ appsformeego/src/application.h @@ -61,6 +61,7 @@ bool isLaunchable() { return myIsLaunchable; } Q_INVOKABLE void launch(); + Q_INVOKABLE void sendRating(uint score); Q_INVOKABLE void sendComment(QString user, QString comment); static QString decodeOct(const QString& data); @@ -93,9 +94,10 @@ void readyChanged(); private slots: - void handleSearchJobFinished(Attica::BaseJob *job); - void handleCommentJobFinished(Attica::BaseJob *job); - void slotAddCommentFinished(Attica::BaseJob *job); + void handleSearchJobFinished(Attica::BaseJob* job); + void handleCommentJobFinished(Attica::BaseJob* job); + void slotAddCommentFinished(Attica::BaseJob* job); + void slotRatingFinished(Attica::BaseJob* job); }; --- appsformeego/src/configuration.cpp +++ appsformeego/src/configuration.cpp @@ -91,3 +91,12 @@ // not saved in config emit valueChanged(); } + +bool Configuration::haveTestingMode() +{ +#if defined(FOR_HARMATTAN) + return true; +#else + return false; +#endif +} --- appsformeego/src/configuration.h +++ appsformeego/src/configuration.h @@ -15,6 +15,8 @@ Q_PROPERTY(bool showTesting READ showTesting WRITE setShowTesting NOTIFY valueChanged); + Q_PROPERTY(bool haveTestingMode READ haveTestingMode); + public: explicit Configuration(QObject *parent = 0); virtual ~Configuration(); @@ -30,6 +32,8 @@ bool showTesting(); void setShowTesting(bool value); + bool haveTestingMode(); + signals: void valueChanged(); }; --- appsformeego/src/installer.cpp +++ appsformeego/src/installer.cpp @@ -82,11 +82,6 @@ << "testing" << (enabled ? "yes" : "no")); myInstallers << installer; - - // this is a blocking operation - qDebug() << "waiting for repository"; - installer->waitForFinished(); - qDebug() << "ready"; } void Installer::install(QString appKey, QUrl url) --- appsformeego/src/packagedb.cpp +++ appsformeego/src/packagedb.cpp @@ -19,6 +19,7 @@ , myRefreshProcess(new QProcess) , myLastRefreshTime(0) , myCurrentMode("idle") + , myIsInstalling(false) { QHash<int, QByteArray> roles; roles[Qt::DisplayRole] = "appname"; @@ -180,6 +181,8 @@ void PackageDB::enableTesting(bool enabled) { myInstaller.enableTesting(enabled); + myIsInstalling = true; + emit installingStatusChanged(); } /* Installs the given application. @@ -198,6 +201,8 @@ emit progressChanged(appKey, 0, 0); myInstaller.install(appKey, uri); + myIsInstalling = true; + emit installingStatusChanged(); myAppInfoQueue << appKey; fetchNextAppInfo(); @@ -213,6 +218,8 @@ tryRefresh(); myInstaller.installPackage(name); + myIsInstalling = true; + emit installingStatusChanged(); } /* Removes the given application. @@ -226,6 +233,7 @@ emit progressChanged(appKey, 0, 0); myInstaller.remove(appKey); + myIsInstalling = true; // why did we fetch appInfo from OCS here? is this a copy&paste relic..? //myAppInfoQueue << appId; @@ -404,6 +412,8 @@ PackageStatus::Ptr status = packageStatus(myCurrentAppKey); updateStatus(status); + myIsInstalling = false; + emit installingStatusChanged(); emit endOfTransaction(myCurrentAppKey); } @@ -413,6 +423,8 @@ updateStatus(status); myCurrentMessage = message; + myIsInstalling = false; + emit installingStatusChanged(); emit endOfTransaction(myCurrentAppKey); } --- appsformeego/src/packagedb.h +++ appsformeego/src/packagedb.h @@ -26,6 +26,8 @@ Q_PROPERTY(int progress READ progress NOTIFY progressChanged); Q_PROPERTY(int subProgress READ subProgress NOTIFY progressChanged); Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY progressChanged); + Q_PROPERTY(bool installing READ installing NOTIFY installingStatusChanged); + public: enum { SummaryRole = Qt::UserRole, @@ -73,6 +75,7 @@ int progress() { return myCurrentProgress; } int subProgress() { return myCurrentSubProgress; } QString errorMessage() { return myCurrentMessage; } + bool installing() { return myIsInstalling; } QList<PackageStatus::Ptr> myPackages; SchemeService* mySchemeService; @@ -90,12 +93,14 @@ uint myCurrentProgress; uint myCurrentSubProgress; QString myCurrentMessage; + bool myIsInstalling; signals: void modelChanged(); void progressChanged(QString appKey, uint progress, uint subProgress); void endOfTransaction(QString appKey); void appPageRequested(QString appId); + void installingStatusChanged(); private slots: --- appsformeego/src/provider.cpp +++ appsformeego/src/provider.cpp @@ -7,6 +7,7 @@ Provider::Provider(QObject *parent) : QObject(parent) + , myIsLoggedIn(false) { // TODO: check if we may load this already //myManager.addProviderFromXml("<provider><id>MeeGo</id><location>http://apps-beta.meego.com/ocs/v1/</location><name>MeeGo.com Apps</name><icon/><termsofuse/><register/><services><content ocsversion='1.6'/><comments ocsversion='1.6'/></services></provider>"); @@ -15,10 +16,6 @@ myManager.addProviderFromXml("<providers><provider><id>opendesktop</id><location>https://api.opendesktop.org/v1/</location><name>openDesktop.org</name><icon/><termsofuse>https://opendesktop.org/terms/</termsofuse><register>https://opendesktop.org/usermanager/new.php</register><services><person ocsversion='1.3'/><friend ocsversion='1.3'/><message ocsversion='1.3'/><activity ocsversion='1.3'/><content ocsversion='1.3'/><fan ocsversion='1.3'/><knowledgebase ocsversion='1.3'/><event ocsversion='1.3'/></services></provider></providers>"); //myManager.addProviderFile(QUrl("http://download.kde.org/ocs/providers.xml")); //myManager.addProviderFile(QUrl("http://apps.formeego.org/ocs/providers.xml")); - - - - } Provider::~Provider() @@ -44,18 +41,37 @@ } void Provider::login(QString user, QString password) -{ - myProvider.saveCredentials(user, password); - qDebug() << "Has Credentials" << myProvider.hasCredentials(); - bool ok = myProvider.loadCredentials(user, password); - qDebug() << "logging in..." << ok; +{ + if (user.isEmpty()) + { + qDebug() << "logged out"; + myProvider.saveCredentials(user, password); + myIsLoggedIn = false; + emit loginChanged(); + } + else + { + qDebug() << "logging in..."; + myProvider.saveCredentials(user, password); + Attica::PostJob* job = myProvider.checkLogin(user, password); + connect(job, SIGNAL(finished(Attica::BaseJob*)), + this, SLOT(slotLoginDone(Attica::BaseJob*))); + job->start(); + } } void Provider::slotLoginDone(Attica::BaseJob *job) { - if (job->metadata().error() == Attica::Metadata::NoError) { - qDebug() << "Logged In"; + qDebug() << "login successful"; + myIsLoggedIn = true; + } + else + { + qDebug() << "Error logging in:" << job->metadata().message(); + myIsLoggedIn = false; + emit loginError(job->metadata().message()); } + emit loginChanged(); } --- appsformeego/src/provider.h +++ appsformeego/src/provider.h @@ -14,6 +14,7 @@ Q_DISABLE_COPY(Provider) Q_PROPERTY(QUrl url READ url WRITE setUrl); + Q_PROPERTY(bool loggedIn READ loggedIn NOTIFY loginChanged); public: Provider(QObject *parent = 0); @@ -29,13 +30,20 @@ Q_INVOKABLE void login(QString user, QString password); private: + bool loggedIn() { return myIsLoggedIn; } + Attica::ProviderManager myManager; Attica::Provider myProvider; QUrl myUrl; + bool myIsLoggedIn; private slots: void slotLoginDone(Attica::BaseJob* job); +signals: + void loginChanged(); + void loginError(QString message); + }; QML_DECLARE_TYPE(Provider) ++++++ apps-client.yaml --- apps-client.yaml +++ apps-client.yaml @@ -1,6 +1,6 @@ Name: apps-client Summary: Apps for Nemo client -Version: 0.9.16 +Version: 0.9.18 Release: 1 Group: Applications/System License: GPLv2 ++++++ boss.conf (new) --- boss.conf +++ boss.conf @@ -0,0 +1,4 @@ +[checks] +check_package_is_complete_sources = warn +check_has_relevant_changelog = warn +check_package_is_complete_changes = warn
