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

ossia / score / 36873490647

01 Oct 2026 02:04PM UTC coverage: 47.45% (-0.1%) from 47.56%
36873490647

push

github

jcelerier
addons: add trackingprotocols addon

152629 of 321661 relevant lines covered (47.45%)

145881.74 hits per line

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

90.11
/src/plugins/score-lib-process/Process/Drop/ProcessDropHandler.cpp
1
#include <Process/Drop/ProcessDropHandler.hpp>
2

3
#include <score/tools/File.hpp>
4

5
#include <ossia/detail/algorithms.hpp>
6

7
#include <QDebug>
8
#include <QFile>
9
#include <QFileInfo>
10
#include <QSet>
11
#include <QUrl>
12
namespace Process
13
{
14

15
ProcessDropHandler::ProcessDropHandler() { }
66,079 ✔
16

17
ProcessDropHandler::~ProcessDropHandler() { }
66,058 ✔
18

19
void ProcessDropHandler::getCustomDrops(
43 ✔
20
    std::vector<ProcessDropHandler::ProcessDrop>& drops, const QMimeData& mime,
21
    const score::DocumentContext& ctx) const noexcept
22
{
23
  // dropCustom is no longer noexcept (some overrides invoke parsers that
24
  // can throw on malformed input — see ProcessDropHandler.hpp). Catch
25
  // here so a throwing handler never escapes through the noexcept
26
  // public API and tears down the editor.
27
  try
28
  {
29
    dropCustom(drops, mime, ctx);
43 ✔
30
  }
43 ✔
31
  catch(const std::exception& e)
32
  {
33
    qWarning() << "ProcessDropHandler::dropCustom threw:" << e.what();
1 ✔
34
  }
1 ✔
35
  catch(...)
36
  {
37
    qWarning() << "ProcessDropHandler::dropCustom threw an unknown exception";
1 ✔
38
  }
2 ✔
39
}
45 ✔
40

41
void ProcessDropHandler::getMimeDrops(
18 ✔
42
    std::vector<ProcessDropHandler::ProcessDrop>& drops, const QMimeData& mime,
43
    const QString& fmt, const score::DocumentContext& ctx) const noexcept
44
{
45
  qDebug() << fmt << mime.data(fmt);
18 ✔
46
  auto df = DroppedFile{{}, mime.data(fmt)};
18 ✔
47
  dropData(drops, df, ctx);
18 ✔
48
}
18 ✔
49

50
void ProcessDropHandler::getFileDrops(
21 ✔
51
    std::vector<ProcessDropHandler::ProcessDrop>& drops, const QMimeData& mime,
52
    const score::FilePath& path, const score::DocumentContext& ctx) const noexcept
53
{
54
  // Check for handling through paths
55
  auto old_sz = drops.size();
21 ✔
56
  dropPath(drops, path, ctx);
21 ✔
57
  if(drops.size() != old_sz)
21 ✔
58
    return;
11 ✔
59

60
  // Fall back to manual handling
61
  if(QFile file{path.absolute}; file.open(QIODevice::ReadOnly))
20 ✔
62
  {
63
    dropData(drops, {path, file.readAll()}, ctx);
10 ✔
64
  }
10 ✔
65
}
21 ✔
66

67
QSet<QString> ProcessDropHandler::mimeTypes() const noexcept
41,236 ✔
68
{
69
  return {};
41,236 ✔
70
}
71

72
QSet<QString> ProcessDropHandler::fileExtensions() const noexcept
6,345 ✔
73
{
74
  return {};
6,345 ✔
75
}
76

77
void ProcessDropHandler::dropCustom(
13 ✔
78
    std::vector<ProcessDropHandler::ProcessDrop>&, const QMimeData& data,
79
    const score::DocumentContext& ctx) const
80
{
81
}
13 ✔
82

83
void ProcessDropHandler::dropPath(
×
84
    std::vector<ProcessDropHandler::ProcessDrop>&, const score::FilePath& data,
85
    const score::DocumentContext& ctx) const noexcept
86
{
87
}
×
88

89
void ProcessDropHandler::dropData(
28 ✔
90
    std::vector<ProcessDropHandler::ProcessDrop>&, const DroppedFile& data,
91
    const score::DocumentContext& ctx) const noexcept
92
{
93
}
28 ✔
94

95
ProcessDropHandlerList::~ProcessDropHandlerList() { }
6,290 ✔
96

97
std::vector<ProcessDropHandler::ProcessDrop> ProcessDropHandlerList::getDrop(
26 ✔
98
    const QMimeData& originalMime, const score::DocumentContext& ctx) const noexcept
99
{
100
  std::vector<ProcessDropHandler::ProcessDrop> res;
26 ✔
101

102
  initCaches();
26 ✔
103

104
#if defined(__EMSCRIPTEN__)
105
  // On wasm, Qt writes dropped files to a transient /qt/tmp/ dir and deletes
106
  // them immediately after this callback returns. Move each dropped file into
107
  // score's persistent import area up front and rewrite the URLs, so BOTH the
108
  // custom-drop path (which re-reads mime.urls() itself, e.g. Sound) and the
109
  // file-extension path see a path that survives the drop and can be re-opened
110
  // later (undo/redo, reload of the process).
111
  QMimeData stagedMime;
112
  bool didStage = false;
113
  {
114
    QList<QUrl> newUrls;
115
    for(const QUrl& url : originalMime.urls())
116
    {
117
      // On the JSPI build (which we use), Qt hands dropped files a
118
      // "weblocalfile:/N/name" URL backed by the JS File object; QUrl::toLocalFile()
119
      // is empty but QFile can read it through QWasmFileEngine. On non-asyncify
120
      // builds files land at /qt/tmp instead. In both cases read the bytes here
121
      // (while the source is alive) and copy them into a real, persistent MEMFS
122
      // path so the fopen-based decoders (ffmpeg / sndfile) can open them later.
123
      QString src;
124
      if(const QString local = url.toLocalFile();
125
         !local.isEmpty() && QFileInfo::exists(local))
126
        src = local;
127
      else if(url.scheme() == QLatin1String("weblocalfile"))
128
        src = url.toString();
129

130
      if(!src.isEmpty())
131
      {
132
        if(QFile f{src}; f.open(QIODevice::ReadOnly))
133
        {
134
          const QByteArray bytes = f.readAll();
135
          f.close();
136
          QString name = url.fileName();
137
          if(name.isEmpty())
138
            name = QFileInfo{src}.fileName();
139
          if(QString staged = score::stageImportedFile(name, bytes); !staged.isEmpty())
140
          {
141
            newUrls.push_back(QUrl::fromLocalFile(staged));
142
            didStage = true;
143
            continue;
144
          }
145
        }
146
      }
147
      newUrls.push_back(url);
148
    }
149
    if(didStage)
150
    {
151
      for(const QString& fmt : originalMime.formats())
152
        stagedMime.setData(fmt, originalMime.data(fmt));
153
      stagedMime.setUrls(newUrls);
154
    }
155
  }
156
  const QMimeData& mime = didStage ? stagedMime : originalMime;
157
#else
158
  const QMimeData& mime = originalMime;
26 ✔
159
#endif
160

161
  auto handleCustomDrop = [&](Process::ProcessDropHandler& handler) {
66 ✔
162
    auto before = res.size();
40 ✔
163
    handler.getCustomDrops(res, mime, ctx);
40 ✔
164
    auto after = res.size();
40 ✔
165
    return after != before;
40 ✔
166
  };
167

168
  // Look for drop handlers with the available MIME types
169
  for(const auto& fmt : mime.formats())
59 ✔
170
  {
171
    if(auto it = m_perMimeTypes.find(fmt.toStdString()); it != m_perMimeTypes.end())
33 ✔
172
    {
173
      auto& handler = *it->second;
18 ✔
174

175
      // First check if a custom drop is in order, which handles everything.
176
      if(handleCustomDrop(handler))
18 ✔
177
        return res;
×
178

179
      // Then fall back to the normal mime data drop
180
      handler.getMimeDrops(res, mime, fmt, ctx);
18 ✔
181
    }
18 ✔
182
  }
183

184
  if(!res.empty())
26 ✔
185
  {
186
    // qDebug() << "handled through getMimeDrops";
187
    return res;
×
188
  }
189

190
  // Look for drop handlers with the available file extensions
191
  for(const auto& url : mime.urls())
45 ✔
192
  {
193
    auto path = url.toLocalFile();
19 ✔
194

195
    QFileInfo f{path};
19 ✔
196
    if(f.exists())
19 ✔
197
    {
198
      auto ext = f.suffix().toLower();
15 ✔
199
      if(auto it = m_perFileExtension.find(ext.toStdString());
15 ✔
200
         it != m_perFileExtension.end())
15 ✔
201
      {
202
        const score::FilePath p{
42 ✔
203
            .absolute = path,
14 ✔
204
            .relative = score::relativizeFilePath(path, ctx),
14 ✔
205
            .filename = f.fileName(),
14 ✔
206
            .basename = f.baseName()};
14 ✔
207

208
        for(auto* h : it->second)
35 ✔
209
        {
210
          auto& handler = *h;
22 ✔
211

212
          // First check if a custom drop is in order, which handles everything.
213
          if(handleCustomDrop(handler))
22 ✔
214
          {
215
            // qDebug() << "handled through getCustomDrops";
216
            return res;
1 ✔
217
          }
218

219
          // Then fall back to the normal mime data drop
220
          handler.getFileDrops(res, mime, p, ctx);
21 ✔
221
        }
222
      }
14 ✔
223
    }
15 ✔
224
  }
19 ✔
225

226
  // TODO Fix Sound::DropHandler::drop so that we don't need to do that
227
  // and remove the custom drop mechanism above (problem is that customData is empty)
228
  /*
229
  {
230
    auto comp = [](auto& lhs, auto& rhs) {
231
      return lhs.creation.customData < rhs.creation.customData;
232
    };
233
    ossia::remove_duplicates(res, comp);
234
  }
235
  if(!res.empty())
236
  {
237
    qDebug() << "handled through getFileDrops";
238
    return res;
239
  }
240
  */
241
  return res;
25 ✔
242
}
26 ✔
243

244
std::optional<TimeVal> ProcessDropHandlerList::getMaxDuration(
×
245
    const std::vector<ProcessDropHandler::ProcessDrop>& res)
246
{
247
  using drop_t = Process::ProcessDropHandler::ProcessDrop;
248
  SCORE_ASSERT(!res.empty());
×
249

250
  auto max_t
251
      = std::max_element(res.begin(), res.end(), [](const drop_t& l, const drop_t& r) {
×
252
          return l.duration < r.duration;
×
253
        });
254

255
  return max_t->duration;
×
256
}
257

258
void ProcessDropHandlerList::initCaches() const
26 ✔
259
{
260
  if(m_lastCacheSize != this->size())
26 ✔
261
  {
262
    m_perMimeTypes.clear();
26 ✔
263
    m_perFileExtension.clear();
26 ✔
264

265
    for(auto& handler : *this)
572 ✔
266
    {
267
      for(const auto& ext : handler.fileExtensions())
3,068 ✔
268
      {
269
        m_perFileExtension[ext.toLower().toStdString()].push_back(&handler);
2,522 ✔
270
      }
271

272
      for(const auto& ext : handler.mimeTypes())
702 ✔
273
      {
274
        m_perMimeTypes[ext.toLower().toStdString()] = &handler;
156 ✔
275
      }
276
    }
277
  }
26 ✔
278
}
26 ✔
279
}
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