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

ossia / score / 31975605394

16 Aug 2026 10:10PM UTC coverage: 20.35%. First build
31975605394

Pull #2211

github

web-flow
Merge 1defa584c into bb5e533af
Pull Request #2211: project: collect, relink, trim and archive a project's files

1182 of 1996 new or added lines in 43 files covered. (59.22%)

43464 of 213584 relevant lines covered (20.35%)

5552.46 hits per line

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

53.03
/src/plugins/score-lib-process/Process/ExternalFiles.cpp
1
#include <Process/Commands/SetControlValue.hpp>
2
#include <Process/Dataflow/WidgetInlets.hpp>
3
#include <Process/ExternalFiles.hpp>
4
#include <Process/Process.hpp>
5

6
#include <Device/Protocol/DeviceInterface.hpp>
7
#include <Device/Protocol/ProtocolFactoryInterface.hpp>
8
#include <Device/Protocol/ProtocolList.hpp>
9
#include <Explorer/Commands/Update/UpdateDeviceSettings.hpp>
10
#include <Explorer/DocumentPlugin/DeviceDocumentPlugin.hpp>
11

12
#include <score/application/GUIApplicationContext.hpp>
13
#include <score/document/DocumentContext.hpp>
14
#include <score/model/EntityMap.hpp>
15
#include <score/tools/File.hpp>
16

17
#include <QFileInfo>
18

19
#include <core/document/Document.hpp>
20
#include <core/document/DocumentModel.hpp>
21

22
#include <ossia/network/value/value_conversion.hpp>
23

24
namespace Process
25
{
26

27
ExternalFileMap::ExternalFileMap(ExternalFileMapper mapper) noexcept
150✔
28
    : m_mapper{std::move(mapper)}
75✔
29
{
30
}
75✔
31

32
ExternalFileMap::~ExternalFileMap()
75✔
33
{
34
  // Commands nobody claimed would otherwise leak: the caller either takes
35
  // them (and pushes them on the stack) or the whole run was abandoned.
36
  for(auto* cmd : m_commands)
75✔
NEW
37
    delete cmd;
×
38
}
75✔
39

40
QString ExternalFileMap::map(ExternalFileRef ref)
95✔
41
{
42
  if(ref.path.isEmpty())
95✔
NEW
43
    return {};
×
44

45
  if(ref.owner.isEmpty())
95✔
NEW
46
    ref.owner = owner;
×
47
  if(ref.kind == score::FileKind::Unknown && !ref.directory)
95✔
48
    ref.kind = score::guessFileKind(ref.path);
6✔
49

50
  if(!m_mapper || !ref.rewritable)
95✔
NEW
51
    return {};
×
52

53
  QString next = m_mapper(ref);
95✔
54
  if(next == ref.path)
95✔
NEW
55
    return {};
×
56
  return next;
95✔
57
}
95✔
58

59
void ExternalFileMap::addCommand(score::Command* cmd)
38✔
60
{
61
  if(cmd)
38✔
62
    m_commands.push_back(cmd);
38✔
63
}
38✔
64

65
std::vector<score::Command*> ExternalFileMap::takeCommands() noexcept
30✔
66
{
67
  return std::move(m_commands);
30✔
68
}
69

NEW
70
void ExternalFileMap::readOnly(const QString& path, score::FileKind kind)
×
71
{
NEW
72
  map({.path = path,
×
NEW
73
       .kind = kind,
×
74
       .usage = FileUsage::Input,
75
       .directory = false,
76
       .rewritable = false,
NEW
77
       .owner = owner});
×
NEW
78
}
×
79

80
//! Rewrite a single path held in an ossia::value, in place.
81
static bool
82
mapStringValue(ExternalFileMap& self, ossia::value& v, const ExternalFileRef& proto)
6✔
83
{
84
  auto str = v.target<std::string>();
6✔
85
  if(!str)
6✔
NEW
86
    return false;
×
87

88
  const QString cur = QString::fromStdString(*str).trimmed();
6✔
89
  if(cur.isEmpty())
6✔
NEW
90
    return false;
×
91

92
  ExternalFileRef ref = proto;
6✔
93
  ref.path = cur;
6✔
94
  const QString next = self.map(std::move(ref));
6✔
95
  if(next.isEmpty())
6✔
96
    return false;
4✔
97

98
  v = ossia::value{next.toStdString()};
2✔
99
  return true;
2✔
100
}
6✔
101

102
void ExternalFileMap::control(
6✔
103
    Process::ControlInlet& inlet, score::FileKind kind, FileUsage usage)
104
{
105
  const ExternalFileRef proto{
30✔
106
      .path = {},
6✔
107
      .kind = kind,
6✔
108
      .usage = usage,
6✔
109
      .directory = false,
110
      .rewritable = true,
111
      .owner = owner};
12✔
112

113
  ossia::value v = inlet.value();
6✔
114
  if(v.target<std::string>())
6✔
115
  {
116
    if(mapStringValue(*this, v, proto))
6✔
117
      addCommand(new Process::SetControlValue{inlet, v});
2✔
118
  }
6✔
NEW
119
  else if(auto* list = v.target<std::vector<ossia::value>>())
×
120
  {
121
    // Image lists and the like: one control, many paths.
NEW
122
    bool changed = false;
×
NEW
123
    for(auto& elt : *list)
×
NEW
124
      changed |= mapStringValue(*this, elt, proto);
×
125

NEW
126
    if(changed)
×
NEW
127
      addCommand(new Process::SetControlValue{inlet, v});
×
NEW
128
  }
×
129
}
6✔
130

NEW
131
void ExternalFileMap::folder(Process::ControlInlet& inlet)
×
132
{
NEW
133
  ossia::value v = inlet.value();
×
NEW
134
  const ExternalFileRef proto{
×
NEW
135
      .path = {},
×
136
      .kind = score::FileKind::Folder,
137
      .usage = FileUsage::Input,
138
      .directory = true,
139
      .rewritable = true,
NEW
140
      .owner = owner};
×
141

NEW
142
  if(mapStringValue(*this, v, proto))
×
NEW
143
    addCommand(new Process::SetControlValue{inlet, v});
×
NEW
144
}
×
145

146
void ProcessModel::mapExternalFiles(Process::ExternalFileMap& map)
171✔
147
{
148
  // Every avendish process, and every process built out of the standard
149
  // widget ports, holds its files here.
150
  for(auto* inlet : m_inlets)
270✔
151
  {
152
    if(auto* audio = qobject_cast<Process::AudioFileChooser*>(inlet))
99✔
NEW
153
      map.control(*audio, score::FileKind::Audio);
×
154
    else if(auto* video = qobject_cast<Process::VideoFileChooser*>(inlet))
99✔
NEW
155
      map.control(*video, score::FileKind::Video);
×
156
    else if(auto* file = qobject_cast<Process::FileChooserBase*>(inlet))
99✔
157
      map.control(*file, score::FileKind::Unknown);
6✔
158
    else if(auto* dir = qobject_cast<Process::FolderChooser*>(inlet))
93✔
NEW
159
      map.folder(*dir);
×
160
  }
161
}
171✔
162

NEW
163
bool looksLikeExistingFile(
×
164
    const QString& value, const score::DocumentContext& ctx) noexcept
165
{
NEW
166
  const QString trimmed = value.trimmed();
×
167
  // Cheap rejections first: a script body is long and has newlines, and
168
  // stat()ing a few kilobytes of QML would be silly.
NEW
169
  if(trimmed.isEmpty() || trimmed.size() > 4096)
×
NEW
170
    return false;
×
NEW
171
  if(trimmed.contains('\n') || trimmed.contains('\r'))
×
NEW
172
    return false;
×
173

NEW
174
  return QFileInfo::exists(score::locateFilePath(trimmed, ctx));
×
NEW
175
}
×
176

177
//! Devices can reference files too (a video-file input, an encoder's output
178
//! destination). Their settings are opaque QVariants, so the protocol factory
179
//! is the only thing that can read and rebuild them.
180
static void mapDeviceExternalFiles(const score::DocumentContext& ctx, ExternalFileMap& map)
75✔
181
{
182
  auto* devplug = ctx.findPlugin<Explorer::DeviceDocumentPlugin>();
75✔
183
  if(!devplug)
75✔
NEW
184
    return;
×
185

186
  const auto& protocols = ctx.app.interfaces<Device::ProtocolFactoryList>();
75✔
187
  for(auto* dev : devplug->list().devices())
75✔
188
  {
NEW
189
    if(!dev)
×
NEW
190
      continue;
×
191

NEW
192
    const Device::DeviceSettings& settings = dev->settings();
×
NEW
193
    auto* factory = protocols.get(settings.protocol);
×
NEW
194
    if(!factory)
×
NEW
195
      continue;
×
196

NEW
197
    map.owner = settings.name;
×
NEW
198
    const QVariant updated = factory->relocateExternalFiles(
×
NEW
199
        settings.deviceSpecificSettings,
×
NEW
200
        [&](const QString& path, score::FileKind kind, bool output) {
×
NEW
201
      return map.map(
×
NEW
202
          {.path = path,
×
NEW
203
           .kind = kind,
×
NEW
204
           .usage = output ? FileUsage::Output : FileUsage::Input,
×
205
           .directory = false,
206
           .rewritable = true,
NEW
207
           .owner = map.owner});
×
NEW
208
        });
×
209

NEW
210
    if(updated.isValid())
×
211
    {
NEW
212
      Device::DeviceSettings next = settings;
×
NEW
213
      next.deviceSpecificSettings = updated;
×
NEW
214
      map.addCommand(
×
NEW
215
          new Explorer::Command::UpdateDeviceSettings{*devplug, settings.name, next});
×
NEW
216
    }
×
NEW
217
  }
×
218
  map.owner.clear();
75✔
219
}
75✔
220

221
void mapDocumentExternalFiles(const score::DocumentContext& ctx, ExternalFileMap& map)
75✔
222
{
223
  // findChildren walks the whole model tree, which is what we want: processes
224
  // nest (scenario -> interval -> process -> scenario -> ...) and a flat list
225
  // of the top-level ones would miss most of a real document.
226
  const auto processes = ctx.document.model().findChildren<Process::ProcessModel*>();
75✔
227
  for(auto* proc : processes)
246✔
228
  {
229
    map.owner = proc->prettyName();
171✔
230
    proc->mapExternalFiles(map);
171✔
231
  }
232
  map.owner.clear();
75✔
233

234
  mapDeviceExternalFiles(ctx, map);
75✔
235
}
75✔
236
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc