From kopete-devel Mon Dec 22 17:07:06 2008 From: =?ISO-8859-1?Q?Timo_Schl=FC=DFler?= Date: Mon, 22 Dec 2008 17:07:06 +0000 To: kopete-devel Subject: Re: [kopete-devel] History Import extension Message-Id: <494FC93A.3080902 () schluessler ! org> X-MARC-Message: https://marc.info/?l=kopete-devel&m=122996567625434 MIME-Version: 1 Content-Type: multipart/mixed; boundary="--------------070504040800050000020409" This is a multi-part message in MIME format. --------------070504040800050000020409 Content-Type: multipart/alternative; boundary="------------070805060703060304090805" --------------070805060703060304090805 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 8bit attached is a version where i changed the things you mentioned in irc ... regards Olivier Goffart wrote: > Le mercredi 3 décembre 2008, Timo Schlüßler a écrit : > >> Hello, >> >> attached is a patch and the two files which add the ability to import >> logs from pidgin to kopete. The source-code files belong to >> kdenetwork/kopete/plugins/history. >> I changed HistoryLogger slightly so that appendMessage() uses the >> timestamp from the message that is to be imported but not >> QDate::currentDate(). >> >> I don't know what happens if you want to import multiuser chatlogs >> because i haven't got any ... i would be glad if you can check that. >> For me it works just fine, but the logs from pidgin are very unsteady. >> anyway i hope it works for all pidgin logs. >> > > Hi, > > sorry for answering so late. > > You shouldn't change the reference date in the getDocument() function. > > You should understand that by definition, the 'int month' passed to the > getDocument function is the difference between the _current_ month and the > month of the document you ask. > > So you should not change the reference. > > What you should do instead is, in appendMessage, call the overloaded > getDocument, that take a QDate instead. > BUT then move the cache from the function that take an integer to this one. > > > ------------------------------------------------------------------------ > > _______________________________________________ > kopete-devel mailing list > kopete-devel@kde.org > https://mail.kde.org/mailman/listinfo/kopete-devel > --------------070805060703060304090805 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit attached is a version where i changed the things you mentioned in irc ...

regards

Olivier Goffart wrote:
Le mercredi 3 décembre 2008, Timo Schlüßler a écrit :
  
Hello,

attached is a patch and the two files which add the ability to import
logs from pidgin to kopete. The source-code files belong to
kdenetwork/kopete/plugins/history.
I changed HistoryLogger slightly so that appendMessage() uses the
timestamp from the message that is to be imported  but not
QDate::currentDate().

I don't know what happens if you want to import multiuser chatlogs
because i haven't got any ... i would be glad if you can check that.
For me it works just fine, but the logs from pidgin are very unsteady.
anyway i hope it works for all pidgin logs.
    

Hi,

sorry for answering so late.

You shouldn't change the reference date in the getDocument() function.

You should understand that by definition, the 'int month' passed to the 
getDocument function is the difference between the _current_ month and the 
month of the document you ask.

So you should not change the reference.

What you should do instead is, in appendMessage, call the overloaded
getDocument, that take a QDate instead.
BUT then move the cache from the function that take an integer to this one.

  

_______________________________________________ kopete-devel mailing list kopete-devel@kde.org https://mail.kde.org/mailman/listinfo/kopete-devel
--------------070805060703060304090805-- --------------070504040800050000020409 Content-Type: text/plain; name="historyimport.cpp" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="historyimport.cpp" /* historyimport.cpp Copyright (c) 2008 by Timo Schluessler Kopete (c) 2008 by the Kopete developers ************************************************************************* * * * 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 2 of the License, or * * (at your option) any later version. * * * ************************************************************************* */ #include "historyimport.h" #include "historylogger.h" #include #include #include #include #include #include HistoryImport::HistoryImport(QWidget *parent) : KDialog(parent) { // set dialog settings setButtons(KDialog::Ok | KDialog::Details | KDialog::Cancel); setWindowTitle(KDialog::makeStandardCaption(i18n("Import History"))); setButtonText(KDialog::Ok, i18n("Import listed logs")); // create widgets QWidget *w = new QWidget(this); QGridLayout *l = new QGridLayout(w); display = new QTextEdit(w); display->setReadOnly(true); treeView = new QTreeView(w); QPushButton *fromPidgin = new QPushButton(i18n("Get history from &Pidgin"), w); l->addWidget(treeView, 0, 0, 1, 3); l->addWidget(display, 0, 4, 1, 10); l->addWidget(fromPidgin, 1, 0); setMainWidget(w); // create details widget QWidget *details = new QWidget(w); QVBoxLayout *dL = new QVBoxLayout(w); QTextEdit *detailsEdit = new QTextEdit(details); detailsEdit->setReadOnly(true); selectByHand = new QCheckBox(i18n("Select log directory by hand"), details); dL->addWidget(selectByHand); dL->addWidget(detailsEdit); details->setLayout(dL); setDetailsWidget(details); detailsCursor = QTextCursor(detailsEdit->document()); // create model for treeView QStandardItemModel *model = new QStandardItemModel(treeView); treeView->setModel(model); model->setHorizontalHeaderLabels(QStringList(i18n("Parsed History"))); // connect everything connect(treeView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(itemClicked(const QModelIndex &))); connect(fromPidgin, SIGNAL(clicked()), this, SLOT(importPidgin())); connect(this, SIGNAL(okClicked()), this, SLOT(save())); // define variables amount = 0; cancel = false; pidginImported = false; timeFormats << "(MM/dd/yyyy hh:mm:ss)" << "(MM/dd/yyyy hh:mm:ss AP)" << "(MM/dd/yy hh:mm:ss)" << "(MM/dd/yy hh:mm:ss AP)" << "(dd.MM.yyyy hh:mm:ss)" << "(dd.MM.yyyy hh:mm:ss AP)" << "(dd.MM.yy hh:mm:ss)" << "(dd.MM.yyyy hh:mm:ss AP)" << "(dd/MM/yyyy hh:mm:ss)" << "(dd/MM/yyyy hh:mm:ss AP)" << "(dd/MM/yy hh:mm:ss)" << "(dd/MM/yy hh:mm:ss AP)"; show(); } HistoryImport::~HistoryImport(void) { } void HistoryImport::save(void) { QProgressDialog progress(i18n("Saving logs to disk ..."), i18n("Abort Saving"), 0, amount, this); progress.setWindowTitle(i18n("Saving")); Log log; foreach (log, logs) { HistoryLogger logger(log.other, this); Message message; foreach (message, log.messages) { Kopete::Message kMessage; if (message.incoming) { kMessage = Kopete::Message(log.other, log.me); kMessage.setDirection(Kopete::Message::Inbound); } else { kMessage = Kopete::Message(log.me, log.other); kMessage.setDirection(Kopete::Message::Outbound); } kMessage.setPlainBody(message.text); kMessage.setTimestamp(message.timestamp); logger.appendMessage(kMessage, log.other); progress.setValue(progress.value()+1); qApp->processEvents(); if (progress.wasCanceled()) { cancel = true; break; } } if (cancel) break; } } void HistoryImport::displayLog(struct Log *log) { Message message; QList items; QStringList strings; items << static_cast(treeView->model())->invisibleRootItem(); items << NULL << NULL << NULL; strings << "" << "" << ""; foreach(message, log->messages) { amount++; // for QProgressDialog in save() strings[0] = log->other->protocol()->pluginId() + " (" + log->other->account()->accountId() + ')'; strings[1] = log->other->nickName(); strings[2] = message.timestamp.toString("yyyy-MM-dd"); bool update = false; int i; for (i=1; i<4; i++) { if (update || !items.at(i) || items.at(i)->data(Qt::DisplayRole) != strings.at(i-1)) { items[i] = findItem(strings.at(i-1), items.at(i-1)); update = true; } //else //kDebug(14310) << "using cached item"; } if (!items.at(3)->data(Qt::UserRole).isValid()) items[3]->setData((int)logs.indexOf(*log), Qt::UserRole); } } QStandardItem * HistoryImport::findItem(const QString &text, QStandardItem *parent) { int i; bool found = false; QStandardItem *child = 0L; for (i=0; i < parent->rowCount(); i++) { child = parent->child(i, 0); if (child->data(Qt::DisplayRole) == text) { found = true; break; } } if (!found) { child = new QStandardItem(text); parent->appendRow(child); } return child; } void HistoryImport::itemClicked(const QModelIndex &index) { QVariant id = index.data(Qt::UserRole); if (id.canConvert()) { Log log = logs.at(id.toInt()); display->document()->clear(); QTextCursor cursor(display->document()); Message message; QDate date = QDate::fromString(index.data(Qt::DisplayRole).toString(), "yyyy-MM-dd"); foreach (message, log.messages) { if (date != message.timestamp.date()) continue; cursor.insertText(message.timestamp.toString("hh:mm:ss ")); if (message.incoming) cursor.insertText(log.other->nickName().append(": ")); else cursor.insertText(log.me->nickName().append(": ")); cursor.insertText(message.text); cursor.insertBlock(); } } } int HistoryImport::countLogs(QDir dir, int depth) { int res = 0; QStack pos; QStringList files; pos.push(0); depth++; forever { files = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); if (pos.size() == depth) { res += dir.entryList(QDir::Files).size(); } if (files.isEmpty() || files.size() <= pos.top() || pos.size() == depth) { dir.cdUp(); pos.pop(); if (pos.isEmpty()) break; pos.top()++; } else if (pos.size() != depth) { dir.cd(files.at(pos.top())); pos.push(0); } } return res; } void HistoryImport::importPidgin() { if (pidginImported) { if (QMessageBox::question(this, i18n("Are you sure?"), i18n("You already imported logs from pidgin. If you do it twice, each message is imported twice!\nAre you sure you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) return; } pidginImported = true; QDir logDir = QDir::homePath(); if (selectByHand->isChecked() || !logDir.cd(".purple/logs")) logDir = QFileDialog::getExistingDirectory(mainWidget(), i18n("Select log directory"), QDir::homePath()); int total = countLogs(logDir, 3); QProgressDialog progress(i18n("Parsing history from pidgin ..."), i18n("Abort parsing"), 0, total, mainWidget()); progress.setWindowTitle(i18n("Parsing history")); progress.show(); cancel = false; QString protocolFolder; foreach (protocolFolder, logDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { logDir.cd(protocolFolder); QString accountFolder; foreach (accountFolder, logDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { logDir.cd(accountFolder); // TODO use findContact? Kopete::ContactList * cList = Kopete::ContactList::self(); QList meList = cList->myself()->contacts(); Kopete::Contact *me; bool found = false; foreach (me, meList) { if (me->protocol()->pluginId().contains(protocolFolder, Qt::CaseInsensitive) && me->account()->accountId().contains(accountFolder, Qt::CaseInsensitive)) { found = true; break; } } if (!found) { detailsCursor.insertText(i18n("WARNING: Can't find matching account for %1 (%2)!\n").arg(accountFolder).arg(protocolFolder)); logDir.cdUp(); continue; } QString chatPartner; foreach (chatPartner, logDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { logDir.cd(chatPartner); Kopete::Contact *other = cList->findContact(me->protocol()->pluginId(), me->account()->accountId(), chatPartner); struct Log log; if (!other) { detailsCursor.insertText(i18n("WARNING: Can't find %1 (%2) in your contact list. Found logs will not be imported!\n").arg(chatPartner).arg(protocolFolder)); logDir.cdUp(); continue; } else { log.me = me; log.other = other; } QString logFile; QStringList filter; filter << "*.html" << "*.txt"; foreach(logFile, logDir.entryList(filter, QDir::Files)) { QFile file(logDir.filePath(logFile)); if (!file.open(QIODevice::ReadOnly)) { detailsCursor.insertText(i18n("WARNING: Can't open file %1. Skipping.\n").arg(logDir.filePath(logFile))); continue; } if (logFile.endsWith(".html")) parsePidginXml(file, &log, QDate::fromString(logFile.left(10), "yyyy-MM-dd")); else if (logFile.endsWith(".txt")) parsePidginTxt(file, &log, QDate::fromString(logFile.left(10), "yyyy-MM-dd")); file.close(); progress.setValue(progress.value()+1); qApp->processEvents(); if (cancel || progress.wasCanceled()) { cancel = true; break; } } logs.append(log); displayLog(&log); if (cancel) break; logDir.cdUp(); } if (cancel) break; logDir.cdUp(); } if (cancel) break; logDir.cdUp(); } } bool HistoryImport::isNickIncoming(const QString &nick, struct Log *log) { bool incoming; if (nick == log->me->nickName()) incoming = false; else if (nick == log->other->nickName()) incoming = true; else if (knownNicks.contains(nick)) incoming = knownNicks.value(nick); else { int r = QMessageBox::question(NULL, i18n("Can't map nickname to account"), i18n("Did you use \"%1\" as nickname in history?").arg(nick), QMessageBox::Yes | QMessageBox::No | QMessageBox::Abort); if (r == QMessageBox::Yes) { knownNicks.insert(nick, true); incoming = true; } else if (r == QMessageBox::No) { knownNicks.insert(nick, false); incoming = false; } else { cancel = true; return false; } } return incoming; } QDateTime HistoryImport::extractTime(const QString &string, QDate ref) { QDateTime dateTime; QTime time; // try some formats used by pidgin if ((time = QTime::fromString(string, "(hh:mm:ss)")) .isValid()); else if ((time = QTime::fromString(string, "(hh:mm:ss AP)")) .isValid()); else { QString format; foreach (format, timeFormats) { if ((dateTime = QDateTime::fromString(string, format)).isValid()) break; } } // check if the century in dateTime is equal to that of our date reference if (dateTime.isValid()) { int diff = ref.year() - dateTime.date().year(); dateTime = dateTime.addYears(diff - (diff % 100)); } // if string contains only a time we use ref as date if (time.isValid()) dateTime = QDateTime(ref, time); // inform the user about the date problems if (!dateTime.isValid()) detailsCursor.insertText(i18n("WARNING: can't parse date \"%1\". You may want to edit the file containing this date manually. (example for recognized date strings: \"05/31/2008 15:24:30\")\n").arg(string, dateTime.toString("yyyy-MM-dd hh:mm:ss"))); return dateTime; } void HistoryImport::parsePidginTxt(QFile &file, struct Log *log, QDate date) { QByteArray line; QTime time; QDateTime dateTime; QString messageText, nick; bool incoming = false; // =false to make the compiler not complain while (!file.atEnd()) { line = file.readLine(); if (line[0] == '(') { if (!messageText.isEmpty()) { // messageText contains an unwished newline at the end if (messageText.endsWith("\n")) messageText.remove(-1, 1); struct Message message; message.incoming = incoming; message.text = messageText; message.timestamp = dateTime; log->messages.append(message); messageText.clear(); } int endTime = line.indexOf(')')+1; dateTime = extractTime(line.left(endTime), date); int nickEnd = QRegExp("\\s").indexIn(line, endTime + 1); // TODO what if a nickname consists of two words? is this possible? // the following while can't be used because in status logs there is no : after the nickname :( //while (line[nickEnd-1] != ':') // nickEnd = QRegExp("\\").indexIn(line, nickEnd); if (line[nickEnd -1] != ':') // this line is a status message continue; nick = line.mid(endTime+1, nickEnd - endTime - 2); // -2 to delete the colon incoming = isNickIncoming(nick, log); if (cancel) return; messageText = line.mid(nickEnd + 1); } else if (line[0] == ' ') { // an already started message is continued in this line int start = QRegExp("\\S").indexIn(line); messageText.append('\n' + line.mid(start)); } } if (!messageText.isEmpty()) { struct Message message; message.incoming = incoming; message.text = messageText; message.timestamp = dateTime; log->messages.append(message); messageText.clear(); } } void HistoryImport::parsePidginXml(QFile &file, struct Log * log, QDate date) { bool incoming = false, inMessage = false, textComes = false; QString messageText, status; QTime time; QDateTime dateTime; // unfortunately pidgin doesn't write <... /> for the tag QByteArray data = file.readAll(); if (data.contains("", data.indexOf("messages.append(message); messageText.clear(); textComes = false; inMessage = false; } } if (reader.hasError()) { // we ignore error 4: premature end of document if (reader.error() != 4) { int i, pos = 0; for (i=1;i Kopete (c) 2008 by the Kopete developers ************************************************************************* * * * 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 2 of the License, or * * (at your option) any later version. * * * ************************************************************************* */ #ifndef HISTORYIMPORT_H #define HISTORYIMPORT_H #include #include #include "kopeteglobal.h" #include "kopetecontact.h" #include "kopetemessage.h" /** * @author Timo Schluessler */ class HistoryImport : public KDialog { Q_OBJECT public: HistoryImport(QWidget *parent); ~HistoryImport(); private: /** * Used for internal storage of a message. */ struct Message { bool incoming; QString text; QDateTime timestamp; }; /** * Holds the messages sent and received between two specified contacts. */ struct Log { Kopete::Contact *me; Kopete::Contact *other; QList messages; /** * Comparison between the Message lists is not neccessary because we need this operator * only in displayLog() to get the index of this log in logs. */ bool operator==(const struct Log & cmp) { if (cmp.me == me && cmp.other == other/* && other.messages == messages*/) return true; else return false; } }; /** * Parses @param file and appends the found messages to @param log. */ void parsePidginXml(QFile &file, struct Log *log, QDate date); /** * Parses @param file and appends the found messages to @param log. */ void parsePidginTxt(QFile &file, struct Log *log, QDate date); /** * Inserts @param log into treeView and prepares to display it when clicking on it. */ void displayLog(struct Log *log); /** * Looks up if an item with @param text exists already as child of @param parent. * If not, it creates one. */ QStandardItem * findItem(const QString &text, QStandardItem *parent); /** * @return the number of files found in @param depth th subdir of @param dir. */ int countLogs(QDir dir, int depth); /** * Checks if nickname @param nick can be mapped to either @param log ->me or @param log ->other. * If not it asks the user if it is his nickname or someone elses. */ bool isNickIncoming(const QString &nick, struct Log *log); /** * Trys to extract the time from @param string using formats specified in timeFormats. * @param ref is used when @param string doesn't contain a date or to adjust a found date. * @param ref is taken from the filename of the log. */ QDateTime extractTime(const QString &string, QDate ref); QStringList timeFormats; QTreeView *treeView; QTextEdit *display; /** * Used for adding details to the details widget. */ QTextCursor detailsCursor; /** * Enables/disables auto search for log dirs. */ QCheckBox *selectByHand; /** * Contains all already parsed logs. */ QList logs; /** * Used for mapping nickname to account. See isNickIncoming(). */ QHash knownNicks; /** * To warn the user when importing logs twice */ bool pidginImported; int amount; bool cancel; private slots: /** * Starts parsing history from pidgin. * Default path is ~/.purple/logs. */ void importPidgin(void); void save(void); void itemClicked(const QModelIndex & index); }; #endif --------------070504040800050000020409 Content-Type: text/plain; name="kopete-history-import-1.0.patch" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="kopete-history-import-1.0.patch" Index: historylogger.cpp =================================================================== --- historylogger.cpp (revision 900154) +++ historylogger.cpp (working copy) @@ -255,7 +255,9 @@ return; } - QDomDocument doc=getDocument(c,0); + QDate date = msg.timestamp().date(); + + QDomDocument doc=getDocument(c, QDate::currentDate().month() - date.month() - (QDate::currentDate().year() - date.year()) * 12); QDomElement docElem = doc.documentElement(); if(docElem.isNull()) @@ -266,8 +268,8 @@ QDomElement headElem = doc.createElement( "head" ); docElem.appendChild( headElem ); QDomElement dateElem = doc.createElement( "date" ); - dateElem.setAttribute( "year", QString::number(QDate::currentDate().year()) ); - dateElem.setAttribute( "month", QString::number(QDate::currentDate().month()) ); + dateElem.setAttribute( "year", QString::number(date.year()) ); + dateElem.setAttribute( "month", QString::number(date.month()) ); headElem.appendChild(dateElem); QDomElement myselfElem = doc.createElement( "contact" ); myselfElem.setAttribute( "type", "myself" ); @@ -293,7 +295,7 @@ // On hight-traffic channel, saving can take lots of CPU. (because the file is big) // So i wait a time proportional to the time needed to save.. - const QString filename=getFileName(c,QDate::currentDate()); + const QString filename=getFileName(c, date); if(!m_toSaveFileName.isEmpty() && m_toSaveFileName != filename) { //that mean the contact or the month has changed, save it now. saveToDisk(); Index: historyviewer.ui =================================================================== --- historyviewer.ui (revision 900154) +++ historyviewer.ui (working copy) @@ -57,6 +57,13 @@ + + + Import History + + + + Message Filter: @@ -73,7 +80,7 @@ - 200 + 140 0 Index: historydialog.cpp =================================================================== --- historydialog.cpp (revision 900154) +++ historydialog.cpp (working copy) @@ -18,6 +18,7 @@ #include "historydialog.h" #include "historylogger.h" +#include "historyimport.h" #include "ui_historyviewer.h" #include "kopetemetacontact.h" #include "kopeteprotocol.h" @@ -161,6 +162,7 @@ connect(mMainWidget->searchLine, SIGNAL(textChanged(const QString&)), this, SLOT(slotSearchTextChanged(const QString&))); connect(mMainWidget->contactComboBox, SIGNAL(activated(int)), this, SLOT(slotContactChanged(int))); connect(mMainWidget->messageFilterBox, SIGNAL(activated(int)), this, SLOT(slotFilterChanged(int ))); + connect(mMainWidget->importHistory, SIGNAL(clicked()), this, SLOT(slotImportHistory())); connect(mHtmlPart, SIGNAL(popupMenu(const QString &, const QPoint &)), this, SLOT(slotRightClick(const QString &, const QPoint &))); //initActions @@ -614,4 +616,10 @@ connect( QApplication::clipboard(), SIGNAL( selectionChanged()), mHtmlPart, SLOT(slotClearSelection())); } +void HistoryDialog::slotImportHistory(void) +{ + HistoryImport importer(this); + importer.exec(); +} + #include "historydialog.moc" Index: historydialog.h =================================================================== --- historydialog.h (revision 900154) +++ historydialog.h (working copy) @@ -96,6 +96,8 @@ void slotCopy(); void slotCopyURL(); + void slotImportHistory(); + private: enum Disabled { Prev=1, Next=2 }; void refreshEnabled( /*Disabled*/ uint disabled ); Index: CMakeLists.txt =================================================================== --- CMakeLists.txt (revision 900154) +++ CMakeLists.txt (working copy) @@ -11,7 +11,8 @@ historydialog.cpp historylogger.cpp converter.cpp - historyguiclient.cpp ) + historyguiclient.cpp + historyimport.cpp ) kde4_add_ui_files(kopete_history_PART_SRCS historyviewer.ui ) --------------070504040800050000020409 Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline _______________________________________________ kopete-devel mailing list kopete-devel@kde.org https://mail.kde.org/mailman/listinfo/kopete-devel --------------070504040800050000020409--