13#include <QElapsedTimer>
16#include <QRegularExpression>
17#include <QStringTokenizer>
24 static constexpr int kThreadTimeoutMs = 5000;
26 QElapsedTimer elapsed;
29 if (w.thread && w.thread->isRunning()) {
31 kThreadTimeoutMs -
static_cast<int>(elapsed.elapsed());
33 w.thread->wait(remaining);
42 const QString &gpgExe,
const QString &filePath,
43 const QRegularExpression &rx,
44 const std::atomic_bool *
cancel) -> QStringList {
45 const QString translatedPath =
52 gpg.setProcessEnvironment(env);
55 QStringList{
"-d",
"--quiet",
"--yes",
"--no-encrypt-to",
"--batch",
58 QString(), &plaintext,
nullptr,
cancel);
59 if (rc != 0 || plaintext.isEmpty())
64 for (QStringView line : QStringView(plaintext).tokenize(u
'\n')) {
65 if (line.endsWith(u
'\r'))
67 const QStringView t = line.trimmed();
70 const QRegularExpressionMatch m = rx.matchView(line);
78 matches << t.toString();
84 qMax<qsizetype>(0, m.capturedStart() - (t.data() - line.data()));
90 if (start > 0 && t[start].isLowSurrogate())
92 if (end < t.size() && t[end - 1].isHighSurrogate())
94 QString preview = t.sliced(start, end - start).toString();
96 preview.prepend(QChar(0x2026));
98 preview.append(QChar(0x2026));
108 const QString &gpgExe,
const QString &storeDir,
109 const QRegularExpression &rx,
110 const std::atomic_bool *
cancel)
111 -> QList<QPair<QString, QStringList>> {
112 QList<QPair<QString, QStringList>> results;
116 const QStringList files =
118 for (
const QString &filePath : files) {
119 if (QThread::currentThread()->isInterruptionRequested() ||
124 results.append({QString(), {}});
128 if (matches.isEmpty())
132 matches.removeLast();
136 if (matches.size() > room) {
137 matches = matches.mid(0, room);
138 matches << QString();
140 matches << QString();
142 total +=
static_cast<int>(matches.size()) -
144 QString entry = QDir(storeDir).relativeFilePath(filePath);
145 if (entry.endsWith(QLatin1String(
".gpg")))
147 results.append({entry, matches});
161 const QString &gpgExe,
const QString &storeDir,
162 const QProcessEnvironment &env) {
167 const int seq = ++
m_seq;
176 if (pattern.trimmed().isEmpty()) {
177 QMetaObject::invokeMethod(
183 Qt::QueuedConnection);
187 const QRegularExpression rx(
188 pattern, caseInsensitive ? QRegularExpression::CaseInsensitiveOption
189 : QRegularExpression::PatternOptions{});
191 QMetaObject::invokeMethod(
197 Qt::QueuedConnection);
200 QPointer<NativeGrep> self(
this);
202 auto emitResults = [self, seq](QList<QPair<QString, QStringList>> results) {
205 QMetaObject::invokeMethod(
207 [self, seq, results = std::move(results)]() {
208 if (self && self->m_seq == seq)
209 emit self->finished(results);
211 Qt::QueuedConnection);
214 auto cancelFlag = std::make_shared<std::atomic_bool>(
false);
215 QThread *thread = QThread::create([gpgExe, storeDir, env, rx, cancelFlag,
216 emitResults = std::move(emitResults)]() {
217 std::move(emitResults)(
218 scanStore(env, gpgExe, storeDir, rx, cancelFlag.get()));
222 connect(thread, &QThread::finished, thread, &QObject::deleteLater);
223 connect(thread, &QThread::finished,
this, [
this, thread]() {
225 [thread](
const Worker &w) {
return w.
thread == thread; });
232 w.cancel->store(
true);
233 if (w.thread && w.thread->isRunning())
234 w.thread->requestInterruption();
static auto executeBlocking(const QString &app, const QStringList &args, const QString &input=QString(), QString *process_out=nullptr, QString *process_err=nullptr) -> int
Run a command synchronously and return its exit code.
static auto translatePathForWsl(const QString &path, const QString &exe) -> QString
Translate a native path for a WSL-routed executable.
void finished(const QList< QPair< QString, QStringList > > &results)
The newest search is over.
void search(const QString &pattern, bool caseInsensitive, const QString &gpgExe, const QString &storeDir, const QProcessEnvironment &env)
Start a search; finished() follows asynchronously, also for an empty or invalid pattern (with no resu...
~NativeGrep() override
Interrupt running searches and wait up to five seconds for them.
static constexpr int kMaxMatches
Most matching lines a search collects over the whole store.
static auto isMoreMarker(const QString &line) -> bool
Whether a line in a result is the marker for "more matches inthis entry" rather than a match.
static constexpr qsizetype kMaxPreviewLength
Longest preview of a matching line, in UTF-16 code units.
static constexpr int kMaxMatchesPerEntry
Most matching lines taken from one entry.
static auto matchFile(const QProcessEnvironment &env, const QString &gpgExe, const QString &filePath, const QRegularExpression &rx, const std::atomic_bool *cancel=nullptr) -> QStringList
Decrypt one .gpg file and return its trimmed lines matching rx.
static auto scanStore(const QProcessEnvironment &env, const QString &gpgExe, const QString &storeDir, const QRegularExpression &rx, const std::atomic_bool *cancel=nullptr) -> QList< QPair< QString, QStringList > >
Walk storeDir, decrypt every .gpg file and collect the matches.
int m_seq
Number of the newest search. A worker reports through finished() only when its own number still equal...
QList< Worker > m_workers
Searches still running, superseded ones included.
void cancel()
Ask running searches to stop without waiting for them: the walk ends at the next file and a gpg that ...
NativeGrep(QObject *parent=nullptr)
Create a searcher.
static auto maxOutputArgs() -> QStringList
The gpg options that enforce kMaxPlaintextBytes.
static auto regularFilesUnder(const QString &dir, const QStringList &nameFilters, QStringList *skipped=nullptr, bool hiddenFiles=false) -> QStringList
Regular files under dir whose name matches one of nameFilters, found by walking only real directories...
Executor, the FIFO queue every gpg, git and pass child process of QtPass goes through,...
NativeGrep, the content search that decrypts every entry on a worker thread instead of running pass g...
Pass, the abstract password-store backend that RealPass and ImitatePass implement,...
QThread * thread
The thread the search runs on.
Util: static helpers for the process environment, binary discovery, store paths, URL safety and the l...