| /************************************************************************************ |
| * Copyright (C) 2014-2015 by Savoir-Faire Linux * |
| * Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> * |
| * * |
| * This library is free software; you can redistribute it and/or * |
| * modify it under the terms of the GNU Lesser General Public * |
| * License as published by the Free Software Foundation; either * |
| * version 2.1 of the License, or (at your option) any later version. * |
| * * |
| * This library 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 * |
| * Lesser General Public License for more details. * |
| * * |
| * You should have received a copy of the GNU Lesser General Public * |
| * License along with this library; if not, write to the Free Software * |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * |
| ***********************************************************************************/ |
| #import "minimalhistorybackend.h" |
| |
| #import <Cocoa/Cocoa.h> |
| |
| //Qt |
| #import <QtCore/QFile> |
| #import <QtCore/QDir> |
| #import <QtCore/qlist.h> |
| #import <QtCore/QHash> |
| #import <QtWidgets/QApplication> |
| #import <QtCore/QStandardPaths> |
| #import <collectioneditor.h> |
| |
| //Ring |
| #import <call.h> |
| #import <account.h> |
| #import <person.h> |
| #import <contactmethod.h> |
| #import <categorizedhistorymodel.h> |
| |
| class MinimalHistoryEditor : public CollectionEditor<Call> |
| { |
| public: |
| MinimalHistoryEditor(CollectionMediator<Call>* m, MinimalHistoryBackend* parent); |
| virtual bool save ( const Call* item ) override; |
| virtual bool remove ( const Call* item ) override; |
| virtual bool batchRemove(const QList<Call*> contacts) override; |
| virtual bool edit ( Call* item ) override; |
| virtual bool addNew ( const Call* item ) override; |
| virtual bool addExisting( const Call* item ) override; |
| |
| private: |
| virtual QVector<Call*> items() const override; |
| |
| //Helpers |
| void saveCall(QTextStream& stream, const Call* call); |
| bool regenFile(const Call* toIgnore); |
| |
| //Attributes |
| QVector<Call*> m_lItems; |
| MinimalHistoryBackend* m_pCollection; |
| }; |
| |
| MinimalHistoryEditor::MinimalHistoryEditor(CollectionMediator<Call>* m, MinimalHistoryBackend* parent) : |
| CollectionEditor<Call>(m),m_pCollection(parent) |
| { |
| |
| } |
| |
| MinimalHistoryBackend::MinimalHistoryBackend(CollectionMediator<Call>* mediator) : |
| CollectionInterface(new MinimalHistoryEditor(mediator,this)),m_pMediator(mediator) |
| { |
| |
| } |
| |
| MinimalHistoryBackend::~MinimalHistoryBackend() |
| { |
| |
| } |
| |
| void MinimalHistoryEditor::saveCall(QTextStream& stream, const Call* call) |
| { |
| const QString direction = (call->direction()==Call::Direction::INCOMING)? |
| Call::HistoryStateName::INCOMING : Call::HistoryStateName::OUTGOING; |
| |
| const Account* a = call->account(); |
| stream << QString("%1=%2\n").arg(Call::HistoryMapFields::CALLID ).arg(call->historyId() ); |
| stream << QString("%1=%2\n").arg(Call::HistoryMapFields::TIMESTAMP_START ).arg(call->startTimeStamp() ); |
| stream << QString("%1=%2\n").arg(Call::HistoryMapFields::TIMESTAMP_STOP ).arg(call->stopTimeStamp() ); |
| stream << QString("%1=%2\n").arg(Call::HistoryMapFields::ACCOUNT_ID ).arg(a?QString(a->id()):"" ); |
| stream << QString("%1=%2\n").arg(Call::HistoryMapFields::DISPLAY_NAME ).arg(call->peerName() ); |
| stream << QString("%1=%2\n").arg(Call::HistoryMapFields::PEER_NUMBER ).arg(call->peerContactMethod()->uri() ); |
| stream << QString("%1=%2\n").arg(Call::HistoryMapFields::DIRECTION ).arg(direction ); |
| stream << QString("%1=%2\n").arg(Call::HistoryMapFields::MISSED ).arg(call->isMissed() ); |
| stream << QString("%1=%2\n").arg(Call::HistoryMapFields::RECORDING_PATH ).arg(call->recordingPath() ); |
| stream << QString("%1=%2\n").arg(Call::HistoryMapFields::CONTACT_USED ).arg(false );//TODO |
| if (call->peerContactMethod()->contact()) { |
| stream << QString("%1=%2\n").arg(Call::HistoryMapFields::CONTACT_UID ).arg( |
| QString(call->peerContactMethod()->contact()->uid()) |
| ); |
| } |
| stream << "\n"; |
| stream.flush(); |
| } |
| |
| bool MinimalHistoryEditor::regenFile(const Call* toIgnore) |
| { |
| QDir dir(QString('/')); |
| dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + QString()); |
| |
| QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') +"history.ini"); |
| if ( file.open(QIODevice::WriteOnly | QIODevice::Text) ) { |
| QTextStream stream(&file); |
| for (const Call* c : CategorizedHistoryModel::instance()->getHistoryCalls()) { |
| if (c != toIgnore) |
| saveCall(stream, c); |
| } |
| file.close(); |
| return true; |
| } |
| return false; |
| } |
| |
| bool MinimalHistoryEditor::save(const Call* call) |
| { |
| if (call->collection()->editor<Call>() != this) |
| return addNew(call); |
| |
| return regenFile(nullptr); |
| } |
| |
| bool MinimalHistoryEditor::remove(const Call* item) |
| { |
| mediator()->removeItem(item); |
| return regenFile(item); |
| } |
| |
| bool MinimalHistoryEditor::batchRemove(const QList<Call*> calls) { |
| QFile::remove(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + "history.ini"); |
| return YES; |
| } |
| |
| bool MinimalHistoryEditor::edit( Call* item) |
| { |
| Q_UNUSED(item) |
| return false; |
| } |
| |
| bool MinimalHistoryEditor::addNew(const Call* call) |
| { |
| QDir dir(QString('/')); |
| dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + QString()); |
| |
| if ((call->collection() && call->collection()->editor<Call>() == this) || call->historyId().isEmpty()) return false; |
| //TODO support \r and \n\r end of line |
| QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/')+"history.ini"); |
| |
| if ( file.open(QIODevice::Append | QIODevice::Text) ) { |
| QTextStream streamFileOut(&file); |
| saveCall(streamFileOut, call); |
| file.close(); |
| |
| const_cast<Call*>(call)->setCollection(m_pCollection); |
| addExisting(call); |
| return true; |
| } |
| else |
| qWarning() << "Unable to save history"; |
| return false; |
| } |
| |
| bool MinimalHistoryEditor::addExisting(const Call* item) |
| { |
| m_lItems << const_cast<Call*>(item); |
| mediator()->addItem(item); |
| return true; |
| } |
| |
| QVector<Call*> MinimalHistoryEditor::items() const |
| { |
| return m_lItems; |
| } |
| |
| QString MinimalHistoryBackend::name () const |
| { |
| return QObject::tr("Minimal history backend"); |
| } |
| |
| QString MinimalHistoryBackend::category () const |
| { |
| return QObject::tr("History"); |
| } |
| |
| QVariant MinimalHistoryBackend::icon() const |
| { |
| return QVariant(); |
| } |
| |
| bool MinimalHistoryBackend::isEnabled() const |
| { |
| return true; |
| } |
| |
| bool MinimalHistoryBackend::load() |
| { |
| // get history limit from our preferences set |
| NSInteger historyLimit = [[NSUserDefaults standardUserDefaults] integerForKey:@"history_limit"]; |
| |
| QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') +"history.ini"); |
| if ( file.open(QIODevice::ReadOnly | QIODevice::Text) ) { |
| QMap<QString,QString> hc; |
| while (!file.atEnd()) { |
| QByteArray line = file.readLine().trimmed(); |
| |
| //The item is complete |
| if ((line.isEmpty() || !line.size()) && hc.size()) { |
| Call* pastCall = Call::buildHistoryCall(hc); |
| if (pastCall->peerName().isEmpty()) { |
| pastCall->setPeerName(QObject::tr("Unknown")); |
| } |
| |
| if(daysSince(pastCall->startTimeStamp()) < historyLimit) { |
| pastCall->setRecordingPath(hc[ Call::HistoryMapFields::RECORDING_PATH ]); |
| pastCall->setCollection(this); |
| |
| editor<Call>()->addExisting(pastCall); |
| } |
| hc.clear(); |
| } |
| // Add to the current set |
| else { |
| const int idx = line.indexOf("="); |
| if (idx >= 0) |
| hc[line.left(idx)] = line.right(line.size()-idx-1); |
| } |
| } |
| return true; |
| } |
| else |
| qWarning() << "History doesn't exist or is not readable"; |
| return false; |
| } |
| |
| int MinimalHistoryBackend::daysSince(time_t timestamp) |
| { |
| NSDate *fromDate; |
| NSDate *toDate; |
| |
| NSDate* fromDateTime = [NSDate dateWithTimeIntervalSince1970:timestamp]; |
| |
| NSCalendar *calendar = [NSCalendar currentCalendar]; |
| |
| [calendar rangeOfUnit:NSCalendarUnitDay startDate:&fromDate |
| interval:NULL forDate:fromDateTime]; |
| [calendar rangeOfUnit:NSCalendarUnitDay startDate:&toDate |
| interval:NULL forDate:[NSDate date]]; |
| |
| NSDateComponents *difference = [calendar components:NSCalendarUnitDay |
| fromDate:fromDate toDate:toDate options:0]; |
| |
| return [difference day]; |
| } |
| |
| bool MinimalHistoryBackend::reload() |
| { |
| return false; |
| } |
| |
| CollectionInterface::SupportedFeatures MinimalHistoryBackend::supportedFeatures() const |
| { |
| return (CollectionInterface::SupportedFeatures) ( |
| CollectionInterface::SupportedFeatures::NONE | |
| CollectionInterface::SupportedFeatures::LOAD | |
| CollectionInterface::SupportedFeatures::CLEAR | |
| CollectionInterface::SupportedFeatures::REMOVE| |
| CollectionInterface::SupportedFeatures::ADD ); |
| } |
| |
| bool MinimalHistoryBackend::clear() |
| { |
| editor<Call>()->batchRemove(items<Call>().toList()); |
| QList<Call*> calls = items<Call>().toList(); |
| for(int i = 0 ; i < calls.count() ; ++i) { |
| CategorizedHistoryModel::instance()->deleteItem(calls[i]); |
| } |
| return true; |
| } |
| |
| QByteArray MinimalHistoryBackend::id() const |
| { |
| return "mhb"; |
| } |