linphone-desktop/linphone-app/src/components/history/HistoryModel.cpp
2021-08-25 11:14:51 +02:00

257 lines
7.8 KiB
C++

/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <QDateTime>
#include <QDesktopServices>
#include <QElapsedTimer>
#include <QFileInfo>
#include <QMimeDatabase>
#include <QTimer>
#include <QUuid>
#include <QMessageBox>
#include <QUrlQuery>
#include <QImageReader>
#include "app/App.hpp"
#include "app/paths/Paths.hpp"
#include "app/providers/ThumbnailProvider.hpp"
#include "components/core/CoreHandlers.hpp"
#include "components/core/CoreManager.hpp"
#include "components/notifier/Notifier.hpp"
#include "components/settings/SettingsModel.hpp"
#include "utils/QExifImageHeader.hpp"
#include "utils/Utils.hpp"
#include "HistoryModel.hpp"
// =============================================================================
using namespace std;
static inline void fillCallStartEntry (QVariantMap &dest, const shared_ptr<linphone::CallLog> &callLog) {
dest["type"] = HistoryModel::CallEntry;
dest["timestamp"] = QDateTime::fromMSecsSinceEpoch(callLog->getStartDate() * 1000);
dest["isOutgoing"] = callLog->getDir() == linphone::Call::Dir::Outgoing;
dest["status"] = static_cast<HistoryModel::CallStatus>(callLog->getStatus());
dest["isStart"] = true;
dest["sipAddress"] = Utils::coreStringToAppString(callLog->getRemoteAddress()->asString());
}
static inline void fillCallEndEntry (QVariantMap &dest, const shared_ptr<linphone::CallLog> &callLog) {
dest["type"] = HistoryModel::CallEntry;
dest["timestamp"] = QDateTime::fromMSecsSinceEpoch((callLog->getStartDate() + callLog->getDuration()) * 1000);
dest["isOutgoing"] = callLog->getDir() == linphone::Call::Dir::Outgoing;
dest["status"] = static_cast<HistoryModel::CallStatus>(callLog->getStatus());
dest["isStart"] = false;
dest["sipAddress"] = Utils::coreStringToAppString(callLog->getRemoteAddress()->asString());
}
// -----------------------------------------------------------------------------
HistoryModel::HistoryModel (QObject *parent) :QAbstractListModel(parent){
CoreManager *coreManager = CoreManager::getInstance();
mCoreHandlers = coreManager->getHandlers();
setSipAddresses();
CoreHandlers *coreHandlers = mCoreHandlers.get();
QObject::connect(coreHandlers, &CoreHandlers::callStateChanged, this, &HistoryModel::handleCallStateChanged);
}
HistoryModel::~HistoryModel () {
}
QHash<int, QByteArray> HistoryModel::roleNames () const {
QHash<int, QByteArray> roles;
roles[Roles::HistoryEntry] = "$historyEntry";
roles[Roles::SectionDate] = "$sectionDate";
return roles;
}
int HistoryModel::rowCount (const QModelIndex &) const {
return mEntries.count();
}
QVariant HistoryModel::data (const QModelIndex &index, int role) const {
int row = index.row();
if (!index.isValid() || row < 0 || row >= mEntries.count())
return QVariant();
switch (role) {
case Roles::HistoryEntry: {
auto &data = mEntries[row].first;
return QVariant::fromValue(data);
}
case Roles::SectionDate:
return QVariant::fromValue(mEntries[row].first["timestamp"].toDate());
}
return QVariant();
}
bool HistoryModel::removeRow (int row, const QModelIndex &) {
return removeRows(row, 1);
}
bool HistoryModel::removeRows (int row, int count, const QModelIndex &parent) {
int limit = row + count - 1;
if (row < 0 || count < 0 || limit >= mEntries.count())
return false;
beginRemoveRows(parent, row, limit);
for (int i = 0; i < count; ++i) {
removeEntry(mEntries[row]);
mEntries.removeAt(row);
}
endRemoveRows();
if (mEntries.count() == 0)
emit allEntriesRemoved();
else if (limit == mEntries.count())
emit lastEntryRemoved();
emit focused();// Removing rows is like having focus. Don't wait asynchronous events.
return true;
}
void HistoryModel::setSipAddresses () {
shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
mEntries.clear();
QElapsedTimer timer;
timer.start();
// Get calls.
for (auto &callLog : core->getCallLogs())
insertCall(callLog);
qInfo() << QStringLiteral("HistoryModel loaded in %3 milliseconds.").arg(timer.elapsed());
}
// -----------------------------------------------------------------------------
void HistoryModel::removeEntry (int id) {
qInfo() << QStringLiteral("Removing call entry: %1.").arg(id);
if (!removeRow(id))
qWarning() << QStringLiteral("Unable to remove call entry: %1").arg(id);
}
void HistoryModel::removeAllEntries () {
qInfo() << QStringLiteral("Removing all call entries.");
beginResetModel();
for (auto &entry : mEntries)
removeEntry(entry);
mEntries.clear();
endResetModel();
emit allEntriesRemoved();
emit focused();// Removing all entries is like having focus. Don't wait asynchronous events.
}
// -----------------------------------------------------------------------------
void HistoryModel::removeEntry (HistoryEntryData &entry) {
int type = entry.first["type"].toInt();
switch (type) {
case HistoryModel::CallEntry: {
if (entry.first["status"].toInt() == CallStatusSuccess) {
// WARNING: Unable to remove symmetric call here. (start/end)
// We are between `beginRemoveRows` and `endRemoveRows`.
// A solution is to schedule a `removeEntry` call in the Qt main loop.
shared_ptr<void> linphonePtr = entry.second;
QTimer::singleShot(0, this, [this, linphonePtr]() {
auto it = find_if(mEntries.begin(), mEntries.end(), [linphonePtr](const HistoryEntryData &entry) {
return entry.second == linphonePtr;
});
if (it != mEntries.end())
removeEntry(int(distance(mEntries.begin(), it)));
});
}
CoreManager::getInstance()->getCore()->removeCallLog(static_pointer_cast<linphone::CallLog>(entry.second));
break;
}
default:
qWarning() << QStringLiteral("Unknown history entry type: %1.").arg(type);
}
}
void HistoryModel::insertCall (const shared_ptr<linphone::CallLog> &callLog) {
linphone::Call::Status status = callLog->getStatus();
auto insertEntry = [this](
const HistoryEntryData &entry,
const QList<HistoryEntryData>::iterator *start = nullptr
) {
auto it = lower_bound(start ? *start : mEntries.begin(), mEntries.end(), entry, [](const HistoryEntryData &a, const HistoryEntryData &b) {
return a.first["timestamp"] < b.first["timestamp"];
});
int row = int(distance(mEntries.begin(), it));
beginInsertRows(QModelIndex(), row, row);
it = mEntries.insert(it, entry);
endInsertRows();
emit layoutChanged();
return it;
};
// Add start call.
QVariantMap start;
fillCallStartEntry(start, callLog);
auto it = insertEntry(qMakePair(start, static_pointer_cast<void>(callLog)));
if (status == linphone::Call::Status::Success) {
QVariantMap end;
fillCallEndEntry(end, callLog);
insertEntry(qMakePair(end, static_pointer_cast<void>(callLog)), &it);
}
}
// -----------------------------------------------------------------------------
void HistoryModel::resetMessageCount () {
emit callCountReset();
}
// -----------------------------------------------------------------------------
void HistoryModel::handleCallStateChanged (const shared_ptr<linphone::Call> &call, linphone::Call::State state) {
if (state == linphone::Call::State::End || state == linphone::Call::State::Error)
insertCall(call->getCallLog());
}