ktexteditor_wakatime v1.5.1
Kate plugin to interface with WakaTime.
Loading...
Searching...
No Matches
wakatime.cpp
1// SPDX-License-Identifier: MIT
2#include <QtCore/QDir>
3#include <QtCore/QProcess>
4
5#include "wakatime.h"
6
7Q_LOGGING_CATEGORY(gLogWakaTime, "wakatime")
8
9const auto kStringLiteralSlash = QStringLiteral("/");
10const auto kWakaTimeCli = QStringLiteral("wakatime-cli");
11
12WakaTime::WakaTime(QObject *parent) : lastTimeSent(QDateTime::fromMSecsSinceEpoch(0)) {
13 Q_UNUSED(parent);
14}
15
16QString WakaTime::getBinPath(const QStringList &binNames) {
17 for (auto &name : binNames) {
18 if (binPathCache.contains(name)) {
19 return binPathCache.value(name);
20 }
21 }
22 auto dotWakaTime = QStringLiteral("%1/.wakatime").arg(QDir::homePath());
23#ifndef Q_OS_WIN
24 static const auto pathSeparator = QStringLiteral(":");
25 static const auto kDefaultPath =
26 QStringLiteral("/usr/bin:/usr/local/bin:/opt/bin:/opt/local/bin");
27#else
28 static const auto pathSeparator = QStringLiteral(";");
29 static const auto kDefaultPath = QStringLiteral("C:\\Windows\\System32;C:\\Windows");
30#endif
31 const auto path = qEnvironmentVariable("PATH", kDefaultPath);
32 auto paths = path.split(pathSeparator, Qt::SkipEmptyParts);
33 paths.insert(0, dotWakaTime);
34 for (auto path : paths) {
35 for (auto &name : binNames) {
36 auto lookFor = path + QDir::separator() + name;
37 auto fi = QFileInfo(lookFor);
38 if (fi.exists(lookFor) && fi.isExecutable()) {
39 binPathCache[name] = lookFor;
40 return lookFor;
41 }
42 }
43 }
44 return QString();
45}
46
47QString WakaTime::getProjectDirectory(const QFileInfo &fileInfo) {
48 QDir currentDirectory(fileInfo.canonicalPath());
49 static QStringList filters;
50 static const auto gitStr = QStringLiteral(".git");
51 static const auto svnStr = QStringLiteral(".svn");
52 filters << gitStr << svnStr;
53 bool vcDirFound = false;
54 while (!vcDirFound) {
55 if (!currentDirectory.canonicalPath().compare(kStringLiteralSlash)) {
56 break;
57 }
58 auto entries =
59 currentDirectory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden);
60 for (const auto &entry : entries) {
61 auto name = entry.fileName();
62 if ((name == gitStr || name == svnStr) && entry.isDir()) {
63 vcDirFound = true;
64 return currentDirectory.dirName();
65 }
66 }
67 currentDirectory.cdUp();
68 }
69 return QString();
70}
71
72WakaTime::State WakaTime::send(const QString &filePath,
73 const QString &mode,
74 int lineNumber,
75 int cursorPosition,
76 int linesInFile,
77 bool isWrite) {
78#ifdef Q_OS_WIN
79#ifdef Q_PROCESSOR_X86_64
80 auto wakatimeCliPath = getBinPath({QStringLiteral("wakatime-cli-windows-amd64.exe"),
81 QStringLiteral("wakatime-cli.exe"),
82 QStringLiteral("wakatime.exe")});
83#elif defined(Q_PROCESSOR_ARM)
84 auto wakatimeCliPath = getBinPath({QStringLiteral("wakatime-cli-windows-arm64.exe"),
85 QStringLiteral("wakatime-cli.exe"),
86 QStringLiteral("wakatime.exe")});
87#else
88 auto wakatimeCliPath = getBinPath({QStringLiteral("wakatime-cli-windows-386.exe"),
89 QStringLiteral("wakatime-cli.exe"),
90 QStringLiteral("wakatime.exe")});
91#endif // Q_PROCESSOR_X86_64
92#elif defined(Q_OS_APPLE)
93 auto wakatimeCliPath = getBinPath({QStringLiteral("wakatime-cli-darwin-arm64"),
94 QStringLiteral("wakatime-cli-darwin-amd64"),
95 kWakaTimeCli,
96 QStringLiteral("wakatime")});
97#else
98 auto wakatimeCliPath = getBinPath({kWakaTimeCli, QStringLiteral("wakatime")});
99#endif // Q_OS_WIN
100 if (wakatimeCliPath.isEmpty()) {
101 qCWarning(gLogWakaTime) << "wakatime-cli not found in PATH.";
103 }
104 // Could be untitled, or a URI (including HTTP). Only local files are handled for now.
105 if (filePath.isEmpty()) {
106 qCDebug(gLogWakaTime) << "Nothing to send about";
107 return NothingToSend;
108 }
109 QStringList arguments;
110 const QFileInfo fileInfo(filePath);
111 // They have it sending the real file path, maybe not respecting symlinks, etc.
112 auto canonicalFilePath = fileInfo.canonicalFilePath();
113 qCDebug(gLogWakaTime) << "File path:" << canonicalFilePath;
114 // Compare date and make sure it has been at least 15 minutes.
115 const auto currentMs = QDateTime::currentMSecsSinceEpoch();
116 const auto deltaMs = currentMs - lastTimeSent.toMSecsSinceEpoch();
117 static const auto intervalMs = 120000; // ms
118 // If the current file has not changed and it has not been 2 minutes since the last heartbeat
119 // was sent, do NOT send this heartbeat. This does not apply to write events as they are always
120 // sent.
121 if (!isWrite) {
122 if (hasSent && deltaMs <= intervalMs && lastFileSent == canonicalFilePath) {
123 qCDebug(gLogWakaTime) << "Not enough time has passed since last send";
124 qCDebug(gLogWakaTime) << "Delta:" << deltaMs / 1000 / 60 << "/ 2 minutes";
125 return TooSoon;
126 }
127 }
128 arguments << QStringLiteral("--entity") << canonicalFilePath;
129 arguments << QStringLiteral("--plugin")
130 << QStringLiteral("ktexteditor-wakatime/%1").arg(VERSION);
131 auto projectName = getProjectDirectory(fileInfo);
132 if (!projectName.isEmpty()) {
133 arguments << QStringLiteral("--alternate-project") << projectName;
134 } else {
135 // LCOV_EXCL_START
136 qCDebug(gLogWakaTime) << "Warning: No project name found";
137 // LCOV_EXCL_STOP
138 }
139 if (isWrite) {
140 arguments << QStringLiteral("--write");
141 }
142 if (!mode.isEmpty()) {
143 arguments << QStringLiteral("--language") << mode;
144 }
145 arguments << QStringLiteral("--lineno") << QString::number(lineNumber);
146 arguments << QStringLiteral("--cursorpos") << QString::number(cursorPosition);
147 arguments << QStringLiteral("--lines-in-file") << QString::number(linesInFile);
148 qCDebug(gLogWakaTime) << "Running:" << wakatimeCliPath << arguments.join(QStringLiteral(" "));
149 auto ret = QProcess::execute(wakatimeCliPath, arguments);
150 if (ret != 0) {
151 qCWarning(gLogWakaTime) << "wakatime-cli returned error code" << ret;
152 return ErrorSending;
153 }
154 lastTimeSent = QDateTime::currentDateTime();
155 lastFileSent = canonicalFilePath;
156 hasSent = true;
157 return SentSuccessfully;
158}
QString getProjectDirectory(const QFileInfo &fileInfo)
Definition wakatime.cpp:47
@ SentSuccessfully
Definition wakatime.h:25
@ ErrorSending
Definition wakatime.h:23
@ TooSoon
Definition wakatime.h:26
@ NothingToSend
Definition wakatime.h:24
@ WakaTimeCliNotInPath
Definition wakatime.h:27
QString getBinPath(const QStringList &binNames)
Definition wakatime.cpp:16
WakaTime::State send(const QString &filePath, const QString &mode, int lineNumber, int cursorPosition, int linesInFile, bool isWrite)
Definition wakatime.cpp:72