Loading...
Searching...
No Matches
ExecutionHelpers.hpp
1#pragma once
2#include <JS/Qml/QmlObjects.hpp>
3#include <Library/LibrarySettings.hpp>
4#include <score/application/ApplicationContext.hpp>
5#include <score/application/GUIApplicationContext.hpp>
6
7#include <ossia/detail/logger.hpp>
8#include <ossia-qt/invoke.hpp>
9#include <ossia-qt/qml_engine_functions.hpp>
10#include <JS/ConsolePanel.hpp>
11
12#include <QDir>
13#include <QQmlComponent>
14#include <QQmlEngine>
15#include <QStandardPaths>
16#include <QUrl>
17
18#if __has_include(<boost/hash2/xxh3.hpp>)
19#include <boost/hash2/xxh3.hpp>
20#include <boost/algorithm/hex.hpp>
21#endif
22
23namespace JS
24{
25
26static inline QString hashFileData(const QByteArray& str)
27{
28 QString hexName;
29#if __has_include(<boost/hash2/xxh3.hpp>)
30 boost::hash2::xxh3_128 hasher;
31 hasher.update(str.constData(), str.size());
32 const auto result = hasher.result();
33 std::string hexString;
34 boost::algorithm::hex(result.begin(), result.end(), std::back_inserter(hexString));
35
36 hexName.reserve(32);
37 hexName.push_back("-");
38 hexName.append(hexString.data());
39#endif
40 return hexName;
41}
42#include <QDir>
43#include <QFile>
44#include <QFileInfo>
45#include <QString>
46
47inline bool copyDirectoryRecursively(const QString& sourcePath, const QString& destPath)
48{
49 QDir sourceDir(sourcePath);
50 if(!sourceDir.exists())
51 {
52 return false;
53 }
54
55 QDir destDir(destPath);
56 // Create the destination directory if it doesn't exist
57 if(!destDir.exists() && !destDir.mkpath("."))
58 {
59 return false;
60 }
61
62 bool success = true;
63
64 // Get all files and directories, including hidden and system files, excluding "." and ".."
65 const QFileInfoList entries = sourceDir.entryInfoList(
66 QDir::NoDotAndDotDot | QDir::AllEntries | QDir::Hidden | QDir::System);
67
68 for(const QFileInfo& entryInfo : entries)
69 {
70 QString newDestPath = destDir.absoluteFilePath(entryInfo.fileName());
71
72 if(entryInfo.isDir())
73 {
74 // Recursively copy subdirectories
75 if(!copyDirectoryRecursively(entryInfo.absoluteFilePath(), newDestPath))
76 {
77 success = false;
78 }
79 }
80 else
81 {
82 // Overwrite existing files at the destination
83 if(QFile::exists(newDestPath))
84 {
85 QFile::remove(newDestPath);
86 }
87 // Copy the file
88 if(!QFile::copy(entryInfo.absoluteFilePath(), newDestPath))
89 {
90 success = false;
91 }
92 }
93 }
94
95 return success;
96}
97
98inline bool copyParentFolderContents(const QString& rootPath, const QString& dst)
99{
100 QFileInfo fileInfo(rootPath);
101
102 QString parentFolder = fileInfo.absolutePath();
103
104 return copyDirectoryRecursively(parentFolder, dst);
105}
106
107// Write str to a cache file on disk so that Qt's QML compilation cache can be used.
108// Returns the cache file path on success, empty string on failure.
109inline QString ensureJSCacheFile(const QByteArray& str, bool is_ui)
110{
111#if __has_include(<boost/hash2/xxh3.hpp>)
112 static const auto cache_path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
113 QString path = cache_path + "/Script" + hashFileData(str) + (is_ui ? ".ui.qml" : ".qml");
114 QFile f{path};
115 if(f.open(QIODevice::ReadWrite))
116 {
117 if(str != f.readAll())
118 {
119 f.resize(0);
120 f.reset();
121 f.write(str);
122 f.flush();
123 }
124 f.close();
125 return path;
126 }
127#endif
128 return {};
129}
130
131inline void loadJSObjectFromString(
132 const QString& rootPath, const QByteArray& str, QQmlComponent& comp, bool is_ui)
133{
134 auto path = ensureJSCacheFile(str, is_ui);
135 if(!path.isEmpty())
136 {
137 static const auto cache_path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
138 copyParentFolderContents(rootPath, cache_path);
139
140 comp.loadUrl(QUrl::fromLocalFile(path));
141 }
142 else
143 {
144 comp.setData(str, QUrl::fromLocalFile(rootPath));
145 }
146}
147
148inline JS::Script* createJSObject(QQmlComponent& c, QQmlContext* context)
149{
150 const auto& errs = c.errors();
151 if(!errs.empty())
152 {
153 ossia::logger().error(
154 "Uncaught exception at line {} : {}", errs[0].line(),
155 errs[0].toString().toStdString());
156 return nullptr;
157 }
158 else
159 {
160 auto object = c.create(context);
161 auto obj = qobject_cast<JS::Script*>(object);
162 if(obj)
163 return obj;
164 delete object;
165 return nullptr;
166 }
167}
168
169inline JS::Script* createJSObject(
170 const QString& rootPath, const QString& val, QQmlEngine* engine,
171 QQmlContext* context)
172{
173 if(val.trimmed().startsWith("import"))
174 {
175 QQmlComponent c{engine};
176 loadJSObjectFromString(rootPath, val.toUtf8(), c, false);
177 return createJSObject(c, context);
178 }
179 else if(QFile::exists(val))
180 {
181 QQmlComponent c{engine, QUrl::fromLocalFile(val)};
182 return createJSObject(c, context);
183 }
184 return nullptr;
185}
186
187inline void setupExecFuncs(auto* self, QObject* context, ossia::qt::qml_engine_functions* m_execFuncs)
188{
189 QObject::connect(
190 m_execFuncs, &ossia::qt::qml_engine_functions::system, qApp,
191 [](const QString& code) {
192 std::thread{[code] { ::system(code.toStdString().c_str()); }}.detach();
193 }, Qt::QueuedConnection);
194
195 if(auto* js_panel = score::GUIAppContext().findPanel<JS::PanelDelegate>())
196 {
197 QObject::connect(
198 m_execFuncs, &ossia::qt::qml_engine_functions::exec, js_panel,
199 &JS::PanelDelegate::evaluate, Qt::QueuedConnection);
200
201 QObject::connect(
202 m_execFuncs, &ossia::qt::qml_engine_functions::compute, m_execFuncs,
203 [self, context, m_execFuncs, js_panel](const QString& code, const QString& cbname) {
204 // Exec thread
205
206 // Callback ran in UI thread
207 auto cb = [self
208 , context=QPointer{context}
209 , cur = QPointer{self->m_object}
210 , m_execFuncs
211 , cbname] (const QVariant& v) {
212 if(!self)
213 return;
214
215 // Go back to exec thread, we have to go through the normal engine exec ctx
216 ossia::qt::run_async(m_execFuncs, [self, context, cur, v, cbname] {
217 if(!context || !cur)
218 return;
219 if(self->m_object != cur)
220 return;
221
222 auto mo = self->m_object->metaObject();
223 for(int i = 0; i < mo->methodCount(); i++)
224 {
225 if(mo->method(i).name() == cbname)
226 {
227 mo->method(i).invoke(
228 self->m_object, Qt::DirectConnection, QGenericReturnArgument(),
229 QArgument<QVariant>{"v", v});
230 }
231 }
232 });
233 };
234
235 // Go to ui thread
236 ossia::qt::run_async(js_panel, [js_panel, code, cb]() {
237 js_panel->compute(code, cb); // This invokes cb
238 });
239 }, Qt::DirectConnection);
240 }
241}
242}
Definition ConsolePanel.hpp:40
Definition QmlObjects.hpp:845