• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PredatorCZ / PreCore / 460

pending completion
460

push

github-actions-ci

PredatorCZ
try fix coverage

3204 of 6095 relevant lines covered (52.57%)

354.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/cli/spike.cpp
1
/*  Spike is universal dedicated module handler
2
    This source contains code for CLI master app
3
    Part of PreCore project
4

5
    Copyright 2021-2022 Lukas Cone
6

7
    Licensed under the Apache License, Version 2.0 (the "License");
8
    you may not use this file except in compliance with the License.
9
    You may obtain a copy of the License at
10

11
        http://www.apache.org/licenses/LICENSE-2.0
12

13
    Unless required by applicable law or agreed to in writing, software
14
    distributed under the License is distributed on an "AS IS" BASIS,
15
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
    See the License for the specific language governing permissions and
17
    limitations under the License.
18
*/
19

20
#include "project.h"
21
#include "spike/app/batch.hpp"
22
#include "spike/app/console.hpp"
23
#include "spike/app/tmp_storage.hpp"
24
#include "spike/io/binwritter.hpp"
25
#include "spike/io/stat.hpp"
26
#include "spike/master_printer.hpp"
27
#include "spike/type/tchar.hpp"
28
#include "spike/util/pugiex.hpp"
29
#include <thread>
30

31
static const char appHeader0[] =
32
    "Simply drag'n'drop files/folders onto application or "
33
    "use as ";
34
static const char appHeader1[] =
35
    " [options] path1 path2 ...\nTool can detect and scan folders and "
36
    "uncompressed zip archives.";
37

38
struct ProcessedFiles : LoadingBar, CounterLine {
39
  char buffer[128]{};
40

41
  ProcessedFiles() : LoadingBar({buffer, sizeof(buffer)}) {}
×
42
  void PrintLine() override {
×
43
    snprintf(buffer, sizeof(buffer), "Processed %4" PRIuMAX " files.",
×
44
             curitem.load(std::memory_order_relaxed));
45
    LoadingBar::PrintLine();
×
46
  }
47
};
48

49
struct ExtractStats {
50
  std::map<JenHash, size_t> archiveFiles;
51
  size_t totalFiles = 0;
52
};
53

54
struct UILines {
55
  ProgressBar *totalProgress{nullptr};
56
  CounterLine *totalCount{nullptr};
57
  std::map<uint32, ProgressBar *> bars;
58
  std::mutex barsMutex;
59

60
  auto ChooseBar() {
×
61
    if (bars.empty()) {
×
62
      return (ProgressBar *)(nullptr);
63
    }
64

65
    auto threadId = std::this_thread::get_id();
66
    auto id = reinterpret_cast<const uint32 &>(threadId);
×
67
    auto found = bars.find(id);
68

69
    if (es::IsEnd(bars, found)) {
×
70
      std::lock_guard<std::mutex> lg(barsMutex);
×
71
      auto retVal = bars.begin()->second;
×
72
      bars.emplace(id, retVal);
×
73
      bars.erase(bars.begin());
74

75
      return retVal;
76
    }
77

78
    return found->second;
×
79
  };
80

81
  UILines(const ExtractStats &stats) {
×
82
    ModifyElements([&](ElementAPI &api) {
×
83
      const size_t minThreads =
84
          std::min(size_t(std::thread::hardware_concurrency()),
×
85
                   stats.archiveFiles.size());
×
86

87
      if (minThreads < 2) {
×
88
        return;
89
      }
90

91
      for (size_t t = 0; t < minThreads; t++) {
×
92
        auto progBar = std::make_unique<ProgressBar>("Thread:");
×
93
        auto progBarRaw = progBar.get();
×
94
        bars.emplace(t, progBarRaw);
×
95
        api.Append(std::move(progBar));
×
96
      }
97
    });
98

99
    auto prog = AppendNewLogLine<DetailedProgressBar>("Total: ");
×
100
    prog->ItemCount(stats.totalFiles);
×
101
    totalCount = prog;
×
102
  }
103

104
  UILines(size_t totalInputFiles) {
×
105
    totalCount = AppendNewLogLine<ProcessedFiles>();
×
106
    auto prog = AppendNewLogLine<DetailedProgressBar>("Total: ");
×
107
    prog->ItemCount(totalInputFiles);
×
108
    totalProgress = prog;
×
109
  }
110

111
  ~UILines() {
×
112
    ModifyElements([&](ElementAPI &api) {
×
113
      if (totalCount) {
×
114
        // Wait a little bit for internal queues to finish printing
115
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
×
116
        if (totalProgress) {
×
117
          auto data = static_cast<ProcessedFiles *>(totalCount);
×
118
          data->Finish();
119
          api.Release(data);
×
120
        } else {
121
          auto data = static_cast<DetailedProgressBar *>(totalCount);
×
122
          api.Remove(data);
×
123
        }
124
      }
125
      api.Clean();
×
126
      std::this_thread::sleep_for(std::chrono::milliseconds(100));
×
127
    });
×
128
  }
129
};
130

131
void ScanModules(const std::string &appFolder, const std::string &appName) {
×
132
  DirectoryScanner sc;
×
133
  sc.AddFilter(std::string_view(".spk$"));
×
134
  sc.Scan(appFolder);
×
135

136
  for (auto &m : sc) {
×
137
    try {
138
      AFileInfo modulePath(m);
×
139
      auto moduleName = modulePath.GetFilename();
×
140
      const size_t firstDotPos = moduleName.find_first_of('.');
141
      std::string moduleNameStr(moduleName.substr(0, firstDotPos));
×
142
      APPContext ctx(moduleNameStr.data(), appFolder, appName);
×
143
      ctx.FromConfig();
×
144
    } catch (const std::runtime_error &e) {
×
145
      printerror(e.what());
×
146
    }
×
147
  }
148
}
149

150
void GenerateDocumentation(const std::string &appFolder,
×
151
                           const std::string &appName,
152
                           const std::string &templatePath) {
153
  DirectoryScanner sc;
×
154
  sc.AddFilter(std::string_view(".spk$"));
×
155
  sc.Scan(appFolder);
×
156
  std::set<std::string> modules;
157
  pugi::xml_document doc;
×
158

159
  if (!templatePath.empty()) {
×
160
    doc = XMLFromFile(templatePath);
×
161
  }
162

163
  for (auto &m : sc) {
×
164
    try {
165
      AFileInfo modulePath(m);
×
166
      auto moduleName = modulePath.GetFilename();
×
167
      const size_t firstDotPos = moduleName.find_first_of('.');
168
      std::string moduleNameStr(moduleName.substr(0, firstDotPos));
×
169
      modules.emplace(moduleNameStr);
170
    } catch (const std::runtime_error &e) {
×
171
      printerror(e.what());
×
172
    }
×
173
  }
174

175
  BinWritter_t<BinCoreOpenMode::Text> wr(appFolder + "/README.md");
×
176
  const char *toolsetName = "[[TOOLSET NAME]]";
177

178
  if (auto child = doc.child("toolset_name"); child) {
×
179
    toolsetName = child.text().as_string();
×
180
  }
181

182
  const char *toolsetDescription = "[[TOOLSET DESCRIPTION]]";
183

184
  if (auto child = doc.child("toolset_description"); child) {
×
185
    toolsetDescription = child.text().as_string();
×
186
  }
187

188
  wr.BaseStream() << "# " << toolsetName << "\n\n"
189
                  << toolsetDescription << "\n\n";
×
190

191
  for (auto &m : modules) {
×
192
    pugi::xml_node node = doc.child(m.data());
×
193
    APPContext ctx(m.data(), appFolder, appName);
×
194
    ctx.GetMarkdownDoc(wr.BaseStream(), node);
×
195
  }
196

197
  if (auto child = doc.child("toolset_footer"); child) {
×
198
    wr.BaseStream() << child.text().as_string();
×
199
  }
200
}
201

202
void PackModeBatch(Batch &batch) {
×
203
  struct PackData {
204
    size_t index = 0;
205
    std::unique_ptr<AppPackContext> archiveContext;
206
    std::string pbarLabel;
207
    DetailedProgressBar *progBar = nullptr;
208
    std::string folderPath;
209
  };
210

211
  auto payload = std::make_shared<PackData>();
212

213
  batch.forEachFolder = [payload, ctx = batch.ctx](const std::string &path,
×
214
                                                   AppPackStats stats) {
×
215
    payload->folderPath = path;
×
216
    payload->archiveContext.reset(ctx->NewArchive(path, stats));
217
    payload->pbarLabel = "Folder id " + std::to_string(payload->index++);
×
218
    payload->progBar =
×
219
        AppendNewLogLine<DetailedProgressBar>(payload->pbarLabel);
×
220
    payload->progBar->ItemCount(stats.numFiles);
×
221
    uint8 consoleDetail = 1 | uint8(ctx->info->multithreaded) << 1;
×
222
    ConsolePrintDetail(consoleDetail);
×
223
    printline("Processing: " << path);
×
224
  };
225

226
  batch.forEachFile = [payload](AppContextShare *iCtx) {
×
227
    payload->archiveContext->SendFile(
×
228
        iCtx->workingFile.GetFullPath().substr(payload->folderPath.size() + 1),
×
229
        iCtx->GetStream());
×
230
    (*payload->progBar)++;
×
231
  };
232

233
  batch.forEachFolderFinish = [payload] {
×
234
    ConsolePrintDetail(1);
×
235
    payload->archiveContext->Finish();
×
236
    payload->archiveContext.reset();
237
    RemoveLogLines(payload->progBar);
×
238
  };
239
}
240

241
auto ExtractStatBatch(Batch &batch) {
×
242
  struct ExtractStatsMaker : ExtractStats {
243
    std::mutex mtx;
244
    LoadingBar *scanBar;
245

246
    void Push(AppContextShare *ctx, size_t numFiles) {
×
247
      std::unique_lock<std::mutex> lg(mtx);
×
248
      archiveFiles.emplace(ctx->Hash(), numFiles);
×
249
      totalFiles += numFiles;
×
250
    }
251

252
    ~ExtractStatsMaker() { RemoveLogLines(scanBar); }
×
253
  };
254

255
  batch.keepFinishLines = false;
×
256
  auto sharedData = std::make_shared<ExtractStatsMaker>();
257
  uint8 consoleDetail = 1 | uint8(batch.ctx->info->multithreaded) << 1;
×
258
  ConsolePrintDetail(consoleDetail);
×
259
  sharedData->scanBar =
×
260
      AppendNewLogLine<LoadingBar>("Processing extract stats.");
×
261

262
  batch.forEachFile = [payload = sharedData,
×
263
                       ctx = batch.ctx](AppContextShare *iCtx) {
×
264
    payload->Push(iCtx, ctx->ExtractStat(std::bind(
×
265
                            [&](size_t offset, size_t size) {
266
                              return iCtx->GetBuffer(size, offset);
×
267
                            },
268
                            std::placeholders::_1, std::placeholders::_2)));
269
  };
270

271
  return sharedData;
×
272
}
273

274
void ProcessBatch(Batch &batch, ExtractStats *stats) {
×
275
  uint8 consoleDetail = 1 | uint8(batch.ctx->info->multithreaded) << 1;
×
276
  ConsolePrintDetail(consoleDetail);
×
277
  batch.forEachFile = [payload = std::make_shared<UILines>(*stats),
×
278
                       archiveFiles =
279
                           std::make_shared<decltype(stats->archiveFiles)>(
280
                               std::move(stats->archiveFiles)),
×
281
                       ctx = batch.ctx](AppContextShare *iCtx) {
×
282
    auto currentBar = payload->ChooseBar();
×
283
    if (currentBar) {
×
284
      currentBar->ItemCount(archiveFiles->at(iCtx->Hash()));
×
285
    }
286

287
    iCtx->forEachFile = [=] {
×
288
      if (currentBar) {
×
289
        (*currentBar)++;
290
      }
291

292
      if (payload->totalCount) {
×
293
        (*payload->totalCount)++;
294
      }
295
    };
296

297
    printline("Processing: " << iCtx->FullPath());
×
298
    ctx->ProcessFile(iCtx);
299
    if (payload->totalProgress) {
×
300
      (*payload->totalProgress)++;
301
    }
302
  };
303
}
304

305
void ProcessBatch(Batch &batch, size_t numFiles) {
×
306
  uint8 consoleDetail = 1 | uint8(batch.ctx->info->multithreaded) << 1;
×
307
  ConsolePrintDetail(consoleDetail);
×
308
  auto payload = std::make_shared<UILines>(numFiles);
309
  batch.forEachFile = [payload = payload,
×
310
                       ctx = batch.ctx](AppContextShare *iCtx) {
×
311
    printline("Processing: " << iCtx->FullPath());
×
312
    ctx->ProcessFile(iCtx);
313
    if (payload->totalProgress) {
×
314
      (*payload->totalProgress)++;
315
    }
316
    if (payload->totalCount) {
×
317
      (*payload->totalCount)++;
318
    }
319
  };
320

321
  auto totalFiles = std::make_shared<size_t>(numFiles);
×
322
  batch.updateFileCount = [payload = payload,
×
323
                           totalFiles = totalFiles](size_t addedFiles) {
×
324
    *totalFiles.get() += addedFiles;
×
325
    payload->totalProgress->ItemCount(*totalFiles);
×
326
  };
327
}
328

329
int Main(int argc, TCHAR *argv[]) {
×
330
  ConsolePrintDetail(1);
×
331
  AFileInfo appLocation(std::to_string(*argv));
×
332
  std::string appFolder(appLocation.GetFolder());
×
333
  std::string appName(appLocation.GetFilename());
×
334

335
  if (argc < 2) {
×
336
    printwarning(
×
337
        "No parameters provided, entering scan mode and generating config.");
338
    ScanModules(appFolder, appName);
×
339
    return 0;
340
  }
341

342
  auto moduleName = std::to_string(argv[1]);
×
343

344
  if (moduleName == "--make-doc") {
×
345
    std::string templatePath;
346

347
    if (argc < 3) {
×
348
      printwarning("Expexted template path!");
×
349
    } else {
350
      templatePath = std::to_string(argv[2]);
×
351
    }
352

353
    GenerateDocumentation(appFolder, appName, templatePath);
×
354
    return 0;
355
  }
356

357
  if (argc < 3) {
×
358
    printerror("Insufficient argument count, expected parameters.");
×
359
    return 1;
360
  }
361

362
  APPContext ctx;
363

364
  try {
365
    ctx = APPContext(moduleName.data(), appFolder, appName);
×
366
  } catch (const std::exception &e) {
×
367
    printerror(e.what());
×
368
    return 2;
369
  }
×
370

371
  ConsolePrintDetail(0);
×
372

373
  printline(ctx.info->header);
×
374
  printline(appHeader0 << appLocation.GetFilename() << ' ' << moduleName
×
375
                       << appHeader1);
376

377
  if (IsHelp(argv[2])) {
×
378
    ctx.PrintCLIHelp();
×
379
    return 0;
380
  }
381

382
  ConsolePrintDetail(1);
×
383
  bool dontLoadConfig = false;
×
384
  std::vector<bool> markedFiles(size_t(argc), false);
×
385
  size_t totalFiles = 0;
386

387
  // Handle cli options and switches
388
  for (int a = 2; a < argc; a++) {
×
389
    auto opt = argv[a];
×
390

391
    if (opt[0] == '-') {
×
392
      auto optStr = std::to_string(opt);
393
      std::string_view optsw(optStr);
394

395
      if (optsw != "--out") {
×
396
        // We won't use config file, reset all booleans to false,
397
        // so we can properly use cli switches
398
        [&] {
×
399
          if (dontLoadConfig) {
×
400
            return;
401
          }
402

403
          printinfo("CLI option detected, config won't be loaded, all booleans "
×
404
                    "set to false!");
405
          ctx.ResetSwitchSettings();
×
406
        }();
×
407
        dontLoadConfig = true;
×
408
      }
409
      optsw.remove_prefix(1);
410

411
      if (opt[0] == '-') {
×
412
        optsw.remove_prefix(1);
413
      }
414

415
      auto valStr = std::to_string(argv[a + 1]);
×
416

417
      if (auto retVal = ctx.ApplySetting(optsw, valStr); retVal > 0) {
×
418
        a++;
×
419
      }
420

421
    } else {
422
      markedFiles[a] = true;
423
      totalFiles++;
×
424
    }
425
  }
426

427
  if (!dontLoadConfig) {
×
428
    printinfo("Loading config: " << appName << ".config");
×
429
    ctx.FromConfig();
×
430
  }
431

432
  InitTempStorage();
×
433
  ctx.SetupModule();
×
434
  {
435
    Batch batch(&ctx, ctx.info->multithreaded * 50);
×
436

437
    if (ctx.NewArchive) {
×
438
      PackModeBatch(batch);
×
439
    } else {
440
      if (ctx.ExtractStat) {
×
441
        auto stats = ExtractStatBatch(batch);
×
442
        for (int a = 2; a < argc; a++) {
×
443
          if (!markedFiles.at(a)) {
×
444
            continue;
×
445
          }
446
          batch.AddFile(std::to_string(argv[a]));
×
447
        }
448

449
        batch.FinishBatch();
×
450
        batch.Clean();
×
451
        stats.get()->totalFiles += totalFiles;
×
452
        ProcessBatch(batch, stats.get());
×
453
      } else {
454
        ProcessBatch(batch, totalFiles);
×
455
      }
456
    }
457

458
    for (int a = 2; a < argc; a++) {
×
459
      if (!markedFiles.at(a)) {
×
460
        continue;
×
461
      }
462
      batch.AddFile(std::to_string(argv[a]));
×
463
    }
464

465
    batch.FinishBatch();
×
466
  }
467

468
  if (ctx.FinishContext) {
×
469
    ctx.FinishContext();
470
  }
471

472
  return 0;
473
}
474

475
int _tmain(int argc, TCHAR *argv[]) {
×
476
  es::SetupWinApiConsole();
477
  InitConsole();
×
478
  CleanTempStorages();
×
479

480
  int retVal = Main(argc, argv);
×
481

482
  CleanCurrentTempStorage();
×
483

484
#ifndef NDEBUG
485
  auto cacheStats = CacheGenerator::GlobalMetrics();
486
  PrintInfo("Cache search hits: ", cacheStats.numSearchHits,
487
            " search misses: ", cacheStats.numSearchMisses);
488
  std::this_thread::sleep_for(std::chrono::milliseconds(100));
489
#endif
490
  TerminateConsole();
×
491
  return retVal;
492
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc