Work on gmail attachments.

This commit is contained in:
Martin Rotter 2017-10-23 13:04:57 +02:00
parent b69a615136
commit fc1f821144
21 changed files with 119 additions and 43 deletions

View File

@ -88,6 +88,7 @@
#define INTERNAL_URL_BLANK "http://rssguard.blank"
#define INTERNAL_URL_MESSAGE_HOST "rssguard.message"
#define INTERNAL_URL_BLANK_HOST "rssguard.blank"
#define INTERNAL_URL_PASSATTACHMENT "http://rssguard.passattachment"
#define FEED_INITIAL_OPML_PATTERN "feeds-%1.opml"

View File

@ -41,28 +41,28 @@ void FormAbout::loadSettingsAndPaths() {
void FormAbout::loadLicenseAndInformation() {
try {
m_ui.m_txtLicenseGnu->setText(IOFactory::readTextFile(APP_INFO_PATH + QL1S("/COPYING_GNU_GPL_HTML")));
m_ui.m_txtLicenseGnu->setText(IOFactory::readFile(APP_INFO_PATH + QL1S("/COPYING_GNU_GPL_HTML")));
}
catch (...) {
m_ui.m_txtLicenseGnu->setText(tr("License not found."));
}
try {
m_ui.m_txtLicenseGnu->setText(IOFactory::readTextFile(APP_INFO_PATH + QL1S("/COPYING_GNU_GPL_HTML")));
m_ui.m_txtLicenseGnu->setText(IOFactory::readFile(APP_INFO_PATH + QL1S("/COPYING_GNU_GPL_HTML")));
}
catch (...) {
m_ui.m_txtLicenseGnu->setText(tr("License not found."));
}
try {
m_ui.m_txtChangelog->setText(IOFactory::readTextFile(APP_INFO_PATH + QL1S("/CHANGELOG")));
m_ui.m_txtChangelog->setText(IOFactory::readFile(APP_INFO_PATH + QL1S("/CHANGELOG")));
}
catch (...) {
m_ui.m_txtChangelog->setText(tr("Changelog not found."));
}
try {
m_ui.m_txtLicenseBsd->setText(IOFactory::readTextFile(APP_INFO_PATH + QL1S("/COPYING_BSD")));
m_ui.m_txtLicenseBsd->setText(IOFactory::readFile(APP_INFO_PATH + QL1S("/COPYING_BSD")));
}
catch (...) {
m_ui.m_txtLicenseBsd->setText(tr("License not found."));

View File

@ -110,7 +110,7 @@ void WebBrowser::loadMessages(const QList<Message>& messages, RootItem* root) {
m_root = root;
if (!m_root.isNull()) {
m_webView->loadMessages(messages);
m_webView->loadMessages(messages, root);
show();
}
}

View File

@ -15,7 +15,7 @@
#include <QWheelEvent>
WebViewer::WebViewer(QWidget* parent) : QWebEngineView(parent) {
WebViewer::WebViewer(QWidget* parent) : QWebEngineView(parent), m_root(nullptr) {
WebPage* page = new WebPage(this);
connect(page, &WebPage::messageStatusChangeRequested, this, &WebViewer::messageStatusChangeRequested);
@ -70,7 +70,7 @@ bool WebViewer::resetWebPageZoom() {
}
}
void WebViewer::loadMessages(const QList<Message>& messages) {
void WebViewer::loadMessages(const QList<Message>& messages, RootItem* root) {
Skin skin = qApp->skins()->currentSkin();
QString messages_layout;
QString single_message_layout = skin.m_layoutMarkup;
@ -80,7 +80,17 @@ void WebViewer::loadMessages(const QList<Message>& messages) {
QString enclosure_images;
foreach (const Enclosure& enclosure, message.m_enclosures) {
enclosures += skin.m_enclosureMarkup.arg(enclosure.m_url, tr("Attachment"), enclosure.m_mimeType);
QString enc_url;
if (!enclosure.m_url.contains(QRegularExpression(QSL("^(http|ftp|\\/)")))) {
enc_url = QString(INTERNAL_URL_PASSATTACHMENT) + QL1S("/?") + enclosure.m_url;
}
else {
enc_url = enclosure.m_url;
}
enclosures += skin.m_enclosureMarkup.arg(enc_url,
tr("Attachment"), enclosure.m_mimeType);
if (enclosure.m_mimeType.startsWith(QSL("image/"))) {
// Add thumbnail image.
@ -106,6 +116,7 @@ void WebViewer::loadMessages(const QList<Message>& messages) {
.arg(enclosure_images));
}
m_root = root;
m_messageContents = skin.m_layoutMarkupWrapper.arg(messages.size() == 1 ? messages.at(0).m_title : tr("Newspaper view"),
messages_layout);
bool previously_enabled = isEnabled();
@ -115,10 +126,6 @@ void WebViewer::loadMessages(const QList<Message>& messages) {
setEnabled(previously_enabled);
}
void WebViewer::loadMessage(const Message& message) {
loadMessages(QList<Message>() << message);
}
void WebViewer::clear() {
bool previously_enabled = isEnabled();
@ -163,3 +170,7 @@ void WebViewer::wheelEvent(QWheelEvent* event) {
}
}
}
RootItem* WebViewer::root() const {
return m_root;
}

View File

@ -8,6 +8,8 @@
#include "core/message.h"
#include "network-web/webpage.h"
class RootItem;
class WebViewer : public QWebEngineView {
Q_OBJECT
@ -22,6 +24,7 @@ class WebViewer : public QWebEngineView {
}
WebPage* page() const;
RootItem* root() const;
public slots:
@ -31,8 +34,7 @@ class WebViewer : public QWebEngineView {
bool resetWebPageZoom();
void displayMessage();
void loadMessages(const QList<Message>& messages);
void loadMessage(const Message& message);
void loadMessages(const QList<Message>& messages, RootItem* root);
void clear();
protected:
@ -45,6 +47,7 @@ class WebViewer : public QWebEngineView {
void messageStatusChangeRequested(int message_id, WebPage::MessageStatusChange change);
private:
RootItem* m_root;
QString m_messageContents;
};

View File

@ -5,12 +5,12 @@
#include "definitions/definitions.h"
#include "exceptions/ioexception.h"
#include <QDataStream>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QObject>
#include <QTemporaryFile>
#include <QTextStream>
IOFactory::IOFactory() {}
@ -68,7 +68,7 @@ QString IOFactory::filterBadCharsFromFilename(const QString& name) {
return value;
}
QByteArray IOFactory::readTextFile(const QString& file_path) {
QByteArray IOFactory::readFile(const QString& file_path) {
QFile input_file(file_path);
QByteArray input_data;
@ -82,15 +82,11 @@ QByteArray IOFactory::readTextFile(const QString& file_path) {
}
}
void IOFactory::writeTextFile(const QString& file_path, const QByteArray& data, const QString& encoding) {
Q_UNUSED(encoding)
void IOFactory::writeFile(const QString& file_path, const QByteArray& data) {
QFile input_file(file_path);
QTextStream stream(&input_file);
if (input_file.open(QIODevice::Text | QIODevice::WriteOnly)) {
stream << data;
stream.flush();
input_file.flush();
if (input_file.open(QIODevice::WriteOnly)) {
input_file.write(data);
input_file.close();
}
else {

View File

@ -30,8 +30,8 @@ class IOFactory {
// Returns contents of a file.
// Throws exception when no such file exists.
static QByteArray readTextFile(const QString& file_path);
static void writeTextFile(const QString& file_path, const QByteArray& data, const QString& encoding = QSL("UTF-8"));
static QByteArray readFile(const QString& file_path);
static void writeFile(const QString& file_path, const QByteArray& data);
// Copies file, overwrites destination.
static bool copyFile(const QString& source, const QString& destination);

View File

@ -122,17 +122,17 @@ Skin SkinFactory::skinInfo(const QString& skin_name, bool* ok) const {
// So if one uses "##/images/border.png" in QSS then it is
// replaced by fully absolute path and target file can
// be safely loaded.
skin.m_layoutMarkupWrapper = QString::fromUtf8(IOFactory::readTextFile(skin_folder + QL1S("html_wrapper.html")));
skin.m_layoutMarkupWrapper = QString::fromUtf8(IOFactory::readFile(skin_folder + QL1S("html_wrapper.html")));
skin.m_layoutMarkupWrapper = skin.m_layoutMarkupWrapper.replace(QSL("##"), APP_SKIN_PATH + QL1S("/") + skin_name);
skin.m_enclosureImageMarkup = QString::fromUtf8(IOFactory::readTextFile(skin_folder + QL1S("html_enclosure_image.html")));
skin.m_enclosureImageMarkup = QString::fromUtf8(IOFactory::readFile(skin_folder + QL1S("html_enclosure_image.html")));
skin.m_enclosureImageMarkup = skin.m_enclosureImageMarkup.replace(QSL("##"), APP_SKIN_PATH + QL1S("/") + skin_name);
skin.m_layoutMarkup = QString::fromUtf8(IOFactory::readTextFile(skin_folder + QL1S("html_single_message.html")));
skin.m_layoutMarkup = QString::fromUtf8(IOFactory::readFile(skin_folder + QL1S("html_single_message.html")));
skin.m_layoutMarkup = skin.m_layoutMarkup.replace(QSL("##"), APP_SKIN_PATH + QL1S("/") + skin_name);
skin.m_enclosureMarkup = QString::fromUtf8(IOFactory::readTextFile(skin_folder + QL1S("html_enclosure_every.html")));
skin.m_enclosureMarkup = QString::fromUtf8(IOFactory::readFile(skin_folder + QL1S("html_enclosure_every.html")));
skin.m_enclosureMarkup = skin.m_enclosureMarkup.replace(QSL("##"), APP_SKIN_PATH + QL1S("/") + skin_name);
skin.m_rawData = QString::fromUtf8(IOFactory::readTextFile(skin_folder + QL1S("theme.css")));
skin.m_rawData = QString::fromUtf8(IOFactory::readFile(skin_folder + QL1S("theme.css")));
skin.m_rawData = skin.m_rawData.replace(QSL("##"), APP_SKIN_PATH + QL1S("/") + skin_name);
skin.m_adblocked = QString::fromUtf8(IOFactory::readTextFile(skin_folder + QL1S("html_adblocked.html")));
skin.m_adblocked = QString::fromUtf8(IOFactory::readFile(skin_folder + QL1S("html_adblocked.html")));
if (ok != nullptr) {
*ok = !skin.m_author.isEmpty() && !skin.m_version.isEmpty() &&

View File

@ -124,12 +124,12 @@ quint64 TextFactory::initializeSecretEncryptionKey() {
QString encryption_file_path = qApp->settings()->pathName() + QDir::separator() + ENCRYPTION_FILE_NAME;
try {
s_encryptionKey = (quint64) QString(IOFactory::readTextFile(encryption_file_path)).toLongLong();
s_encryptionKey = (quint64) QString(IOFactory::readFile(encryption_file_path)).toLongLong();
}
catch (ApplicationException) {
// Well, key does not exist or is invalid, generate and save one.
s_encryptionKey = generateSecretEncryptionKey();
IOFactory::writeTextFile(encryption_file_path, QString::number(s_encryptionKey).toLocal8Bit());
IOFactory::writeFile(encryption_file_path, QString::number(s_encryptionKey).toLocal8Bit());
}
}

View File

@ -281,7 +281,7 @@ void AdBlockCustomList::loadSubscription(const QStringList& disabledRules) {
QString rules;
try {
rules = QString::fromUtf8(IOFactory::readTextFile(filePath()));
rules = QString::fromUtf8(IOFactory::readFile(filePath()));
}
catch (ApplicationException&) {}

View File

@ -4,9 +4,12 @@
#include "definitions/definitions.h"
#include "gui/webviewer.h"
#include "services/abstract/rootitem.h"
#include "services/abstract/serviceroot.h"
#include <QString>
#include <QStringList>
#include <QUrl>
WebPage::WebPage(QObject* parent) : QWebEnginePage(parent) {
setBackgroundColor(Qt::transparent);
@ -45,6 +48,14 @@ void WebPage::javaScriptAlert(const QUrl& securityOrigin, const QString& msg) {
}
bool WebPage::acceptNavigationRequest(const QUrl& url, NavigationType type, bool isMainFrame) {
const RootItem* root = view()->root();
if (url.toString().startsWith(INTERNAL_URL_PASSATTACHMENT) &&
root != nullptr &&
root->getParentServiceRoot()->downloadAttachmentOnMyOwn(url)) {
return false;
}
if (url.host() == INTERNAL_URL_MESSAGE_HOST) {
setHtml(view()->messageContents(), QUrl(INTERNAL_URL_MESSAGE));
return true;

View File

@ -61,6 +61,11 @@ RecycleBin* ServiceRoot::recycleBin() const {
return m_recycleBin;
}
bool ServiceRoot::downloadAttachmentOnMyOwn(const QUrl& url) const {
Q_UNUSED(url)
return false;
}
QList<QAction*> ServiceRoot::contextMenu() {
return serviceMenu();
}

View File

@ -33,6 +33,7 @@ class ServiceRoot : public RootItem {
bool deleteViaGui();
bool markAsReadUnread(ReadStatus status);
virtual RecycleBin* recycleBin() const;
virtual bool downloadAttachmentOnMyOwn(const QUrl& url) const;
QList<Message> undeletedMessages() const;
virtual bool supportsFeedAdding() const;

View File

@ -7,10 +7,13 @@
#define GMAIL_OAUTH_TOKEN_URL "https://accounts.google.com/o/oauth2/token"
#define GMAIL_OAUTH_SCOPE "https://mail.google.com/"
#define GMAIL_API_GET_ATTACHMENT "https://www.googleapis.com/gmail/v1/users/me/messages/%20/attachments/"
#define GMAIL_API_LABELS_LIST "https://www.googleapis.com/gmail/v1/users/me/labels"
#define GMAIL_API_MSGS_LIST "https://www.googleapis.com/gmail/v1/users/me/messages"
#define GMAIL_API_BATCH "https://www.googleapis.com/batch"
#define GMAIL_ATTACHMENT_SEP "####"
#define GMAIL_DEFAULT_BATCH_SIZE 50
#define GMAIL_MAX_BATCH_SIZE 999
#define GMAIL_MIN_BATCH_SIZE 20

View File

@ -10,8 +10,12 @@
#include "services/gmail/definitions.h"
#include "services/gmail/gmailentrypoint.h"
#include "services/gmail/gmailfeed.h"
#include "services/gmail/gui/formeditgmailaccount.h"
#include "services/gmail/network/gmailnetworkfactory.h"
#include <QJsonDocument>
#include <QJsonObject>
GmailServiceRoot::GmailServiceRoot(GmailNetworkFactory* network, RootItem* parent) : ServiceRoot(parent),
CacheForServiceRoot(), m_serviceMenu(QList<QAction*>()), m_network(network) {
if (network == nullptr) {
@ -100,14 +104,36 @@ void GmailServiceRoot::saveAccountDataToDatabase() {
}
}
bool GmailServiceRoot::downloadAttachmentOnMyOwn(const QUrl& url) const {
QString str_url = url.toString();
QString attachment_id = str_url.mid(str_url.indexOf(QL1C('?')) + 1);
QStringList parts = attachment_id.split(QL1S(GMAIL_ATTACHMENT_SEP));
Downloader* down = network()->downloadAttachment(parts.at(1));
connect(down, &Downloader::completed, [parts, down](QNetworkReply::NetworkError status, QByteArray contents) {
if (status == QNetworkReply::NetworkError::NoError) {
QString data = QJsonDocument::fromJson(contents).object()["data"].toString();
if (!data.isEmpty()) {
IOFactory::writeFile(parts.at(0), QByteArray::fromBase64(data.toLocal8Bit(),
QByteArray::Base64Option::Base64UrlEncoding));
}
}
down->deleteLater();
});
return true;
}
bool GmailServiceRoot::canBeEdited() const {
return true;
}
bool GmailServiceRoot::editViaGui() {
//FormEditInoreaderAccount form_pointer(qApp->mainFormWidget());
// TODO: dodělat
//form_pointer.execForEdit(this);
FormEditGmailAccount form_pointer(qApp->mainFormWidget());
form_pointer.execForEdit(this);
return true;
}

View File

@ -17,6 +17,8 @@ class GmailServiceRoot : public ServiceRoot, public CacheForServiceRoot {
void saveAccountDataToDatabase();
bool downloadAttachmentOnMyOwn(const QUrl& url) const;
void setNetwork(GmailNetworkFactory* network);
GmailNetworkFactory* network() const;

View File

@ -100,6 +100,22 @@ void GmailNetworkFactory::setUsername(const QString& username) {
return decodeFeedCategoriesData(category_data);
}*/
Downloader* GmailNetworkFactory::downloadAttachment(const QString& attachment_id) {
Downloader* downloader = new Downloader();
QString bearer = m_oauth2->bearer().toLocal8Bit();
if (bearer.isEmpty()) {
return nullptr;
}
QString target_url = QString(GMAIL_API_GET_ATTACHMENT) + attachment_id;
downloader->appendRawHeader(QString(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(), bearer.toLocal8Bit());
downloader->downloadFile(target_url);
return downloader;
}
QList<Message> GmailNetworkFactory::messages(const QString& stream_id, Feed::Status& error) {
Downloader downloader;
QEventLoop loop;
@ -402,9 +418,7 @@ bool GmailNetworkFactory::fillFullMessage(Message& msg, const QJsonObject& json,
}
else {
// We have attachment.
// TODO: pokračovat tady, přidat způsob jak dát userovi možnost
// stahnout prilohy
msg.m_enclosures.append(Enclosure(QL1S("##") + body["attachmentId"].toString(),
msg.m_enclosures.append(Enclosure(filename + QL1S(GMAIL_ATTACHMENT_SEP) + body["attachmentId"].toString(),
filename + QString(" (%1 KB)").arg(QString::number(body["size"].toInt() / 1000.0))));
}
}

View File

@ -15,6 +15,7 @@
class RootItem;
class GmailServiceRoot;
class OAuth2Service;
class Downloader;
class GmailNetworkFactory : public QObject {
Q_OBJECT
@ -38,6 +39,8 @@ class GmailNetworkFactory : public QObject {
// Returned items do not have primary IDs assigned.
//RootItem* feedsCategories();
Downloader* downloadAttachment(const QString& attachment_id);
QList<Message> messages(const QString& stream_id, Feed::Status& error);
void markMessagesRead(RootItem::ReadStatus status, const QStringList& custom_ids, bool async = true);
void markMessagesStarred(RootItem::Importance importance, const QStringList& custom_ids, bool async = true);

View File

@ -257,7 +257,7 @@ void FormStandardImportExport::exportFeeds() {
if (result_export) {
try {
IOFactory::writeTextFile(m_ui->m_lblSelectFile->label()->text(), result_data);
IOFactory::writeFile(m_ui->m_lblSelectFile->label()->text(), result_data);
m_ui->m_lblResult->setStatus(WidgetWithStatus::Ok, tr("Feeds were exported successfully."), tr("Feeds were exported successfully."));
}
catch (IOException& ex) {

View File

@ -63,7 +63,7 @@ void StandardServiceRoot::start(bool freshly_activated) {
QString output_msg;
try {
model.importAsOPML20(IOFactory::readTextFile(file_to_load), false);
model.importAsOPML20(IOFactory::readFile(file_to_load), false);
model.checkAllItems();
if (mergeImportExportModel(&model, this, output_msg)) {

View File

@ -225,7 +225,7 @@ TtRssGetHeadlinesResponse TtRssNetworkFactory::getHeadlines(int feed_id, int lim
result = TtRssGetHeadlinesResponse(QString::fromUtf8(result_raw));
}
IOFactory::writeTextFile("aaa", result_raw);
IOFactory::writeFile("aaa", result_raw);
if (network_reply.first != QNetworkReply::NoError) {
qWarning("TT-RSS: getHeadlines failed with error %d.", network_reply.first);