#
# Machekku Entity Time Support
#
# Version: 0.1.2
# Date: 2007-02-09
#
# iris/xmpp-im/types.cpp      |    2
# iris/xmpp-im/xmpp_tasks.cpp |    2
# src/entitytimetask.cpp      |  123 +++++++++++++++++++++++++++++++++++++++++++
# src/entitytimetask.h        |   49 +++++++++++++++++
# src/infodlg.cpp             |   35 +++++++++++-
# src/infodlg.h               |    3 -
# src/psiaccount.cpp          |    4 +
# src/src.pri                 |    4 +
# src/systeminfo.cpp          |   16 ++++-
# src/timeserver.cpp          |   83 +++++++++++++++++++++++++++++
# src/timeserver.h            |   35 ++++++++++++
# src/userlist.cpp            |   43 +++++++++++++++
# src/userlist.h              |    7 ++
# 13 files changed, 396 insertions(+), 10 deletions(-)
#
diff -rN -u old-psit-1/iris/xmpp-im/types.cpp new-psit-1/iris/xmpp-im/types.cpp
--- old-psit-1/iris/xmpp-im/types.cpp	2007-02-09 02:50:51.234375000 +0100
+++ new-psit-1/iris/xmpp-im/types.cpp	2007-02-09 02:50:57.828125000 +0100
@@ -1697,7 +1697,7 @@
 	QDomElement t = root.elementsByTagNameNS("jabber:x:delay", "x").item(0).toElement();
 	if(!t.isNull()) {
 		d->timeStamp = stamp2TS(t.attribute("stamp"));
-		d->timeStamp = d->timeStamp.addSecs(timeZoneOffset * 3600);
+		d->timeStamp = d->timeStamp.addSecs(timeZoneOffset * 60);
 		d->spooled = true;
 	}
 	else {
diff -rN -u old-psit-1/iris/xmpp-im/xmpp_tasks.cpp new-psit-1/iris/xmpp-im/xmpp_tasks.cpp
--- old-psit-1/iris/xmpp-im/xmpp_tasks.cpp	2007-02-09 02:50:51.187500000 +0100
+++ new-psit-1/iris/xmpp-im/xmpp_tasks.cpp	2007-02-09 02:50:57.859375000 +0100
@@ -700,7 +700,7 @@
 			if(i.hasAttribute("stamp")) {
 				QDateTime dt;
 				if(stamp2TS(i.attribute("stamp"), &dt))
-					dt = dt.addSecs(client()->timeZoneOffset() * 3600);
+					dt = dt.addSecs(client()->timeZoneOffset() * 60);
 				p.setTimeStamp(dt);
 			}
 		}
diff -rN -u old-psit-1/src/entitytimetask.cpp new-psit-1/src/entitytimetask.cpp
--- old-psit-1/src/entitytimetask.cpp	1970-01-01 01:00:00.000000000 +0100
+++ new-psit-1/src/entitytimetask.cpp	2007-02-09 02:51:00.671875000 +0100
@@ -0,0 +1,123 @@
+/*
+ * entitytimetask.cpp - Entity time fetching task
+ * Copyright (C) 2007  Maciej Niedzielski
+ *
+ * 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.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <QTime>
+#include "entitytimetask.h"
+#include "xmpp_xmlcommon.h"
+
+using namespace XMPP;
+
+/**
+ * \class EntityTimeTask
+ * \brief Gets entity time
+ *
+ * This task can be used to get time zone information of an entity.
+ */
+
+
+// convert [+|-]hh:mm to minutes
+static Maybe<int> stringToOffset(const QString &off)
+{
+	QTime t = QTime::fromString(off.mid(1), "hh:mm");
+
+	if (t.isValid() && off[0] == '+' || off[0] == '-') {
+		int m = t.hour() * 60 + t.minute();
+		if (off[0] == '-')
+			m = -m;
+		return m;
+	}
+	else {
+		return Maybe<int>();
+	}
+}
+
+/**
+ * \brief Create new task.
+ */
+EntityTimeTask::EntityTimeTask(Task* parent) : Task(parent)
+{
+}
+
+/**
+ * \brief Queried entity's JID.
+ */
+const Jid & EntityTimeTask::jid() const
+{
+	return jid_;
+}
+
+/**
+ * \brief Prepares the task to get information from JID.
+ */
+void EntityTimeTask::get(const Jid &jid)
+{
+	jid_ = jid;
+	iq_ = createIQ(doc(), "get", jid_.full(), id());
+	QDomElement query = doc()->createElement("query");
+	query.setAttribute("xmlns", "http://www.xmpp.org/extensions/xep-0202.html#ns");
+	iq_.appendChild(query);
+}
+
+void EntityTimeTask::onGo()
+{
+	send(iq_);
+}
+
+bool EntityTimeTask::take(const QDomElement &x)
+{
+	if (!iqVerify(x, jid_, id()))
+		return false;
+
+	if (x.attribute("type") == "result") {
+		bool found = false;
+		QDomElement q = queryTag(x);
+		QDomElement tag;
+		tag = findSubTag(q, "utc", &found);
+		if (found)
+			utc_ = tagContent(tag);
+		tag = findSubTag(q, "tzo", &found);
+		if (found) {
+			tzoString_ = tagContent(tag);
+			tzo_ = stringToOffset(tzoString_);
+		}
+		setSuccess();
+	}
+	else {
+		setError(x);
+	}
+
+	return true;
+}
+
+/**
+ * \brief Timezone offset in [+|-]hh:mm format (or empty string if no data).
+ */
+const QString& EntityTimeTask::timezoneOffsetString() const
+{
+	return tzoString_;
+}
+
+/**
+ * \brief Timezone offset in minutes (if available).
+ */
+Maybe<int> EntityTimeTask::timezoneOffset() const
+{
+	return tzo_;
+}
diff -rN -u old-psit-1/src/entitytimetask.h new-psit-1/src/entitytimetask.h
--- old-psit-1/src/entitytimetask.h	1970-01-01 01:00:00.000000000 +0100
+++ new-psit-1/src/entitytimetask.h	2007-02-09 02:51:00.687500000 +0100
@@ -0,0 +1,49 @@
+/*
+ * entitytimetask.h - Entity time fetching task
+ * Copyright (C) 2007  Maciej Niedzielski
+ *
+ * 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.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef ENTITYTIMETASK_H
+#define ENTITYTIMETASK_H
+
+#include <QDomElement>
+#include "xmpp_task.h"
+#include "xmpp_jid.h"
+#include "maybe.h"
+
+class EntityTimeTask : public XMPP::Task
+{
+public:
+	EntityTimeTask(Task*);
+
+	void onGo();
+	bool take(const QDomElement &);
+	void get(const XMPP::Jid &jid);
+	const XMPP::Jid & jid() const;
+
+	const QString& timezoneOffsetString() const;
+	Maybe<int> timezoneOffset() const;
+
+private:
+	QDomElement iq_;
+	XMPP::Jid jid_;
+	QString utc_, tzoString_;
+	Maybe<int> tzo_;
+};
+
+#endif
diff -rN -u old-psit-1/src/infodlg.cpp new-psit-1/src/infodlg.cpp
--- old-psit-1/src/infodlg.cpp	2007-02-09 02:50:51.062500000 +0100
+++ new-psit-1/src/infodlg.cpp	2007-02-09 02:51:01.000000000 +0100
@@ -40,6 +40,7 @@
 #include "iconset.h"
 #include "common.h"
 #include "lastactivitytask.h"
+#include "entitytimetask.h"
 #include "vcardfactory.h"
 #include "iconwidget.h"
 #include "contactview.h"
@@ -117,7 +118,7 @@
 	updateStatus();
 	foreach(UserListItem* u, d->pa->findRelevant(j)) {
 		foreach(UserResource r, u->userResourceList()) {
-			requestClientVersion(d->jid.withResource(r.name()));
+			requestResourceInfo(d->jid.withResource(r.name()));
 		}
 		if (u->userResourceList().isEmpty() && u->lastAvailable().isNull()) {
 			requestLastActivity();
@@ -578,13 +579,24 @@
 	}
 }
 
-void InfoDlg::requestClientVersion(const Jid& j)
+/**
+ * \brief Requests per-resource information.
+ *
+ * Gets information about client version and time.
+ */
+void InfoDlg::requestResourceInfo(const Jid& j)
 {
 	d->infoRequested += j.full();
+
 	JT_ClientVersion *jcv = new JT_ClientVersion(d->pa->client()->rootTask());
 	connect(jcv, SIGNAL(finished()), SLOT(clientVersionFinished()));
 	jcv->get(j);
 	jcv->go(true);
+
+	EntityTimeTask *jet = new EntityTimeTask(d->pa->client()->rootTask());
+	connect(jet, SIGNAL(finished()), SLOT(entityTimeFinished()));
+	jet->get(j);
+	jet->go(true);
 }
 
 void InfoDlg::clientVersionFinished()
@@ -604,6 +616,23 @@
 	}
 }
 
+void InfoDlg::entityTimeFinished()
+{
+	EntityTimeTask *j = (EntityTimeTask *)sender();
+	if(j->success()) {
+		foreach(UserListItem* u, d->pa->findRelevant(j->jid())) {
+			UserResourceList::Iterator rit = u->userResourceList().find(j->jid().resource());
+			bool found = (rit == u->userResourceList().end()) ? false: true;
+			if(!found)
+				continue;
+
+			(*rit).setTimezone(j->timezoneOffset());
+			d->pa->contactProfile()->updateEntry(*u);
+			updateStatus();
+		}
+	}
+}
+
 void InfoDlg::requestLastActivity()
 {
 	LastActivityTask *jla = new LastActivityTask(d->jid.bare(),d->pa->client()->rootTask());
@@ -629,7 +658,7 @@
 {
 	if (d->jid.compare(j,false)) {
 		if (!d->infoRequested.contains(j.withResource(r.name()).full()))
-			requestClientVersion(j.withResource(r.name()));
+			requestResourceInfo(j.withResource(r.name()));
 	}
 }
 
diff -rN -u old-psit-1/src/infodlg.h new-psit-1/src/infodlg.h
--- old-psit-1/src/infodlg.h	2007-02-09 02:50:51.062500000 +0100
+++ new-psit-1/src/infodlg.h	2007-02-09 02:51:01.015625000 +0100
@@ -58,6 +58,7 @@
 	void contactUnavailable(const Jid &, const Resource &);
 	void contactUpdated(const Jid &);
 	void clientVersionFinished();
+	void entityTimeFinished();
 	void requestLastActivityFinished();
 	void jt_finished();
 	void doSubmit();
@@ -76,7 +77,7 @@
 	bool edited();
 	void setEdited(bool);
 	void setPreviewPhoto(const QString& str);
-	void requestClientVersion(const XMPP::Jid& j);
+	void requestResourceInfo(const XMPP::Jid& j);
 	void requestLastActivity();
 };
 
diff -rN -u old-psit-1/src/psiaccount.cpp new-psit-1/src/psiaccount.cpp
--- old-psit-1/src/psiaccount.cpp	2007-02-09 02:50:51.062500000 +0100
+++ new-psit-1/src/psiaccount.cpp	2007-02-09 02:51:06.062500000 +0100
@@ -113,6 +113,7 @@
 #include "certutil.h"
 #include "proxy.h"
 #include "psicontactlist.h"
+#include "timeserver.h"
 
 #ifdef PSI_PLUGINS
 #include "pluginmanager.h"
@@ -563,6 +564,9 @@
 	d->httpAuthManager = new HttpAuthManager(d->client->rootTask());
 	connect(d->httpAuthManager, SIGNAL(confirmationRequest(const PsiHttpAuthRequest &)), SLOT(incomingHttpAuthRequest(const PsiHttpAuthRequest &)));
 
+	// Time server
+	new TimeServer(d->client->rootTask());
+
 	// Initialize Adhoc Commands server
 	d->ahcManager = new AHCServerManager(this);
 	d->rcSetStatusServer = 0;
diff -rN -u old-psit-1/src/src.pri new-psit-1/src/src.pri
--- old-psit-1/src/src.pri	2007-02-09 02:50:51.046875000 +0100
+++ new-psit-1/src/src.pri	2007-02-09 02:51:07.000000000 +0100
@@ -165,6 +165,8 @@
 	$$PWD/xdata_widget.h \
 	$$PWD/statuspreset.h \
 	$$PWD/lastactivitytask.h \
+	$$PWD/entitytimetask.h \
+	$$PWD/timeserver.h \
 	$$PWD/mucmanager.h \
 	$$PWD/mucjoindlg.h \
 	$$PWD/mucconfigdlg.h \
@@ -276,6 +278,8 @@
 	$$PWD/psiactionlist.cpp \
 	$$PWD/xdata_widget.cpp \
 	$$PWD/lastactivitytask.cpp \
+	$$PWD/entitytimetask.cpp \
+	$$PWD/timeserver.cpp \
 	$$PWD/statuspreset.cpp \
 	$$PWD/mucmanager.cpp \
 	$$PWD/mucjoindlg.cpp \
diff -rN -u old-psit-1/src/systeminfo.cpp new-psit-1/src/systeminfo.cpp
--- old-psit-1/src/systeminfo.cpp	2007-02-09 02:50:51.031250000 +0100
+++ new-psit-1/src/systeminfo.cpp	2007-02-09 02:51:07.078125000 +0100
@@ -37,7 +37,7 @@
 		if(s.at(0) == '+')
 			s.remove(0,1);
 		s.truncate(s.length()-2);
-		timezone_offset_ = s.toInt();
+		timezone_offset_ = s.toInt() * 60;	// FIX-ME: should really read the offset in minutes
 	}
 	strcpy(fmt, "%Z");
 	strftime(str, 256, fmt, localtime(&x));
@@ -144,14 +144,12 @@
 
 #if defined(Q_WS_WIN)
 	TIME_ZONE_INFORMATION i;
-	//GetTimeZoneInformation(&i);
-	//timezone_offset_ = (-i.Bias) / 60;
 	memset(&i, 0, sizeof(i));
 	bool inDST = (GetTimeZoneInformation(&i) == TIME_ZONE_ID_DAYLIGHT);
 	int bias = i.Bias;
 	if(inDST)
 		bias += i.DaylightBias;
-	timezone_offset_ = (-bias) / 60;
+	timezone_offset_ = -bias;
 	timezone_str_ = "";
 	for(int n = 0; n < 32; ++n) {
 		int w = inDST ? i.DaylightName[n] : i.StandardName[n];
@@ -192,3 +190,13 @@
 }
 
 SystemInfo* SystemInfo::instance_ = NULL;
+
+/**
+ * \fn int SystemInfo::timezoneOffset()
+ * \brief Local timezone offset in minutes.
+ */
+
+/**
+ * \fn const QString& SystemInfo::timezoneString() const
+ * \brief Local timezone name.
+ */
diff -rN -u old-psit-1/src/timeserver.cpp new-psit-1/src/timeserver.cpp
--- old-psit-1/src/timeserver.cpp	1970-01-01 01:00:00.000000000 +0100
+++ new-psit-1/src/timeserver.cpp	2007-02-09 02:51:07.375000000 +0100
@@ -0,0 +1,83 @@
+/*
+ * timeserver.cpp - Entity time server
+ * Copyright (C) 2001, 2002, 2007  Justin Karneges, Maciej Niedzielski
+ *
+ * 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.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include "timeserver.h"
+#include "systeminfo.h"
+#include "xmpp_xmlcommon.h"
+#include <QDateTime>
+
+using namespace XMPP;
+
+
+/**
+ * \class TimeServer
+ * \brief Server current time
+ *
+ * This serving task answers XEP-0202 and XEP-0090 queries
+ */
+
+TimeServer::TimeServer(Task *parent)
+:Task(parent)
+{
+}
+
+TimeServer::~TimeServer()
+{
+}
+
+bool TimeServer::take(const QDomElement &e)
+{
+	if (e.tagName() != "iq" || e.attribute("type") != "get")
+		return false;
+
+	QString ns = queryNS(e);
+	if (ns == "http://www.xmpp.org/extensions/xep-0202.html#ns") {
+		QDomElement iq = createIQ(doc(), "result", e.attribute("from"), e.attribute("id"));
+		QDomElement query = doc()->createElement("query");
+		query.setAttribute("xmlns", ns);
+		iq.appendChild(query);
+
+		QDateTime local = QDateTime::currentDateTime();
+		int off = SystemInfo::instance()->timezoneOffset();
+		QTime t = QTime(0, 0).addSecs(abs(off)*60);
+		QString tzo = (off < 0 ? "-" : "+") + t.toString("HH:mm");
+		query.appendChild(textTag(doc(), "tzo", tzo));
+		query.appendChild(textTag(doc(), "utc", local.toUTC().toString(Qt::ISODate) + "Z"));
+
+		send(iq);
+		return true;
+	}
+	else if (ns == "jabber:iq:time") {
+		QDomElement iq = createIQ(doc(), "result", e.attribute("from"), e.attribute("id"));
+		QDomElement query = doc()->createElement("query");
+		query.setAttribute("xmlns", "jabber:iq:time");
+		iq.appendChild(query);
+
+		QDateTime local = QDateTime::currentDateTime();
+		QString str = SystemInfo::instance()->timezoneString();
+		query.appendChild(textTag(doc(), "utc", TS2stamp(local.toUTC())));
+		query.appendChild(textTag(doc(), "tz", str));
+		query.appendChild(textTag(doc(), "display", QString("%1 %2").arg(local.toString()).arg(str)));
+
+		send(iq);
+		return true;
+	}
+	return false;
+}
diff -rN -u old-psit-1/src/timeserver.h new-psit-1/src/timeserver.h
--- old-psit-1/src/timeserver.h	1970-01-01 01:00:00.000000000 +0100
+++ new-psit-1/src/timeserver.h	2007-02-09 02:51:07.437500000 +0100
@@ -0,0 +1,35 @@
+/*
+ * timeserver.h - Entity time server
+ * Copyright (C) 2007  Maciej Niedzielski
+ *
+ * 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.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef TIMESERVER_H
+#define TIMESERVER_H
+
+#include "xmpp_task.h"
+
+class TimeServer : public XMPP::Task
+{
+	Q_OBJECT
+public:
+	TimeServer(Task *);
+	~TimeServer();
+	bool take(const QDomElement &);
+};
+
+#endif
diff -rN -u old-psit-1/src/userlist.cpp new-psit-1/src/userlist.cpp
--- old-psit-1/src/userlist.cpp	2007-02-09 02:50:51.015625000 +0100
+++ new-psit-1/src/userlist.cpp	2007-02-09 02:51:12.562500000 +0100
@@ -103,6 +103,43 @@
 	}
 }
 
+/**
+ * \brief Timezone offset in minutes (if available).
+ */
+Maybe<int> UserResource::timezoneOffset() const
+{
+	return v_tzo;
+}
+
+/**
+ * \brief Timezone offset as string (or empty string if no data).
+ *
+ * String is formatted as "UTC[+|-]h[:mm]".
+ */
+const QString& UserResource::timezoneOffsetString() const
+{
+	return v_tzoString;
+}
+
+/**
+ * \brief Set timezone offset (in minutes).
+ */
+void UserResource::setTimezone(Maybe<int> off)
+{
+	v_tzo = off;
+
+	if (off.hasValue()) {
+		QTime t = QTime(0, 0).addSecs(abs(off.value())*60);
+		QString u = QString("UTC") + (off.value() < 0 ? "-" : "+");
+		u += QString::number(t.hour());
+		if (t.minute())
+			u += QString(":%1").arg(t.minute());
+		v_tzoString = u;
+	}
+	else
+		v_tzoString = "";
+}
+
 const QString & UserResource::publicKeyID() const
 {
 	return v_keyID;
@@ -563,6 +600,12 @@
 			if (!r.geoLocation().isNull())
 				str += QString("<br><nobr>") + QObject::tr("Geolocation") + ": " + QString::number(r.geoLocation().lat().value()) + "/" + QString::number(r.geoLocation().lon().value()) + "</nobr>"; 
 
+			// Entity Time
+			if (r.timezoneOffset().hasValue()) {
+				QDateTime dt = QDateTime::currentDateTime().toUTC().addSecs(r.timezoneOffset().value()*60);
+				str += QString("<br><nobr>") + QObject::tr("Time") + QString(": %1 (%2)").arg(dt.toString(Qt::TextDate)).arg(r.timezoneOffsetString()) + "</nobr>";
+			}
+
 			// client
 			if(!r.versionString().isEmpty() && PsiOptions::instance()->getOption("options.ui.contactlist.tooltip.client-version").toBool()) {
 				QString ver = r.versionString();
diff -rN -u old-psit-1/src/userlist.h new-psit-1/src/userlist.h
--- old-psit-1/src/userlist.h	2007-02-09 02:50:51.000000000 +0100
+++ new-psit-1/src/userlist.h	2007-02-09 02:51:12.578125000 +0100
@@ -30,6 +30,7 @@
 #include "mood.h"
 #include "geolocation.h"
 #include "physicallocation.h"
+#include "maybe.h"
 
 class AvatarFactory;
 
@@ -48,6 +49,10 @@
 	const QString& clientOS() const;
 	void setClient(const QString& name, const QString& version, const QString& os);
 
+	Maybe<int> timezoneOffset() const;
+	const QString& timezoneOffsetString() const;
+	void setTimezone(Maybe<int> tzo);
+
 	const QString & publicKeyID() const;
 	int pgpVerifyStatus() const;
 	QDateTime sigTimestamp() const;
@@ -64,6 +69,8 @@
 
 private:
 	QString v_ver, v_clientName, v_clientVersion, v_clientOS, v_keyID;
+	Maybe<int> v_tzo;
+	QString v_tzoString;
 	QString v_tune;
 	GeoLocation v_geoLocation;
 	PhysicalLocation v_physicalLocation;

