Merge branch 'master' of github.com:martinrotter/rssguard

This commit is contained in:
Martin Rotter 2020-11-19 13:16:19 +01:00
commit 508d645c82
23 changed files with 12422 additions and 413 deletions

View File

@ -9,6 +9,8 @@ RSS Guard
[![GitHub issues](https://img.shields.io/github/issues/martinrotter/rssguard.svg?maxAge=360)](https://github.com/martinrotter/rssguard/issues) [![GitHub issues](https://img.shields.io/github/issues/martinrotter/rssguard.svg?maxAge=360)](https://github.com/martinrotter/rssguard/issues)
[![License](https://img.shields.io/github/license/martinrotter/rssguard.svg?maxAge=360000)](https://github.com/martinrotter/rssguard/blob/master/LICENSE.md) [![License](https://img.shields.io/github/license/martinrotter/rssguard.svg?maxAge=360000)](https://github.com/martinrotter/rssguard/blob/master/LICENSE.md)
### [Downloads](https://github.com/martinrotter/rssguard/releases)
RSS Guard is simple, light and easy-to-use RSS/ATOM feed aggregator developed using Qt framework which supports online feed synchronization with these services: RSS Guard is simple, light and easy-to-use RSS/ATOM feed aggregator developed using Qt framework which supports online feed synchronization with these services:
* [Tiny Tiny RSS](https://tt-rss.org), * [Tiny Tiny RSS](https://tt-rss.org),
* [Inoreader](https://www.inoreader.com), * [Inoreader](https://www.inoreader.com),

7119
localization/qtbase_fi.ts Executable file

File diff suppressed because it is too large Load Diff

4731
localization/rssguard_fi.ts Executable file

File diff suppressed because it is too large Load Diff

View File

@ -2204,7 +2204,7 @@ It is highly recommended to create your own &quot;Application ID&quot;.</source>
</message> </message>
<message> <message>
<source>Display &amp;documentation</source> <source>Display &amp;documentation</source>
<translation type="unfinished"/> <translation>Exibir &amp;documentação</translation>
</message> </message>
</context> </context>
<context> <context>

View File

@ -198,11 +198,11 @@
<name>ColorToolButton</name> <name>ColorToolButton</name>
<message> <message>
<source>Click me to change color!</source> <source>Click me to change color!</source>
<translation type="unfinished"/> <translation>Нажмите здесь, чтобы изменить цвет!</translation>
</message> </message>
<message> <message>
<source>Select new color</source> <source>Select new color</source>
<translation type="unfinished"/> <translation>Выбор нового цвета</translation>
</message> </message>
</context> </context>
<context> <context>
@ -312,7 +312,7 @@ This website contains %n feed(s).</source>
</message> </message>
<message> <message>
<source>Feeds were detected, but no suitable accounts are configured.</source> <source>Feeds were detected, but no suitable accounts are configured.</source>
<translation type="unfinished"/> <translation>Каналы обнаружены, но подходящие учетные записи не настроены.</translation>
</message> </message>
</context> </context>
<context> <context>
@ -672,7 +672,7 @@ or this functionality is not implemented yet.</source>
</message> </message>
<message> <message>
<source>Context menu for label</source> <source>Context menu for label</source>
<translation type="unfinished"/> <translation>Контекстное меню для меток</translation>
</message> </message>
</context> </context>
<context> <context>
@ -820,27 +820,27 @@ or this functionality is not implemented yet.</source>
</message> </message>
<message> <message>
<source>Name for your label</source> <source>Name for your label</source>
<translation type="unfinished"/> <translation>Название новой метки</translation>
</message> </message>
<message> <message>
<source>Label&apos;s name cannot be empty.</source> <source>Label&apos;s name cannot be empty.</source>
<translation type="unfinished"/> <translation>Название метки не может быть пустым.</translation>
</message> </message>
<message> <message>
<source>Perfect!</source> <source>Perfect!</source>
<translation type="unfinished"/> <translation>Отлично!</translation>
</message> </message>
<message> <message>
<source>Hot stuff</source> <source>Hot stuff</source>
<translation type="unfinished"/> <translation>Новая метка</translation>
</message> </message>
<message> <message>
<source>Create new label</source> <source>Create new label</source>
<translation type="unfinished"/> <translation>Создать новую метку</translation>
</message> </message>
<message> <message>
<source>Edit label &apos;%1&apos;</source> <source>Edit label &apos;%1&apos;</source>
<translation type="unfinished"/> <translation>Изменить метку &apos;%1&apos;</translation>
</message> </message>
</context> </context>
<context> <context>
@ -2201,11 +2201,11 @@ It is highly recommended to create your own &quot;Application ID&quot;.</source>
</message> </message>
<message> <message>
<source>Alternate row colors in lists</source> <source>Alternate row colors in lists</source>
<translation type="unfinished"/> <translation>Чередовать цвет фона строк в списках</translation>
</message> </message>
<message> <message>
<source>Display &amp;documentation</source> <source>Display &amp;documentation</source>
<translation type="unfinished"/> <translation>Показать документацию (&amp;D)</translation>
</message> </message>
</context> </context>
<context> <context>
@ -3065,30 +3065,30 @@ Login tokens expiration: %2</source>
<name>LabelsMenu</name> <name>LabelsMenu</name>
<message> <message>
<source>Labels</source> <source>Labels</source>
<translation type="unfinished"/> <translation>Метки</translation>
</message> </message>
</context> </context>
<context> <context>
<name>LabelsNode</name> <name>LabelsNode</name>
<message> <message>
<source>Labels</source> <source>Labels</source>
<translation type="unfinished"/> <translation>Метки</translation>
</message> </message>
<message> <message>
<source>You can see all your labels (tags) here.</source> <source>You can see all your labels (tags) here.</source>
<translation type="unfinished"/> <translation>Вы можете увидеть все метки (теги) здесь.</translation>
</message> </message>
<message> <message>
<source>New label</source> <source>New label</source>
<translation type="unfinished"/> <translation>Новая метка</translation>
</message> </message>
<message> <message>
<source>This account does not allow you to create labels.</source> <source>This account does not allow you to create labels.</source>
<translation type="unfinished"/> <translation>Эта учетная запись не позволяет создавать метки.</translation>
</message> </message>
<message> <message>
<source>Not allowed</source> <source>Not allowed</source>
<translation type="unfinished"/> <translation>Не допускается</translation>
</message> </message>
</context> </context>
<context> <context>
@ -3613,7 +3613,7 @@ version by clicking this popup notification.</source>
</message> </message>
<message> <message>
<source>Standard online feeds (RSS/ATOM/JSON)</source> <source>Standard online feeds (RSS/ATOM/JSON)</source>
<translation type="unfinished"/> <translation>Стандартные онлайн-каналы (RSS/ATOM/JSON)</translation>
</message> </message>
</context> </context>
<context> <context>

View File

@ -16,3 +16,5 @@ You can easily (de)assign label to messages in message viewer.
<img src="images/label-assign.png" width="80%"> <img src="images/label-assign.png" width="80%">
Note that (de)assignments of labels to messages are synchronized back to supported servers in regular intervals. Note that (de)assignments of labels to messages are synchronized back to supported servers in regular intervals.
Also, [message filters](Message-filters.md) can assign or remove labels to/from messages.

View File

@ -13,19 +13,55 @@ As you can see, RSS Guard processes all feeds scheduled for message downloading
## Writing message filter ## Writing message filter
Message filter consists of arbitrary JavaScript code which must provide function with prototype `function filterMessage() { }`. This function must be fast and must return integer values which belong to enumeration [`FilteringAction`](https://github.com/martinrotter/rssguard/blob/master/src/librssguard/core/message.h#L83). For example, your function must return `2` to block the message which is subsequently NOT saved into database. For easier usage, RSS Guard 3.7.1+ offers named variables for this, which are called `MSG_ACCEPT` and `MSG_IGNORE`. Message filter consists of arbitrary JavaScript code which must provide function with prototype
Each message is accessible in your script via global variable named `msg` of type [`MessageObject`](https://github.com/martinrotter/rssguard/blob/master/src/librssguard/core/message.h#L118). Some properties are writable, thus allowing you to change contents of the message before it is written to DB. You can mark message important, parse its description or perhaps change author name!!! ```js
function filterMessage() { }
```
RSS Guard 3.8.0+ offers also read-only list of labels assigned to each message. You can therefore do actions in your filtering script based on which labels are assigned to the message. The property is called `assignedLabels` and is array of `Label` objects. Each `Label` in the array offers these properties: `title` (title of the label), `color` (color of the label) and `customId` (account-specific ID of the label). This function must be fast and must return values which belong to enumeration `FilteringAction` from this [file](https://github.com/martinrotter/rssguard/blob/master/src/librssguard/core/message.h). You can you either direct numerical value of each enumerant, for example `2` or you can use self-descriptive enumerant name, for example `MessageObject.Ignore`. Named enumerants are supported in RSS Guard 3.8.1+. RSS Guard 3.7.1+ also offers names `MSG_ACCEPT` and `MSG_IGNORE` as aliases for `MessageObject.Accept` and `MessageObject.Ignore`.
Each message is accessible in your script via global variable named `msg` of type `MessageObject`, see this [file](https://github.com/martinrotter/rssguard/blob/master/src/librssguard/core/message.h) for the declaration. Some properties are writable, allowing you to change contents of the message before it is written to DB. You can mark message important, parse its description or perhaps change author name or even assign some label to it!!!
RSS Guard 3.8.0+ offers also list of labels assigned to each message. You can therefore do actions in your filtering script based on which labels are assigned to the message. The property is called `assignedLabels` and is array of `Label` objects. Each `Label` in the array offers these properties: `title` (title of the label), `color` (color of the label) and `customId` (account-specific ID of the label). If you change assigned labels to the message, then the change will be eventually synchronized back to server if respective plugin supports it.
Passed message also offers special function Passed message also offers special function
```js ```js
MessageObject.isDuplicateWithAttribute(DuplicationAttributeCheck) MessageObject.isDuplicateWithAttribute(DuplicationAttributeCheck)
``` ```
which allows you to perform runtime check for existence of the message in RSS Guard's database. The parameter is integer value from enumeration [`DuplicationAttributeCheck`](https://github.com/martinrotter/rssguard/blob/master/src/librssguard/core/message.h#L91) and specifies how exactly you want to determine if given message is "duplicate".
For example if you want to check if there is already another message with same author in database, then you call `msg.isDuplicateWithAttribute(4)`. Enumeration even supports "flags" approach, thus you can combine multiple checks via bitwise `OR` operation in single call, for example like this: `msg.isDuplicateWithAttribute(4 | 16)`. which allows you to perform runtime check for existence of the message in RSS Guard's database. The parameter is integer value from enumeration `DuplicationAttributeCheck` from this [file](https://github.com/martinrotter/rssguard/blob/master/src/librssguard/core/message.h) and specifies how exactly you want to determine if given message is "duplicate". Again, you can use direct integer value or enumerant name.
For example if you want to check if there is already another message with same author in database, then you call `msg.isDuplicateWithAttribute(MessageObject.SameAuthor)`. Enumeration even supports "flags" approach, thus you can combine multiple checks via bitwise `OR` operation in single call, for example like this: `msg.isDuplicateWithAttribute(MessageObject.SameAuthor | MessageObject.SameUrl)`.
## API reference
Here is the reference of methods and properties of some types available in your filtering scipts.
### `MessageObject`
| Property/method | Description |
|---|---|
| `Array<Label> assignedLabels` | `READ-ONLY` List of labels assigned to the message. |
| `Array<Label> availableLabels` | `READ-ONLY` List of labels which are currently available and can be assigned to the message. Available in RSS Guard 3.8.1+. |
| `String feedCustomId` | `READ-ONLY` Service-specific ID of the feed which this message belongs to. |
| `Number accountId` | `READ-ONLY` RSS Guard's ID of the account activated in the program. This property is highly advanced and you probably do not need to use it at all. |
| `String title` | Title of the message. |
| `String url` | URL of the message. |
| `String author` | Author of the message. |
| `String contents` | Contents of the message. |
| `Date created` | Date/time of the message. |
| `Boolean isRead` | Is message read? |
| `Boolean isImportant` | Is message important? |
| `Boolean isDuplicateWithAttribute(DuplicationAttributeCheck)` | Allows you to test if this particular message is already stored in RSS Guard's DB. |
| `Boolean assignLabel(String)` | Assigns label to this message. The passed `String` value is the `customId` property of `Label` type. See its API reference for relevant info. Available in RSS Guard 3.8.1+. |
| `Boolean deassignLabel(String)` | Removes label from this message. The passed `String` value is the `customId` property of `Label` type. See its API reference for relevant info. Available in RSS Guard 3.8.1+. |
### `Label`
| Property/method | Description |
|---|---|
| `String title` | `READ-ONLY` Label title. |
| `String customId` | `READ-ONLY` Service-specific ID of this label. This ID is used as unique identifier for the label and is particularly useful if you want to (de)assign label to/from message. |
| `color color` | `READ-ONLY` Label color. Note that type `color` has its documentation [here](https://doc.qt.io/qt-5/qml-color.html). |
## Examples ## Examples
Accept only messages from "Bob" while also marking them important. Accept only messages from "Bob" while also marking them important.
@ -33,10 +69,10 @@ Accept only messages from "Bob" while also marking them important.
function filterMessage() { function filterMessage() {
if (msg.author == "Bob") { if (msg.author == "Bob") {
msg.isImportant = true; msg.isImportant = true;
return MSG_ACCEPT; return MessageObject.Accept;
} }
else { else {
return MSG_IGNORE; return MessageObject.Ignore;
} }
} }
``` ```
@ -45,18 +81,44 @@ Replace all dogs with cats!
```js ```js
function filterMessage() { function filterMessage() {
msg.title = msg.title.replace("dogs", "cats"); msg.title = msg.title.replace("dogs", "cats");
return MSG_ACCEPT; return MessageObject.Accept;
}
```
Write details of available labels and assign the first label to the message.
```js
function filterMessage() {
console.log('Number of assigned labels ' + msg.assignedLabels.length);
console.log('Number of available labels ' + msg.availableLabels.length);
var i;
for (i = 0; i < msg.availableLabels.length; i++) {
var lbl = msg.availableLabels[i];
console.log('Available label:');
console.log(' Title: \'' + lbl.title + '\' ID: \'' + lbl.customId + '\'');
}
if (msg.availableLabels.length > 0) {
console.log('Assigning first label to message...');
msg.assignLabel(msg.availableLabels[0].customId);
console.log('Number of assigned labels ' + msg.assignedLabels.length);
}
console.log();
return MessageObject.Accept;
} }
``` ```
Make sure that your receive only one message with particular URL and all other messages with same URL are subsequently ignored. Make sure that your receive only one message with particular URL and all other messages with same URL are subsequently ignored.
```js ```js
function filterMessage() { function filterMessage() {
if (msg.isDuplicateWithAttribute(2)) { if (msg.isDuplicateWithAttribute(MessageObject.SameUrl)) {
return MSG_IGNORE; return MessageObject.Ignore;
} }
else { else {
return MSG_ACCEPT; return MessageObject.Accept;
} }
} }
``` ```

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -27,6 +27,7 @@
<file>graphics/misc/flags/de.png</file> <file>graphics/misc/flags/de.png</file>
<file>graphics/misc/flags/en.png</file> <file>graphics/misc/flags/en.png</file>
<file>graphics/misc/flags/es.png</file> <file>graphics/misc/flags/es.png</file>
<file>graphics/misc/flags/fi.png</file>
<file>graphics/misc/flags/fr.png</file> <file>graphics/misc/flags/fr.png</file>
<file>graphics/misc/flags/gl.png</file> <file>graphics/misc/flags/gl.png</file>
<file>graphics/misc/flags/he.png</file> <file>graphics/misc/flags/he.png</file>
@ -69,6 +70,7 @@
<file>../localization/rssguard_de.qm</file> <file>../localization/rssguard_de.qm</file>
<file>../localization/rssguard_en.qm</file> <file>../localization/rssguard_en.qm</file>
<file>../localization/rssguard_es.qm</file> <file>../localization/rssguard_es.qm</file>
<file>../localization/rssguard_fi.qm</file>
<file>../localization/rssguard_fr.qm</file> <file>../localization/rssguard_fr.qm</file>
<file>../localization/rssguard_gl.qm</file> <file>../localization/rssguard_gl.qm</file>
<file>../localization/rssguard_he.qm</file> <file>../localization/rssguard_he.qm</file>
@ -89,6 +91,7 @@
<file>../localization/qtbase_da.qm</file> <file>../localization/qtbase_da.qm</file>
<file>../localization/qtbase_de.qm</file> <file>../localization/qtbase_de.qm</file>
<file>../localization/qtbase_es.qm</file> <file>../localization/qtbase_es.qm</file>
<file>../localization/qtbase_fi.qm</file>
<file>../localization/qtbase_fr.qm</file> <file>../localization/qtbase_fr.qm</file>
<file>../localization/qtbase_it.qm</file> <file>../localization/qtbase_it.qm</file>
<file>../localization/qtbase_ja.qm</file> <file>../localization/qtbase_ja.qm</file>

View File

@ -1,50 +1,30 @@
# List startup folder. $old_pwd = $pwd.Path
$old_pwd=$pwd.Path
$ssl_bin = "C:\OpenSSL-v111-Win64\bin"
$mysql_dir = "C:\Program Files\MySQL\MySQL Server 5.7"
ls "$ssl_bin" # Get Qt.
ls "$mysql_dir\lib" $qt_version = "5.15.1"
ls $qt_stub = "qt-$qt_version-dynamic-msvc2019-x86_64"
$qt_link = "https://github.com/martinrotter/qt5-minimalistic-builds/releases/download/$qt_version/$qt_stub.7z"
echo "qmake args are: '$env:qmake_args'." $qt_output = "qt.7z"
# Setup env path with qmake.
$env:PATH = "$env:QTDIR\bin;" + $env:PATH
# Build MySQL Qt plugin.
$qt_ver = "$env:QTVER"
$qt_rev = "$env:QTREV"
$qtbase_url = "https://download.qt.io/archive/qt/$qt_ver/$qt_ver.$qt_rev/submodules/qtbase-everywhere-src-$qt_ver.$qt_rev.zip"
$output = "qt.zip"
mkdir "build-mysql"
cd "build-mysql"
echo "Building MySQL Qt plugin, downloading Qt '$qt_ver.$qt_rev'."
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-WebRequest -Uri $qtbase_url -OutFile $output Invoke-WebRequest -Uri $qt_link -OutFile $qt_output
& ".\resources\scripts\7za\7za.exe" x $qt_output
& "..\resources\scripts\7za\7za.exe" x $output $qt_path = (Resolve-Path $qt_stub).Path
$qt_qmake = "$qt_path\bin\qmake.exe"
$qt_mysql_dir = "./qtbase-everywhere-src-$qt_ver.$qt_rev/src/plugins/sqldrivers"
$mysql_d_rev = $mysql_dir.Replace('\', '/')
cd "$qt_mysql_dir"
qmake.exe -- MYSQL_INCDIR="$mysql_d_rev/include" MYSQL_LIBDIR="$mysql_d_rev/lib"
nmake.exe sub-mysql
Copy-Item -Path ".\plugins\sqldrivers\qsqlmysql.dll" -Destination "$old_pwd\build-mysql"
cd "$qt_stub\bin\"
& ".\qtbinpatcher.exe"
cd "$old_pwd" cd "$old_pwd"
ls "build-mysql"
$env:PATH = "$qt_path\bin\;" + $env:PATH
# Build RSS Guard itself. # Build RSS Guard itself.
echo "qmake args are: '$env:qmake_args'."
mkdir "rssguard-build" mkdir "rssguard-build"
cd "rssguard-build" cd "rssguard-build"
qmake.exe ..\build.pro "$env:qmake_args" & "$qt_qmake" "..\build.pro" "$env:qmake_args"
nmake.exe nmake.exe
cd "src\rssguard" cd "src\rssguard"
@ -56,12 +36,11 @@ windeployqt.exe --verbose 1 --compiler-runtime --no-translations --release rssgu
cd ".." cd ".."
# Copy OpenSSL. # Copy OpenSSL.
Copy-Item -Path "$ssl_bin\libcrypto*.dll" -Destination ".\app\" Copy-Item -Path "$qt_path\bin\libcrypto*.dll" -Destination ".\app\"
Copy-Item -Path "$ssl_bin\libssl*.dll" -Destination ".\app\" Copy-Item -Path "$qt_path\bin\libssl*.dll" -Destination ".\app\"
# Copy MySQL Qt plugin. # Copy MySQL Qt plugin.
Copy-Item -Path "$mysql_dir\lib\libmysql.dll" -Destination ".\app\" Copy-Item -Path "$qt_path\bin\libmariadb.dll" -Destination ".\app\"
Copy-Item -Path "$old_pwd\build-mysql\qsqlmysql.dll" -Destination ".\app\sqldrivers\"
nmake.exe windows_all nmake.exe windows_all
cd "$old_pwd" cd "$old_pwd"

View File

@ -9,7 +9,7 @@ read -p "Password: " PASSWORD
# Setup parameters. # Setup parameters.
RESOURCE=../../../localization/rssguard_en.ts RESOURCE=../../../localization/rssguard_en.ts
CODES="cs da de es fr gl he id it ja lt nl pl pt ru sv uk zh_CN zh_TW" CODES="cs da de es fi fr gl he id it ja lt nl pl pt ru sv uk zh_CN zh_TW"
TRANSLATION='../../../localization/rssguard_$CODE.ts' TRANSLATION='../../../localization/rssguard_$CODE.ts'
declare PARAMS declare PARAMS

View File

@ -9,6 +9,7 @@
#include "miscellaneous/application.h" #include "miscellaneous/application.h"
#include "services/abstract/cacheforserviceroot.h" #include "services/abstract/cacheforserviceroot.h"
#include "services/abstract/feed.h" #include "services/abstract/feed.h"
#include "services/abstract/labelsnode.h"
#include <QDebug> #include <QDebug>
#include <QJSEngine> #include <QJSEngine>
@ -112,15 +113,13 @@ void FeedDownloader::updateOneFeed(Feed* feed) {
// Perform per-message filtering. // Perform per-message filtering.
QJSEngine filter_engine; QJSEngine filter_engine;
MessageFilter::initializeFilteringEngine(filter_engine);
// Create JavaScript communication wrapper for the message. // Create JavaScript communication wrapper for the message.
MessageObject msg_obj(&database, feed->customId(), feed->getParentServiceRoot()->accountId()); MessageObject msg_obj(&database,
feed->customId(),
feed->getParentServiceRoot()->accountId(),
feed->getParentServiceRoot()->labelsNode()->labels());
// Register the wrapper. MessageFilter::initializeFilteringEngine(filter_engine, &msg_obj);
auto js_object = filter_engine.newQObject(&msg_obj);
filter_engine.globalObject().setProperty("msg", js_object);
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Setting up JS evaluation took " << tmr.nsecsElapsed() / 1000 << " microseconds."; qDebugNN << LOGSEC_FEEDDOWNLOADER << "Setting up JS evaluation took " << tmr.nsecsElapsed() / 1000 << " microseconds.";
@ -153,18 +152,18 @@ void FeedDownloader::updateOneFeed(Feed* feed) {
tmr.restart(); tmr.restart();
try { try {
FilteringAction decision = msg_filter->filterMessage(&filter_engine); MessageObject::FilteringAction decision = msg_filter->filterMessage(&filter_engine);
qDebugNN << LOGSEC_FEEDDOWNLOADER qDebugNN << LOGSEC_FEEDDOWNLOADER
<< "Running filter script, it took " << tmr.nsecsElapsed() / 1000 << " microseconds."; << "Running filter script, it took " << tmr.nsecsElapsed() / 1000 << " microseconds.";
switch (decision) { switch (decision) {
case FilteringAction::Accept: case MessageObject::FilteringAction::Accept:
// Message is normally accepted, it could be tweaked by the filter. // Message is normally accepted, it could be tweaked by the filter.
continue; continue;
case FilteringAction::Ignore: case MessageObject::FilteringAction::Ignore:
// Remove the message, we do not want it. // Remove the message, we do not want it.
remove_msg = true; remove_msg = true;
@ -196,6 +195,30 @@ void FeedDownloader::updateOneFeed(Feed* feed) {
important_msgs << *msg_orig; important_msgs << *msg_orig;
} }
// Process changed labels.
for (Label* lbl : msg_backup.m_assignedLabels) {
if (!msg_orig->m_assignedLabels.contains(lbl)) {
// Label is not there anymore, it was deassigned.
lbl->deassignFromMessage(*msg_orig);
qDebugNN << "It was detected that label" << QUOTE_W_SPACE(lbl->customId())
<< "was DEASSIGNED from message" << QUOTE_W_SPACE(msg_orig->m_customId)
<< "by message filter(s).";
}
}
for (Label* lbl : msg_orig->m_assignedLabels) {
if (!msg_backup.m_assignedLabels.contains(lbl)) {
// Label is in new message, but is not in old message, it
// was newly assigned.
lbl->assignToMessage(*msg_orig);
qDebugNN << "It was detected that label" << QUOTE_W_SPACE(lbl->customId())
<< "was ASSIGNED to message" << QUOTE_W_SPACE(msg_orig->m_customId)
<< "by message filter(s).";
}
}
if (remove_msg) { if (remove_msg) {
msgs.removeAt(i--); msgs.removeAt(i--);
} }

View File

@ -2,6 +2,7 @@
#include "core/message.h" #include "core/message.h"
#include "3rd-party/boolinq/boolinq.h"
#include "miscellaneous/textfactory.h" #include "miscellaneous/textfactory.h"
#include "services/abstract/label.h" #include "services/abstract/label.h"
@ -158,45 +159,39 @@ uint qHash(const Message& key) {
return (uint(key.m_accountId) * 10000) + uint(key.m_id); return (uint(key.m_accountId) * 10000) + uint(key.m_id);
} }
MessageObject::MessageObject(QSqlDatabase* db, const QString& feed_custom_id, int account_id, QObject* parent) MessageObject::MessageObject(QSqlDatabase* db, const QString& feed_custom_id,
: QObject(parent), m_db(db), m_feedCustomId(feed_custom_id), m_accountId(account_id), m_message(nullptr) {} int account_id, QList<Label*> available_labels,
QObject* parent)
: QObject(parent), m_db(db), m_feedCustomId(feed_custom_id), m_accountId(account_id), m_message(nullptr),
m_availableLabels(available_labels) {}
void MessageObject::setMessage(Message* message) { void MessageObject::setMessage(Message* message) {
m_message = message; m_message = message;
} }
bool MessageObject::isDuplicateWithAttribute(int attribute_check) const { bool MessageObject::isDuplicateWithAttribute(MessageObject::DuplicationAttributeCheck attribute_check) const {
if (attribute_check <= 0) {
qCriticalNN << LOGSEC_MESSAGEMODEL
<< "Bad DuplicationAttributeCheck value '"
<< attribute_check
<< "' was passed from JS filter script.";
return true;
}
// Check database according to duplication attribute_check. // Check database according to duplication attribute_check.
DuplicationAttributeCheck attrs = static_cast<DuplicationAttributeCheck>(attribute_check);
QSqlQuery q(*m_db); QSqlQuery q(*m_db);
QStringList where_clauses; QStringList where_clauses;
QList<QPair<QString, QVariant>> bind_values; QList<QPair<QString, QVariant>> bind_values;
// Now we construct the query according to parameter. // Now we construct the query according to parameter.
if ((attrs& DuplicationAttributeCheck::SameTitle) == DuplicationAttributeCheck::SameTitle) { if ((attribute_check& DuplicationAttributeCheck::SameTitle) == DuplicationAttributeCheck::SameTitle) {
where_clauses.append(QSL("title = :title")); where_clauses.append(QSL("title = :title"));
bind_values.append({ ":title", title() }); bind_values.append({ ":title", title() });
} }
if ((attrs& DuplicationAttributeCheck::SameUrl) == DuplicationAttributeCheck::SameUrl) { if ((attribute_check& DuplicationAttributeCheck::SameUrl) == DuplicationAttributeCheck::SameUrl) {
where_clauses.append(QSL("url = :url")); where_clauses.append(QSL("url = :url"));
bind_values.append({ ":url", url() }); bind_values.append({ ":url", url() });
} }
if ((attrs& DuplicationAttributeCheck::SameAuthor) == DuplicationAttributeCheck::SameAuthor) { if ((attribute_check& DuplicationAttributeCheck::SameAuthor) == DuplicationAttributeCheck::SameAuthor) {
where_clauses.append(QSL("author = :author")); where_clauses.append(QSL("author = :author"));
bind_values.append({ ":author", author() }); bind_values.append({ ":author", author() });
} }
if ((attrs& DuplicationAttributeCheck::SameDateCreated) == DuplicationAttributeCheck::SameDateCreated) { if ((attribute_check& DuplicationAttributeCheck::SameDateCreated) == DuplicationAttributeCheck::SameDateCreated) {
where_clauses.append(QSL("date_created = :date_created")); where_clauses.append(QSL("date_created = :date_created"));
bind_values.append({ ":date_created", created().toMSecsSinceEpoch() }); bind_values.append({ ":date_created", created().toMSecsSinceEpoch() });
} }
@ -204,7 +199,7 @@ bool MessageObject::isDuplicateWithAttribute(int attribute_check) const {
where_clauses.append(QSL("account_id = :account_id")); where_clauses.append(QSL("account_id = :account_id"));
bind_values.append({ ":account_id", accountId() }); bind_values.append({ ":account_id", accountId() });
if ((attrs& DuplicationAttributeCheck::AllFeedsSameAccount) != DuplicationAttributeCheck::AllFeedsSameAccount) { if ((attribute_check& DuplicationAttributeCheck::AllFeedsSameAccount) != DuplicationAttributeCheck::AllFeedsSameAccount) {
// Limit to current feed. // Limit to current feed.
where_clauses.append(QSL("feed = :feed")); where_clauses.append(QSL("feed = :feed"));
bind_values.append({ ":feed", feedCustomId() }); bind_values.append({ ":feed", feedCustomId() });
@ -244,6 +239,37 @@ bool MessageObject::isDuplicateWithAttribute(int attribute_check) const {
return false; return false;
} }
bool MessageObject::assignLabel(QString label_custom_id) const {
Label* lbl = boolinq::from(m_availableLabels).firstOrDefault([label_custom_id](Label* lbl) {
return lbl->customId() == label_custom_id;
});
if (lbl != nullptr) {
if (!m_message->m_assignedLabels.contains(lbl)) {
m_message->m_assignedLabels.append(lbl);
}
return true;
}
else {
return false;
}
}
bool MessageObject::deassignLabel(QString label_custom_id) const {
Label* lbl = boolinq::from(m_message->m_assignedLabels).firstOrDefault([label_custom_id](Label* lbl) {
return lbl->customId() == label_custom_id;
});
if (lbl != nullptr) {
m_message->m_assignedLabels.removeAll(lbl);
return true;
}
else {
return false;
}
}
QString MessageObject::title() const { QString MessageObject::title() const {
return m_message->m_title; return m_message->m_title;
} }
@ -311,3 +337,7 @@ int MessageObject::accountId() const {
QList<Label*> MessageObject::assignedLabels() const { QList<Label*> MessageObject::assignedLabels() const {
return m_message->m_assignedLabels; return m_message->m_assignedLabels;
} }
QList<Label*> MessageObject::availableLabels() const {
return m_availableLabels;
}

View File

@ -82,45 +82,11 @@ QDataStream& operator>>(QDataStream& in, Message& myObj);
uint qHash(const Message& key, uint seed); uint qHash(const Message& key, uint seed);
uint qHash(const Message& key); uint qHash(const Message& key);
enum class FilteringAction {
// Message is normally accepted and stored in DB.
Accept = 1,
// Message is ignored and now stored in DB.
Ignore = 2
};
enum class DuplicationAttributeCheck {
// Message with same title in DB.
SameTitle = 1,
// Message with same URL in DB.
SameUrl = 2,
// Message with same author in DB.
SameAuthor = 4,
// Messages with same creation date in DB.
SameDateCreated = 8,
// Compare with all messages from the account not only with messages from same feed.
// Note that this value must be used via bitwise OR with other values,
// for example 2 | 4 | 16.
AllFeedsSameAccount = 16
};
inline DuplicationAttributeCheck operator|(DuplicationAttributeCheck lhs, DuplicationAttributeCheck rhs) {
return static_cast<DuplicationAttributeCheck>(int(lhs) | int(rhs));
}
inline DuplicationAttributeCheck operator&(DuplicationAttributeCheck lhs, DuplicationAttributeCheck rhs) {
return static_cast<DuplicationAttributeCheck>(int(lhs) & int(rhs));
}
class MessageObject : public QObject { class MessageObject : public QObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QList<Label*> assignedLabels READ assignedLabels) Q_PROPERTY(QList<Label*> assignedLabels READ assignedLabels)
Q_PROPERTY(QList<Label*> availableLabels READ availableLabels)
Q_PROPERTY(QString feedCustomId READ feedCustomId) Q_PROPERTY(QString feedCustomId READ feedCustomId)
Q_PROPERTY(int accountId READ accountId) Q_PROPERTY(int accountId READ accountId)
Q_PROPERTY(QString title READ title WRITE setTitle) Q_PROPERTY(QString title READ title WRITE setTitle)
@ -132,16 +98,59 @@ class MessageObject : public QObject {
Q_PROPERTY(bool isImportant READ isImportant WRITE setIsImportant) Q_PROPERTY(bool isImportant READ isImportant WRITE setIsImportant)
public: public:
explicit MessageObject(QSqlDatabase* db, const QString& feed_custom_id, int account_id, QObject* parent = nullptr); enum class FilteringAction {
// Message is normally accepted and stored in DB.
Accept = 1,
// Message is ignored and now stored in DB.
Ignore = 2
};
Q_ENUM(FilteringAction)
enum class DuplicationAttributeCheck {
// Message with same title in DB.
SameTitle = 1,
// Message with same URL in DB.
SameUrl = 2,
// Message with same author in DB.
SameAuthor = 4,
// Messages with same creation date in DB.
SameDateCreated = 8,
// Compare with all messages from the account not only with messages from same feed.
// Note that this value must be used via bitwise OR with other values,
// for example 2 | 4 | 16.
AllFeedsSameAccount = 16
};
Q_ENUM(DuplicationAttributeCheck)
explicit MessageObject(QSqlDatabase* db, const QString& feed_custom_id,
int account_id, QList<Label*> available_labels,
QObject* parent = nullptr);
void setMessage(Message* message); void setMessage(Message* message);
// Check if message is duplicate with another messages in DB. // Check if message is duplicate with another messages in DB.
// Parameter "attribute_check" is DuplicationAttributeCheck enum // Parameter "attribute_check" is DuplicationAttributeCheck enum
// value casted to int. // value casted to int.
Q_INVOKABLE bool isDuplicateWithAttribute(int attribute_check) const; Q_INVOKABLE bool isDuplicateWithAttribute(DuplicationAttributeCheck attribute_check) const;
// Adds given label to list of assigned labels to this message.
// Returns true if label was assigned now or if the message already has it assigned.
Q_INVOKABLE bool assignLabel(QString label_custom_id) const;
// Removes given label from list of assigned labels of this message.
// Returns true if label was now removed or if it is not assigned to the message at all.
Q_INVOKABLE bool deassignLabel(QString label_custom_id) const;
// Returns list of assigned and available messages.
QList<Label*> assignedLabels() const; QList<Label*> assignedLabels() const;
QList<Label*> availableLabels() const;
// Generic Message's properties bindings. // Generic Message's properties bindings.
QString feedCustomId() const; QString feedCustomId() const;
@ -173,6 +182,17 @@ class MessageObject : public QObject {
QString m_feedCustomId; QString m_feedCustomId;
int m_accountId; int m_accountId;
Message* m_message; Message* m_message;
QList<Label*> m_availableLabels;
}; };
inline MessageObject::DuplicationAttributeCheck operator|(MessageObject::DuplicationAttributeCheck lhs,
MessageObject::DuplicationAttributeCheck rhs) {
return static_cast<MessageObject::DuplicationAttributeCheck>(int(lhs) | int(rhs));
}
inline MessageObject::DuplicationAttributeCheck operator&(MessageObject::DuplicationAttributeCheck lhs,
MessageObject::DuplicationAttributeCheck rhs) {
return static_cast<MessageObject::DuplicationAttributeCheck>(int(lhs) & int(rhs));
}
#endif // MESSAGE_H #endif // MESSAGE_H

View File

@ -7,7 +7,7 @@
MessageFilter::MessageFilter(int id, QObject* parent) : QObject(parent), m_id(id) {} MessageFilter::MessageFilter(int id, QObject* parent) : QObject(parent), m_id(id) {}
FilteringAction MessageFilter::filterMessage(QJSEngine* engine) { MessageObject::FilteringAction MessageFilter::filterMessage(QJSEngine* engine) {
// NOTE: Filter is represented by JavaScript code, each filter must define // NOTE: Filter is represented by JavaScript code, each filter must define
// function with "filterMessage()" prototype. There is a global "msg" object // function with "filterMessage()" prototype. There is a global "msg" object
// representing "message" available. // representing "message" available.
@ -56,7 +56,7 @@ FilteringAction MessageFilter::filterMessage(QJSEngine* engine) {
throw FilteringException(error, message); throw FilteringException(error, message);
} }
return FilteringAction(filter_output.toInt()); return MessageObject::FilteringAction(filter_output.toInt());
} }
int MessageFilter::id() const { int MessageFilter::id() const {
@ -79,10 +79,17 @@ void MessageFilter::setScript(const QString& script) {
m_script = script; m_script = script;
} }
void MessageFilter::initializeFilteringEngine(QJSEngine& engine) { void MessageFilter::initializeFilteringEngine(QJSEngine& engine, MessageObject* message_wrapper) {
engine.installExtensions(QJSEngine::Extension::ConsoleExtension); engine.installExtensions(QJSEngine::Extension::ConsoleExtension);
engine.globalObject().setProperty("MSG_ACCEPT", int(FilteringAction::Accept)); engine.globalObject().setProperty("MSG_ACCEPT", int(MessageObject::FilteringAction::Accept));
engine.globalObject().setProperty("MSG_IGNORE", int(FilteringAction::Ignore)); engine.globalObject().setProperty("MSG_IGNORE", int(MessageObject::FilteringAction::Ignore));
// Register the wrapper.
auto js_object = engine.newQObject(message_wrapper);
auto js_meta_object = engine.newQMetaObject(&message_wrapper->staticMetaObject);
engine.globalObject().setProperty("msg", js_object);
engine.globalObject().setProperty(message_wrapper->staticMetaObject.className(), js_meta_object);
} }
void MessageFilter::setId(int id) { void MessageFilter::setId(int id) {

View File

@ -16,7 +16,7 @@ class MessageFilter : public QObject {
public: public:
explicit MessageFilter(int id = -1, QObject* parent = nullptr); explicit MessageFilter(int id = -1, QObject* parent = nullptr);
FilteringAction filterMessage(QJSEngine* engine); MessageObject::FilteringAction filterMessage(QJSEngine* engine);
int id() const; int id() const;
void setId(int id); void setId(int id);
@ -27,7 +27,7 @@ class MessageFilter : public QObject {
QString script() const; QString script() const;
void setScript(const QString& script); void setScript(const QString& script);
static void initializeFilteringEngine(QJSEngine& engine); static void initializeFilteringEngine(QJSEngine& engine, MessageObject* message_wrapper);
private: private:
int m_id; int m_id;

View File

@ -147,31 +147,26 @@ void FormMessageFiltersManager::loadFilter() {
void FormMessageFiltersManager::testFilter() { void FormMessageFiltersManager::testFilter() {
// Perform per-message filtering. // Perform per-message filtering.
QJSEngine filter_engine; QJSEngine filter_engine;
MessageFilter::initializeFilteringEngine(filter_engine);
QSqlDatabase database = qApp->database()->connection(metaObject()->className()); QSqlDatabase database = qApp->database()->connection(metaObject()->className());
MessageObject msg_obj(&database,
// Create JavaScript communication wrapper for the message. QString::number(NO_PARENT_CATEGORY),
MessageObject msg_obj(&database, QString::number(NO_PARENT_CATEGORY), NO_PARENT_CATEGORY); selectedAccount() != nullptr
? selectedAccount()->accountId()
// Register the wrapper. : NO_PARENT_CATEGORY,
auto js_object = filter_engine.newQObject(&msg_obj); {});
auto* fltr = selectedFilter();
filter_engine.globalObject().setProperty("msg", js_object);
Message msg = testingMessage(); Message msg = testingMessage();
MessageFilter::initializeFilteringEngine(filter_engine, &msg_obj);
msg_obj.setMessage(&msg); msg_obj.setMessage(&msg);
auto* fltr = selectedFilter();
try { try {
FilteringAction decision = fltr->filterMessage(&filter_engine); MessageObject::FilteringAction decision = fltr->filterMessage(&filter_engine);
m_ui.m_txtErrors->setTextColor(decision == FilteringAction::Accept ? Qt::GlobalColor::darkGreen : Qt::GlobalColor::red); m_ui.m_txtErrors->setTextColor(decision == MessageObject::FilteringAction::Accept ? Qt::GlobalColor::darkGreen : Qt::GlobalColor::red);
QString answer = tr("Message will be %1.\n\n").arg(decision == FilteringAction::Accept QString answer = tr("Message will be %1.\n\n").arg(decision == MessageObject::FilteringAction::Accept
? tr("ACCEPTED") ? tr("ACCEPTED")
: tr("REJECTED")); : tr("REJECTED"));
@ -351,6 +346,7 @@ void FormMessageFiltersManager::initializeTestingMessage() {
Message FormMessageFiltersManager::testingMessage() const { Message FormMessageFiltersManager::testingMessage() const {
Message msg; Message msg;
msg.m_feedId = NO_PARENT_CATEGORY;
msg.m_url = m_ui.m_txtSampleUrl->text(); msg.m_url = m_ui.m_txtSampleUrl->text();
msg.m_title = m_ui.m_txtSampleTitle->text(); msg.m_title = m_ui.m_txtSampleTitle->text();
msg.m_author = m_ui.m_txtSampleAuthor->text(); msg.m_author = m_ui.m_txtSampleAuthor->text();

View File

@ -116,7 +116,7 @@ QString s_customLogFile = QString();
void Application::performLogging(QtMsgType type, const QMessageLogContext& context, const QString& msg) { void Application::performLogging(QtMsgType type, const QMessageLogContext& context, const QString& msg) {
#ifndef QT_NO_DEBUG_OUTPUT #ifndef QT_NO_DEBUG_OUTPUT
QString console_message = qFormatLogMessage(type, context, msg); QString console_message = qFormatLogMessage(type, context, msg);
std::wcout << console_message.toStdWString() << std::endl; std::cout << console_message.toStdString() << std::endl;
if (!s_customLogFile.isEmpty()) { if (!s_customLogFile.isEmpty()) {
QFile log_file(s_customLogFile); QFile log_file(s_customLogFile);
@ -584,7 +584,8 @@ void Application::determineFirstRuns() {
void Application::parseCmdArguments() { void Application::parseCmdArguments() {
QCommandLineOption log_file(QStringList() << CLI_LOG_SHORT << CLI_LOG_LONG, QCommandLineOption log_file(QStringList() << CLI_LOG_SHORT << CLI_LOG_LONG,
"Write application debug log to file.", "log-file"); "Write application debug log to file. Note that logging to file may slow application down.",
"log-file");
QCommandLineOption custom_data_folder(QStringList() << CLI_DAT_SHORT << CLI_DAT_LONG, QCommandLineOption custom_data_folder(QStringList() << CLI_DAT_SHORT << CLI_DAT_LONG,
"Use custom folder for user data and disable single instance application mode.", "Use custom folder for user data and disable single instance application mode.",
"user-data-folder"); "user-data-folder");

View File

@ -70,6 +70,10 @@ QString WebFactory::stripTags(QString text) {
} }
QString WebFactory::unescapeHtml(const QString& html) { QString WebFactory::unescapeHtml(const QString& html) {
if (html.isEmpty()) {
return html;
}
if (m_htmlNamedEntities.isEmpty()) { if (m_htmlNamedEntities.isEmpty()) {
generateUnescapes(); generateUnescapes();
} }

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>622</width> <width>519</width>
<height>442</height> <height>352</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -15,263 +15,287 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<layout class="QFormLayout" name="formLayout"> <widget class="QTabWidget" name="tabWidget">
<item row="0" column="0"> <property name="currentIndex">
<widget class="QLabel" name="m_lblParentCategory"> <number>0</number>
<property name="text"> </property>
<string>Parent category</string> <widget class="QWidget" name="tabGeneral">
</property> <attribute name="title">
<property name="buddy"> <string>General</string>
<cstring>m_cmbParentCategory</cstring> </attribute>
</property> <layout class="QFormLayout" name="formLayout_4">
</widget> <item row="0" column="0">
</item> <widget class="QLabel" name="m_lblParentCategory">
<item row="0" column="1">
<widget class="QComboBox" name="m_cmbParentCategory">
<property name="toolTip">
<string>Select parent item for your feed.</string>
</property>
<property name="iconSize">
<size>
<width>12</width>
<height>12</height>
</size>
</property>
<property name="frame">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Type</string>
</property>
<property name="buddy">
<cstring>m_cmbType</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="m_cmbType">
<property name="toolTip">
<string>Select type of the standard feed.</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Encoding</string>
</property>
<property name="buddy">
<cstring>m_cmbEncoding</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="m_cmbEncoding">
<property name="toolTip">
<string>Select encoding of the standard feed. If you are unsure about the encoding, then select &quot;UTF-8&quot; encoding.</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Auto-update</string>
</property>
<property name="buddy">
<cstring>m_cmbAutoUpdateType</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="m_cmbAutoUpdateType">
<property name="toolTip">
<string>Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings.</string>
</property>
</widget>
</item>
<item>
<widget class="TimeSpinBox" name="m_spinAutoUpdateInterval">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="0">
<widget class="QLabel" name="m_lblTitle">
<property name="text">
<string>Title</string>
</property>
<property name="buddy">
<cstring>m_txtTitle</cstring>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="LineEditWithStatus" name="m_txtTitle" native="true"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="m_lblDescription">
<property name="text">
<string>Description</string>
</property>
<property name="buddy">
<cstring>m_txtDescription</cstring>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="LineEditWithStatus" name="m_txtDescription" native="true"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>URL</string>
</property>
<property name="buddy">
<cstring>m_txtUrl</cstring>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="LineEditWithStatus" name="m_txtUrl" native="true"/>
</item>
<item row="7" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="m_btnFetchMetadata">
<property name="text"> <property name="text">
<string>Fetch it now</string> <string>Parent category</string>
</property>
<property name="buddy">
<cstring>m_cmbParentCategory</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item row="0" column="1">
<widget class="LabelWithStatus" name="m_lblFetchMetadata" native="true"> <widget class="QComboBox" name="m_cmbParentCategory">
<property name="toolTip">
<string>Select parent item for your feed.</string>
</property>
<property name="iconSize">
<size>
<width>12</width>
<height>12</height>
</size>
</property>
<property name="frame">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Type</string>
</property>
<property name="buddy">
<cstring>m_cmbType</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="m_cmbType">
<property name="toolTip">
<string>Select type of the standard feed.</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Encoding</string>
</property>
<property name="buddy">
<cstring>m_cmbEncoding</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="m_cmbEncoding">
<property name="toolTip">
<string>Select encoding of the standard feed. If you are unsure about the encoding, then select &quot;UTF-8&quot; encoding.</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="m_lblTitle">
<property name="text">
<string>Title</string>
</property>
<property name="buddy">
<cstring>m_txtTitle</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="LineEditWithStatus" name="m_txtTitle" native="true"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="m_lblDescription">
<property name="text">
<string>Description</string>
</property>
<property name="buddy">
<cstring>m_txtDescription</cstring>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="LineEditWithStatus" name="m_txtDescription" native="true"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>URL</string>
</property>
<property name="buddy">
<cstring>m_txtUrl</cstring>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="LineEditWithStatus" name="m_txtUrl" native="true"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Fetch metadata</string>
</property>
<property name="buddy">
<cstring>m_btnFetchMetadata</cstring>
</property>
</widget>
</item>
<item row="6" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="m_btnFetchMetadata">
<property name="text">
<string>Fetch it now</string>
</property>
</widget>
</item>
<item>
<widget class="LabelWithStatus" name="m_lblFetchMetadata" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="7" column="0">
<widget class="QLabel" name="m_lblIcon">
<property name="text">
<string>Icon</string>
</property>
<property name="buddy">
<cstring>m_btnIcon</cstring>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QToolButton" name="m_btnIcon">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="layoutDirection"> <property name="minimumSize">
<enum>Qt::RightToLeft</enum> <size>
<width>40</width>
<height>40</height>
</size>
</property>
<property name="toolTip">
<string>Select icon for your feed.</string>
</property>
<property name="text">
<string notr="true"/>
</property>
<property name="iconSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
<property name="autoRaise">
<bool>false</bool>
</property>
<property name="arrowType">
<enum>Qt::NoArrow</enum>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </widget>
<item row="8" column="0"> <widget class="QWidget" name="tabUpdating">
<widget class="QLabel" name="m_lblIcon"> <attribute name="title">
<property name="text"> <string>Auto-updating</string>
<string>Icon</string> </attribute>
</property> <layout class="QFormLayout" name="formLayout">
<property name="buddy"> <item row="0" column="0">
<cstring>m_btnIcon</cstring> <widget class="QLabel" name="label_6">
</property> <property name="text">
</widget> <string>Auto-update</string>
</item> </property>
<item row="8" column="1"> <property name="buddy">
<widget class="QToolButton" name="m_btnIcon"> <cstring>m_cmbAutoUpdateType</cstring>
<property name="sizePolicy"> </property>
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> </widget>
<horstretch>0</horstretch> </item>
<verstretch>0</verstretch> <item row="0" column="1">
</sizepolicy> <layout class="QHBoxLayout" name="horizontalLayout">
</property> <item>
<property name="minimumSize"> <widget class="QComboBox" name="m_cmbAutoUpdateType">
<size> <property name="toolTip">
<width>40</width> <string>Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings.</string>
<height>40</height> </property>
</size> </widget>
</property> </item>
<property name="toolTip"> <item>
<string>Select icon for your feed.</string> <widget class="TimeSpinBox" name="m_spinAutoUpdateInterval">
</property> <property name="enabled">
<property name="text"> <bool>false</bool>
<string notr="true"/> </property>
</property> </widget>
<property name="iconSize"> </item>
<size> </layout>
<width>20</width> </item>
<height>20</height> </layout>
</size> </widget>
</property> <widget class="QWidget" name="tabNetwork">
<property name="popupMode"> <attribute name="title">
<enum>QToolButton::InstantPopup</enum> <string>Network</string>
</property> </attribute>
<property name="autoRaise"> <layout class="QFormLayout" name="formLayout_3">
<bool>false</bool> <item row="0" column="0">
</property> <widget class="QGroupBox" name="m_gbAuthentication">
<property name="arrowType"> <property name="toolTip">
<enum>Qt::NoArrow</enum> <string>Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported.</string>
</property> </property>
</widget> <property name="title">
</item> <string>Requires HTTP authentication</string>
<item row="9" column="0" colspan="2"> </property>
<widget class="QGroupBox" name="m_gbAuthentication"> <property name="flat">
<property name="toolTip"> <bool>false</bool>
<string>Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported.</string> </property>
</property> <property name="checkable">
<property name="title"> <bool>true</bool>
<string>Requires HTTP authentication</string> </property>
</property> <property name="checked">
<property name="flat"> <bool>false</bool>
<bool>false</bool> </property>
</property> <layout class="QFormLayout" name="formLayout_2">
<property name="checkable"> <item row="0" column="0">
<bool>true</bool> <widget class="QLabel" name="label_4">
</property> <property name="text">
<property name="checked"> <string>Username</string>
<bool>false</bool> </property>
</property> <property name="buddy">
<layout class="QFormLayout" name="formLayout_2"> <cstring>m_txtUsername</cstring>
<item row="0" column="0"> </property>
<widget class="QLabel" name="label_4"> </widget>
<property name="text"> </item>
<string>Username</string> <item row="0" column="1">
</property> <widget class="LineEditWithStatus" name="m_txtUsername" native="true"/>
<property name="buddy"> </item>
<cstring>m_txtUsername</cstring> <item row="1" column="0">
</property> <widget class="QLabel" name="label_5">
</widget> <property name="text">
</item> <string>Password</string>
<item row="0" column="1"> </property>
<widget class="LineEditWithStatus" name="m_txtUsername" native="true"/> <property name="buddy">
</item> <cstring>m_txtPassword</cstring>
<item row="1" column="0"> </property>
<widget class="QLabel" name="label_5"> </widget>
<property name="text"> </item>
<string>Password</string> <item row="1" column="1">
</property> <widget class="LineEditWithStatus" name="m_txtPassword" native="true"/>
<property name="buddy"> </item>
<cstring>m_txtPassword</cstring> </layout>
</property> </widget>
</widget> </item>
</item> </layout>
<item row="1" column="1"> </widget>
<widget class="LineEditWithStatus" name="m_txtPassword" native="true"/> </widget>
</item>
</layout>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Fetch metadata</string>
</property>
<property name="buddy">
<cstring>m_btnFetchMetadata</cstring>
</property>
</widget>
</item>
</layout>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="m_buttonBox"> <widget class="QDialogButtonBox" name="m_buttonBox">

View File

@ -110,7 +110,10 @@ QIcon Label::generateIcon(const QColor& color) {
} }
void Label::assignToMessage(const Message& msg) { void Label::assignToMessage(const Message& msg) {
QSqlDatabase database = qApp->database()->connection(metaObject()->className()); bool is_main_thread = QThread::currentThread() == qApp->thread();
QSqlDatabase database = is_main_thread ?
qApp->database()->connection(metaObject()->className()) :
qApp->database()->connection(QSL("feed_upd"));
if (getParentServiceRoot()->onBeforeLabelMessageAssignmentChanged({ this }, { msg }, true)) { if (getParentServiceRoot()->onBeforeLabelMessageAssignmentChanged({ this }, { msg }, true)) {
DatabaseQueries::assignLabelToMessage(database, this, msg); DatabaseQueries::assignLabelToMessage(database, this, msg);
@ -120,7 +123,10 @@ void Label::assignToMessage(const Message& msg) {
} }
void Label::deassignFromMessage(const Message& msg) { void Label::deassignFromMessage(const Message& msg) {
QSqlDatabase database = qApp->database()->connection(metaObject()->className()); bool is_main_thread = QThread::currentThread() == qApp->thread();
QSqlDatabase database = is_main_thread ?
qApp->database()->connection(metaObject()->className()) :
qApp->database()->connection(QSL("feed_upd"));
if (getParentServiceRoot()->onBeforeLabelMessageAssignmentChanged({ this }, { msg }, false)) { if (getParentServiceRoot()->onBeforeLabelMessageAssignmentChanged({ this }, { msg }, false)) {
DatabaseQueries::deassignLabelFromMessage(database, this, msg); DatabaseQueries::deassignLabelFromMessage(database, this, msg);

View File

@ -11,7 +11,6 @@ class RSSGUARD_DLLSPEC Label : public RootItem {
Q_OBJECT Q_OBJECT
// Added for message filtering with labels. // Added for message filtering with labels.
Q_PROPERTY(QString customId READ customId)
Q_PROPERTY(QColor color READ color) Q_PROPERTY(QColor color READ color)
public: public:

View File

@ -23,6 +23,7 @@ class RSSGUARD_DLLSPEC RootItem : public QObject {
// Added for message filtering with labels. // Added for message filtering with labels.
Q_PROPERTY(QString title READ title) Q_PROPERTY(QString title READ title)
Q_PROPERTY(QString customId READ customId)
public: public:
enum class ReadStatus { enum class ReadStatus {