• Skip to content
  • Skip to link menu
  • KDE API Reference
  • KDE Home
  • Contact Us
 

Nepomuk-Core

  • KTp
logs-importer-private.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2012 Dan Vrátil <dvratil@redhat.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18 
19 #include "logs-importer-private.h"
20 #include "logs-importer.h"
21 
22 #include <KLocalizedString>
23 #include <KDebug>
24 #include <KStandardDirs>
25 
26 using namespace KTp;
27 
28 LogsImporter::Private::Private(KTp::LogsImporter* parent)
29  : QThread(parent)
30  , m_day(0)
31  , m_month(0)
32  , m_year(0)
33  , m_isMUCLog(false)
34 {
35 
36 }
37 
38 LogsImporter::Private::~Private()
39 {
40 
41 }
42 
43 void LogsImporter::Private::setAccountId(const QString& accountId)
44 {
45  m_accountId = accountId;
46 }
47 
48 void LogsImporter::Private::run()
49 {
50  QStringList files = findKopeteLogs(m_accountId);
51  if (files.isEmpty()) {
52  Q_EMIT error(i18n("No Kopete logs found"));
53  return;
54  }
55 
56  Q_FOREACH (const QString &file, files) {
57  convertKopeteLog(file);
58  }
59 }
60 
61 QString LogsImporter::Private::accountIdToAccountName(const QString &accountId) const
62 {
63  int plugin = accountId.indexOf(QLatin1Char('/'));
64  int protocol = accountId.indexOf(QLatin1Char('/'), plugin + 1);
65 
66  QString username = accountId.mid(protocol + 1);
67 
68  /* ICQ accounts are prefixed with '_X' (X being a number) */
69  if (username.startsWith(QLatin1Char('_'))) {
70  username = username.remove(0, 2);
71  }
72 
73  /* Remove trailing "0" */
74  username.chop(1);
75 
76  /* Kopete escapes ".", "/", "~", "?" and "*" as "-" */
77  username.replace(QLatin1String("_2e"), QLatin1String("-")); /* . */
78  username.replace(QLatin1String("_2f"), QLatin1String("-")); /* / */
79  username.replace(QLatin1String("_7e"), QLatin1String("-")); /* ~ */
80  username.replace(QLatin1String("_3f"), QLatin1String("-")); /* ? */
81  username.replace(QLatin1String("_2a"), QLatin1String("-")); /* * */
82 
83  /* But Kopete has apparently no problem with "@", so unescape it */
84  username.replace(QLatin1String("_40"), QLatin1String("@"));
85 
86  return username;
87 }
88 
89 QString LogsImporter::Private::accountIdToProtocol(const QString &accountId) const
90 {
91  if (accountId.startsWith(QLatin1String("haze/aim/"))) {
92  return QLatin1String("AIMProtocol");
93  } else if (accountId.startsWith(QLatin1String("haze/msn/"))) {
94  return QLatin1String("WlmProtocol");
95  } else if (accountId.startsWith(QLatin1String("haze/icq/"))) {
96  return QLatin1String("ICQProtocol");
97  } else if (accountId.startsWith(QLatin1String("haze/yahoo/"))) {
98  return QLatin1String("YahooProtocol");
99  } else if (accountId.startsWith(QLatin1String("gabble/jabber/"))) {
100  return QLatin1String("JabberProtocol");
101  } else if (accountId.startsWith(QLatin1String("sunshine/gadugadu/")) ||
102  accountId.startsWith(QLatin1String("haze/gadugadu/"))) {
103  return QLatin1String("GaduProtocol");
104  } else {
105  /* We don't support these Kopete protocols:
106  * Bonjour - unable to reliably map Telepathy account to Kopete
107  * GroupWise - no support in Telepathy
108  * Meanwhile - no support in Telepathy
109  * QQ - no support in Telepathy
110  * SMS - no support in Telepathy
111  * Skype - not supported by KTp
112  * WinPopup - no support in Telepathy
113  */
114  kWarning() << accountId << "is an unsupported protocol";
115  return QString();
116  }
117 }
118 
119 QStringList LogsImporter::Private::findKopeteLogs(const QString &accountId) const
120 {
121  QStringList files;
122 
123  QString protocol = accountIdToProtocol(accountId);
124  if (protocol.isEmpty()) {
125  kWarning() << "Unsupported protocol";
126  return files;
127  }
128 
129  QString kopeteAccountId = accountIdToAccountName(accountId);
130  if (kopeteAccountId.isEmpty()) {
131  kWarning() << "Unable to parse account ID";
132  return files;
133  }
134 
135  QDir dir(KStandardDirs::locateLocal("data", QLatin1String("kopete/logs/") +
136  protocol + QDir::separator() + kopeteAccountId));
137 
138  if (dir.exists()) {
139  QFileInfoList entries = dir.entryInfoList(QStringList() << QLatin1String("*.xml"), QDir::Files | QDir::NoDotAndDotDot | QDir::Readable);
140  Q_FOREACH (const QFileInfo &finfo, entries) {
141  files << finfo.filePath();
142  }
143  }
144 
145  return files;
146 }
147 
148 void LogsImporter::Private::initKTpDocument()
149 {
150  m_ktpDocument.clear();
151  m_ktpLogElement.clear();
152 
153  QDomNode xmlNode = m_ktpDocument.createProcessingInstruction(
154  QLatin1String("xml"), QLatin1String("version='1.0' encoding='utf-8'"));
155  m_ktpDocument.appendChild(xmlNode);
156 
157  xmlNode = m_ktpDocument.createProcessingInstruction(
158  QLatin1String("xml-stylesheet"), QLatin1String("type=\"text/xsl\" href=\"log-store-xml.xsl\""));
159  m_ktpDocument.appendChild(xmlNode);
160 
161  m_ktpLogElement = m_ktpDocument.createElement(QLatin1String("log"));
162  m_ktpDocument.appendChild(m_ktpLogElement);
163 }
164 
165 void LogsImporter::Private::saveKTpDocument()
166 {
167  QString filename = QString(QLatin1String("%1%2%3.log"))
168  .arg(m_year)
169  .arg(m_month, 2, 10, QLatin1Char('0'))
170  .arg(m_day, 2, 10, QLatin1Char('0'));
171 
172  KStandardDirs dirs;
173  QString path = dirs.localxdgdatadir() + QDir::separator() + QLatin1String("TpLogger") + QDir::separator() + QLatin1String("logs");
174 
175  if (m_isMUCLog) {
176  path += QDir::separator() + QLatin1String("chatrooms");
177  } else {
178  QString accountId = m_accountId;
179  /* Escape '/' in accountId as '_' */
180  if (m_accountId.contains(QLatin1Char('/'))) {
181  accountId.replace(QLatin1Char('/'), QLatin1String("_"));
182  }
183  path += QDir::separator() + accountId;
184  }
185 
186  path += QDir::separator() + m_contactId;
187 
188  /* Make sure the path exists */
189  QDir dir(path);
190  if (!dir.exists()) {
191  QDir::home().mkpath(QDir::home().relativeFilePath(dir.path()));
192  }
193 
194  path += QDir::separator() + filename;
195 
196  QFile outFile(path);
197  if (outFile.exists()) {
198  kWarning() << path << "already exists, not importing logs";
199  return;
200  }
201 
202  outFile.open(QIODevice::WriteOnly);
203  QTextStream stream(&outFile);
204  m_ktpDocument.save(stream, 0);
205 
206  kDebug() << "Stored as" << path;
207 }
208 
209 KDateTime LogsImporter::Private::parseKopeteTime(const QDomElement& kopeteMessage) const
210 {
211  QString strtime = kopeteMessage.attribute(QLatin1String("time"));
212  if (strtime.isEmpty()) {
213  return KDateTime();
214  }
215 
216  /* Kopete time attribute is in format "D H:M:S" - year and month are stored in
217  * log header, Hour, minute and seconds don't have zero padding */
218  QStringList dateTime = strtime.split(QLatin1Char(' '), QString::SkipEmptyParts);
219  if (dateTime.length() != 2) {
220  return KDateTime();
221  }
222 
223  QStringList time = dateTime.at(1).split(QLatin1Char(':'));
224 
225  QString str = QString(QLatin1String("%1-%2-%3T%4:%5:%6Z"))
226  .arg(m_year)
227  .arg(m_month, 2, 10, QLatin1Char('0'))
228  .arg(dateTime.at(0).toInt(), 2, 10, QLatin1Char('0'))
229  .arg(time.at(0).toInt(), 2, 10, QLatin1Char('0'))
230  .arg(time.at(1).toInt(), 2, 10, QLatin1Char('0'))
231  .arg(time.at(2).toInt(), 2, 10, QLatin1Char('0'));
232 
233  /* Kopete stores date in local timezone but Telepathy in UTC. Note that we
234  * must use time offset at the specific date rather then current offset
235  * (could be different due to for example DST) */
236  KDateTime localTz = KDateTime::fromString(str, KDateTime::ISODate);
237  KDateTime utc = localTz.addSecs(-KDateTime::currentLocalDateTime().timeZone().offset(localTz.toTime_t()));
238 
239  return utc;
240 }
241 
242 QDomElement LogsImporter::Private::convertKopeteMessage(const QDomElement& kopeteMessage)
243 {
244  KDateTime time = parseKopeteTime(kopeteMessage);
245  if (!time.isValid()) {
246  kWarning() << "Failed to parse message time, skipping message";
247  return QDomElement();
248  }
249 
250  /* If this is the very first message we are processing, then initialize
251  * the day counter */
252  if (m_day == 0) {
253  m_day = time.date().day();
254  }
255 
256  /* Kopete stores logs by months, while Telepathy by days. When day changes,
257  * save to current KTp log and prepare a new document */
258  if (time.date().day() != m_day) {
259  saveKTpDocument();
260  m_day = time.date().day();
261 
262  initKTpDocument();
263  }
264 
265  QDomElement ktpMessage = m_ktpDocument.createElement(QLatin1String("message"));
266  ktpMessage.setAttribute(QLatin1String("time"), time.toUtc().toString(QLatin1String("%Y%m%dT%H:%M:%S")));
267 
268  QString sender = kopeteMessage.attribute(QLatin1String("from"));
269  if (!m_isMUCLog && sender.startsWith(m_contactId) && sender.length() > m_contactId.length()) {
270  m_isMUCLog = true;
271  }
272 
273  /* In MUC, the "from" attribute is in format "room@conf.server/senderId", so strip
274  * the room name */
275  if (m_isMUCLog) {
276  sender = sender.remove(m_contactId);
277  }
278 
279  ktpMessage.setAttribute(QLatin1String("id"), sender);
280  ktpMessage.setAttribute(QLatin1String("name"), kopeteMessage.attribute(QLatin1String("nick")));
281 
282  if (sender == m_meId) {
283  ktpMessage.setAttribute(QLatin1String("isuser"), QLatin1String("true"));
284  } else {
285  ktpMessage.setAttribute(QLatin1String("isuser"), QLatin1String("false"));
286  }
287 
288  /* These are not present in Kopete logs, but that should not matter */
289  ktpMessage.setAttribute(QLatin1String("token"), QString());
290  ktpMessage.setAttribute(QLatin1String("message-token"), QString());
291  ktpMessage.setAttribute(QLatin1String("type"), QLatin1String("normal"));
292 
293  /* Copy the message content */
294  QDomText message = m_ktpDocument.createTextNode(kopeteMessage.text());
295  ktpMessage.appendChild(message);
296 
297  return ktpMessage;
298 }
299 
300 void LogsImporter::Private::convertKopeteLog(const QString& filepath)
301 {
302  kDebug() << "Converting" << filepath;
303 
304  /* Init */
305  m_day = 0;
306  m_month = 0;
307  m_year = 0;
308  m_isMUCLog = false;
309  m_meId.clear();
310  m_contactId.clear();
311 
312  initKTpDocument();
313 
314  QFile f(filepath);
315  f.open(QIODevice::ReadOnly);
316 
317  QByteArray ba = f.readAll();
318 
319  m_kopeteDocument.setContent(ba);
320  /* Get <history> node */
321  QDomElement history = m_kopeteDocument.documentElement();
322  /* Get all <msg> nodes in <history> node */
323  QDomNodeList kopeteMessages = history.elementsByTagName(QLatin1String("msg"));
324 
325  /* Get <head> node and parse it */
326  QDomNodeList heads = history.elementsByTagName(QLatin1String("head"));
327  if (heads.isEmpty()) {
328  Q_EMIT error(i18n("Invalid Kopete log format"));
329  return;
330  }
331 
332  QDomNode head = heads.item(0);
333  QDomNodeList headData = head.childNodes();
334  if (headData.length() < 3) {
335  Q_EMIT error(i18n("Invalid Kopete log format"));
336  return;
337  }
338 
339  for (int i = 0; i < headData.count(); i++) {
340  QDomElement el = headData.item(i).toElement();
341 
342  if (el.tagName() == QLatin1String("date")) {
343  m_year = el.attribute(QLatin1String("year"), QString()).toInt();
344  m_month = el.attribute(QLatin1String("month"), QString()).toInt();
345  } else if (el.tagName() == QLatin1String("contact")) {
346  if (el.attribute(QLatin1String("type")) == QLatin1String("myself")) {
347  m_meId = el.attribute(QLatin1String("contactId"));
348  } else {
349  m_contactId = el.attribute(QLatin1String("contactId"));
350  }
351  }
352  }
353 
354  if ((m_year == 0) || (m_month == 0) || m_meId.isEmpty() || m_contactId.isEmpty()) {
355  kWarning() << "Failed to correctly parse header. Possibly invalid log format";
356  return;
357  }
358 
359  for (int i = 0; i < kopeteMessages.count(); i++) {
360  QDomElement kopeteMessage = kopeteMessages.item(i).toElement();
361 
362  QDomElement ktpMessage = convertKopeteMessage(kopeteMessage);
363 
364  m_ktpLogElement.appendChild(ktpMessage);
365  }
366 
367  saveKTpDocument();
368 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Fri Mar 22 2013 10:58:52 by doxygen 1.8.1.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

ktp-common-internals API Reference

Skip menu "ktp-common-internals API Reference"
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal