1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "remotechecker.h"
6#include "common/common.h"
7
8#include <QDirIterator>
9#include <QMimeDatabase>
10#include <QDebug>
11
12using FO = FileOperation;
13
14void RemoteChecker::doCheckClangd(const QString &language)
15{
16 if (checkClangdFlag)
17 return;
18
19 checkClangdFlag = true;
20
21 QString user = "deepin";
22 QString origin = "clangd-archive";
23 QString branch = "dev";
24 QString rawPrefix = "https://raw.githubusercontent.com";
25 QString platformSupportFileName = "platform.support";
26 QString clangdSha256FileName = "clangd.sha256";
27 QString clangdFileName = "clangd";
28
29 QUrl remotePlatformSupportUrl(rawPrefix + "/" + user + "/" + origin + "/"
30 + branch + "/" + platformSupportFileName);
31 QString currentPlatform = ProcessUtil::localPlatform();
32 // get is support platform
33 QStringList platformSupports;
34 for (auto one : getRemoteFile(remotePlatformSupportUrl).split("\n")) {
35 if (!one.isEmpty()) {
36 platformSupports.append(one);
37 }
38 }
39 if (!platformSupports.contains(currentPlatform)) {
40 lspServErr << "get remote platform support error"
41 << ", remote:" << platformSupports
42 << ", local:" << currentPlatform;
43 return;
44 }
45
46 QUrl remoteClangdShasum256Url(rawPrefix + "/" + user + "/" + origin + "/" + branch
47 + "/" + currentPlatform + "/" + clangdSha256FileName);
48 // local lsp shasum256 file
49 QString localClangdShasum256Path = CustomPaths::lspRuntimePath(language) + QDir::separator() + clangdSha256FileName;
50 if (!FO::exists(localClangdShasum256Path)) { // not local shasum256 file
51 // save remote shasum256
52 saveRemoteFile(remoteClangdShasum256Url, localClangdShasum256Path);
53 } else { // exist shasum256 file
54 QString remoteClangdShasum256Data = getRemoteFile(remoteClangdShasum256Url);
55 if (!remoteClangdShasum256Data.isEmpty()) {
56 if (FO::readAll(localClangdShasum256Path) != remoteClangdShasum256Data) {
57 // save remote shasum256
58 saveRemoteFile(remoteClangdShasum256Url, localClangdShasum256Path);
59 }
60 }
61 }
62
63 // local lsp cxx backend program
64 QString localClangdPath = CustomPaths::lspRuntimePath(language) + QDir::separator() + clangdFileName;
65 bool localClangdKeep = true;
66 if (FO::exists(localClangdPath)) {
67 if (FO::exists(localClangdShasum256Path)) {
68 localClangdKeep = checkShasum(localClangdPath, FO::readAll(localClangdShasum256Path), "256");
69 } else { // not exists clangd program
70 localClangdKeep = false;
71 }
72 } else {
73 localClangdKeep = false;
74 }
75
76 if (!localClangdKeep) {
77 FO::doRemove(localClangdPath);
78 QString downloadPrefix = "https://github.com";
79 QUrl remoteClangdUrl(downloadPrefix + "/" + user + "/" + origin
80 + "/raw/dev/" + currentPlatform + "/" + clangdFileName);
81 QStringList args = { remoteClangdUrl.toEncoded(), "-O", clangdFileName };
82 WGetDialog dialog;
83 dialog.setWorkingDirectory(CustomPaths::lspRuntimePath(language));
84 dialog.setWgetArguments(args);
85 dialog.exec();
86 QFile::Permissions permission = QFile::Permission::ReadUser
87 | QFile::Permission::WriteUser
88 | QFile::Permission::ExeUser;
89 QFile(localClangdPath).setPermissions(permission);
90 }
91}
92
93void RemoteChecker::doCheckJdtls(const QString &language)
94{
95 if (checkJdtlsFlag)
96 return;
97
98 checkJdtlsFlag = true;
99
100 QUrl remoteJdtlsUrl("https://download.eclipse.org/jdtls/snapshots/jdt-language-server-1.11.0-202205051421.tar.gz");
101 QString localJdtlsPath = CustomPaths::lspRuntimePath(language) + QDir::separator() + "jdt-language-server.tar.gz";
102 QUrl remoteJdtlsShasum256Url("https://download.eclipse.org/jdtls/snapshots/jdt-language-server-1.11.0-202205051421.tar.gz.sha256");
103 QString localJdtlsShasum256Path = CustomPaths::lspRuntimePath(language) + QDir::separator() + "jdt-language-server.tar.gz.sha256";
104
105 // local lsp shasum256 file
106 if (!FO::exists(localJdtlsShasum256Path)) { // not local shasum256 file
107 // save remote shasum256
108 saveRemoteFile(remoteJdtlsShasum256Url, localJdtlsShasum256Path);
109 } else { // exist shasum256 file
110 QString remoteClangdShasum256Data = getRemoteFile(remoteJdtlsShasum256Url);
111 if (!remoteClangdShasum256Data.isEmpty()) {
112 if (FO::readAll(localJdtlsShasum256Path) != remoteClangdShasum256Data) {
113 // save remote shasum256
114 saveRemoteFile(remoteJdtlsShasum256Url, localJdtlsShasum256Path);
115 }
116 }
117 }
118
119 // local lsp cxx backend program
120 bool localKeep = true;
121 if (FO::exists(localJdtlsPath)) {
122 if (FO::exists(localJdtlsShasum256Path)) {
123 localKeep = checkShasum(localJdtlsPath, FO::readAll(localJdtlsShasum256Path), "256");
124 } else { // not exists clangd program
125 localKeep = false;
126 }
127 } else {
128 localKeep = false;
129 }
130
131 if (!localKeep) {
132 FO::doRemove(localJdtlsPath);
133
134 QStringList args = { remoteJdtlsUrl.toEncoded(), "-O", localJdtlsPath };
135 WGetDialog dialog;
136 dialog.setWorkingDirectory(CustomPaths::lspRuntimePath(language));
137 dialog.setWgetArguments(args);
138 dialog.exec();
139
140 ProcessDialog processDialog;
141 processDialog.setWorkingDirectory(CustomPaths::lspRuntimePath(language));
142 processDialog.setProgram("tar");
143 processDialog.setArguments({"zxvf", localJdtlsPath, "-C", "."});
144 processDialog.exec();
145 }
146}
147
148void RemoteChecker::doCheckPyls(const QString &language)
149{
150 Q_UNUSED(language)
151
152 if (checkPylsFlag)
153 return;
154
155 checkPylsFlag = true;
156
157 // virtualenv not support, use user env
158 QString pip3PackageName {"python-language-server[all]"};
159 QString executeProgram {"pyls"};
160
161 QString userLocalBinPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation)
162 + QDir::separator() + ".local" + QDir::separator() + "bin";
163 if (!QFileInfo(userLocalBinPath + QDir::separator() + executeProgram).exists()) {
164 Pip3Dialog pip3Dialog;
165 pip3Dialog.install("python-language-server[all]");
166 pip3Dialog.exec();
167 }
168}
169
170void RemoteChecker::checkJSServer(const QString &checkPath)
171{
172 if (checkJSServerFlag)
173 return;
174
175 checkJSServerFlag = true;
176
177 QString workingDirectory = checkPath;
178 QString nodePath = workingDirectory + "/node_modules/node/bin/node";
179 if (QFileInfo::exists(nodePath))
180 return;
181
182 QProcess process;
183 process.setWorkingDirectory(workingDirectory);
184
185 auto runCommand = [&](const QString &program, const QStringList &args)
186 {
187 process.start(program, args);
188 process.waitForFinished();
189 QString error = process.readAllStandardError();
190 if (!error.isEmpty())
191 qCritical() << QString("run %1 error:").arg(program) << error;
192 };
193
194 runCommand("npm", {"install", "n"});
195 runCommand("npm", {"install", "typescript", "typescript-language-server"});
196
197 process.setWorkingDirectory(workingDirectory + "/node_modules/.bin");
198 process.setEnvironment({QString("N_PREFIX=%1/node_modules/node").arg(workingDirectory)});
199 runCommand("./n", {"stable"});
200}
201
202RemoteChecker::RemoteChecker()
203{
204
205}
206
207RemoteChecker &RemoteChecker::instance()
208{
209 static RemoteChecker ins;
210 return ins;
211}
212
213void RemoteChecker::checkLanguageBackend(const QString &language)
214{
215 if (language == "C/C++") {
216 doCheckClangd(language);
217 } else if (language == "Java") {
218 doCheckJdtls(language);
219 } else if (language == "Python") {
220 doCheckPyls(language);
221 }
222}
223
224bool RemoteChecker::checkShasum(const QString &filePath, const QString &src_code, const QString &mode)
225{
226 QProcess checkProcess;
227 checkProcess.setProgram("shasum");
228 checkProcess.setArguments({"-a", mode, filePath});
229 checkProcess.start();
230 checkProcess.waitForFinished();
231
232 QString output = checkProcess.readAll();
233 QStringList result = output.split(" ");
234 if (result.size() >= 2) {
235 output = result.first();
236 }
237 return src_code == output;
238}
239
240QString RemoteChecker::getRemoteFile(const QUrl &url)
241{
242 QString ret;
243 while (ret.isEmpty()) {
244 QProcess curlProc;
245 curlProc.setProgram("curl");
246 curlProc.setArguments({url.toEncoded()});
247 curlProc.start();
248 lspServOut << curlProc.program().toStdString();
249 for (auto args : curlProc.arguments()) {
250 lspServOut << "," << args.toStdString();
251 }
252 curlProc.waitForFinished(1500);
253
254 if (curlProc.exitCode() == 0)
255 ret = curlProc.readAll();
256 }
257
258 if (ret.endsWith("\n"))
259 ret.remove(ret.size() - 1, 1);
260 return ret;
261}
262
263bool RemoteChecker::saveRemoteFile(const QUrl &url, const QString &saveFilePath)
264{
265 QString data = getRemoteFile(url);
266 if (data.isEmpty()) {
267 return false;
268 }
269
270 QFile file(saveFilePath);
271 if (!file.open(QFile::WriteOnly | QFile::Truncate)) {
272 return false;
273 }
274 file.write(data.toLatin1());
275 file.close();
276 return true;
277}
278
279