Skip to content

Commit 7feb1c4

Browse files
committed
Add support for loading projects from URL
1 parent 0f18f9c commit 7feb1c4

File tree

7 files changed

+288
-22
lines changed

7 files changed

+288
-22
lines changed

include/scratchcpp/project.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#pragma once
44

55
#include <memory>
6+
#include <functional>
67
#include "spimpl.h"
78

89
#include "global.h"
@@ -14,7 +15,11 @@ class ProjectPrivate;
1415

1516
class IEngine;
1617

17-
/*! \brief The Project class provides API for reading and running Scratch projects. */
18+
/*!
19+
* \brief The Project class provides API for reading and running Scratch projects.
20+
*
21+
* \note Loading online projects is supported if the LIBSCRATCHCPP_NETWORK_SUPPORT option is set, just use setFileName("some URL")
22+
*/
1823
class LIBSCRATCHCPP_EXPORT Project
1924
{
2025
public:
@@ -37,6 +42,8 @@ class LIBSCRATCHCPP_EXPORT Project
3742

3843
std::shared_ptr<IEngine> engine() const;
3944

45+
void setDownloadProgressCallback(const std::function<void(unsigned int, unsigned int)> &&f);
46+
4047
private:
4148
spimpl::unique_impl_ptr<ProjectPrivate> impl;
4249
};

src/project.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,17 @@ void Project::runEventLoop()
6565
impl->runEventLoop();
6666
}
6767

68-
/*! Returns the project file name. */
68+
/*! Returns the project file name or URL. */
6969
const std::string &Project::fileName() const
7070
{
7171
return impl->fileName;
7272
}
7373

74-
/*! Sets the project file name. */
74+
/*! Sets the project file name or URL. */
7575
void Project::setFileName(const std::string &newFileName)
7676
{
7777
impl->fileName = newFileName;
78+
impl->detectScratchVersion();
7879
}
7980

8081
/*! Returns the version of Scratch used for the project. */
@@ -97,3 +98,12 @@ std::shared_ptr<IEngine> Project::engine() const
9798
{
9899
return impl->engine;
99100
}
101+
102+
/*!
103+
* Sets the function which will be called when the asset download progress changes.
104+
* \note The first parameter is the number of downloaded assets and the latter is the number of all assets to download.
105+
*/
106+
void Project::setDownloadProgressCallback(const std::function<void(unsigned int, unsigned int)> &&f)
107+
{
108+
impl->setDownloadProgressCallback(f);
109+
}

src/project_p.cpp

Lines changed: 114 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,36 @@
11
// SPDX-License-Identifier: Apache-2.0
22

3+
#include <scratchcpp/costume.h>
4+
#include <scratchcpp/sound.h>
35
#include <iostream>
46

57
#include "project_p.h"
68
#include "internal/scratch3reader.h"
9+
#include "internal/projectdownloaderfactory.h"
10+
#include "internal/projectdownloader.h"
11+
#include "internal/projecturl.h"
712
#include "engine/internal/engine.h"
813

914
using namespace libscratchcpp;
1015

16+
IProjectDownloaderFactory *ProjectPrivate::downloaderFactory = nullptr;
17+
1118
ProjectPrivate::ProjectPrivate() :
1219
engine(std::make_shared<Engine>())
1320
{
21+
if (!downloaderFactory)
22+
downloaderFactory = ProjectDownloaderFactory::instance().get();
23+
24+
downloader = downloaderFactory->create();
1425
}
1526

1627
ProjectPrivate::ProjectPrivate(const std::string &fileName) :
17-
fileName(fileName),
18-
engine(std::make_shared<Engine>())
28+
ProjectPrivate()
1929
{
20-
// Auto detect Scratch version
21-
scratchVersion = ScratchVersion::Invalid;
22-
Scratch3Reader scratch3;
23-
scratch3.setFileName(fileName);
24-
if (scratch3.isValid())
25-
scratchVersion = ScratchVersion::Scratch3;
30+
this->fileName = fileName;
2631

27-
if (scratchVersion == ScratchVersion::Invalid)
28-
std::cerr << "Unable to determine Scratch version." << std::endl;
32+
// Auto detect Scratch version
33+
detectScratchVersion();
2934
}
3035

3136
ProjectPrivate::ProjectPrivate(const std::string &fileName, ScratchVersion scratchVersion) :
@@ -47,16 +52,77 @@ bool ProjectPrivate::load()
4752
break;
4853
}
4954

50-
reader->setFileName(fileName);
51-
if (!reader->isValid()) {
52-
scratchVersion = ScratchVersion::Invalid;
53-
std::cerr << "Could not read the project." << std::endl;
54-
return false;
55-
}
55+
// Load from URL
56+
ProjectUrl url(fileName);
57+
58+
if (url.isProjectUrl()) {
59+
// Download JSON
60+
if (!downloader->downloadJson(url.projectId())) {
61+
std::cerr << "Failed to download the project file." << std::endl;
62+
return false;
63+
}
64+
65+
bool ret = reader->loadData(downloader->json());
66+
67+
if (!ret)
68+
return false;
69+
70+
// Get asset file names
71+
std::vector<std::string> assetNames;
72+
std::unordered_map<std::string, Asset *> assets;
73+
const auto &targets = reader->targets();
74+
75+
for (auto target : targets) {
76+
const auto &costumes = target->costumes();
77+
const auto &sounds = target->sounds();
78+
79+
for (auto costume : costumes) {
80+
auto it = std::find(assetNames.begin(), assetNames.end(), costume->fileName());
81+
if (it == assetNames.end()) {
82+
assetNames.push_back(costume->fileName());
83+
assets[assetNames.back()] = costume.get();
84+
} else
85+
assets[*it] = costume.get();
86+
}
87+
88+
for (auto sound : sounds) {
89+
auto it = std::find(assetNames.begin(), assetNames.end(), sound->fileName());
90+
if (it == assetNames.end()) {
91+
assetNames.push_back(sound->fileName());
92+
assets[assetNames.back()] = sound.get();
93+
} else
94+
assets[*it] = sound.get();
95+
}
96+
}
97+
98+
// Download assets
99+
if (!downloader->downloadAssets(assetNames)) {
100+
std::cerr << "Failed to download the project assets." << std::endl;
101+
return false;
102+
}
56103

57-
bool ret = reader->load();
58-
if (!ret)
59-
return false;
104+
const auto &assetData = downloader->assets();
105+
assert(assetData.size() == assetNames.size());
106+
107+
// Load asset data
108+
for (size_t i = 0; i < assets.size(); i++) {
109+
const std::string &data = assetData[i];
110+
assets[assetNames[i]]->setData(data.size(), static_cast<void *>(const_cast<char *>(data.c_str())));
111+
}
112+
113+
} else {
114+
// Load from file
115+
reader->setFileName(fileName);
116+
if (!reader->isValid()) {
117+
scratchVersion = ScratchVersion::Invalid;
118+
std::cerr << "Could not read the project." << std::endl;
119+
return false;
120+
}
121+
122+
bool ret = reader->load();
123+
if (!ret)
124+
return false;
125+
}
60126

61127
engine->clear();
62128
engine->setTargets(reader->targets());
@@ -81,6 +147,30 @@ void ProjectPrivate::runEventLoop()
81147
engine->runEventLoop();
82148
}
83149

150+
void ProjectPrivate::detectScratchVersion()
151+
{
152+
ProjectUrl url(fileName);
153+
154+
scratchVersion = ScratchVersion::Invalid;
155+
Scratch3Reader scratch3;
156+
157+
if (url.isProjectUrl()) {
158+
if (!downloader->downloadJson(url.projectId())) {
159+
std::cerr << "Failed to download the project file." << std::endl;
160+
return;
161+
}
162+
163+
scratch3.loadData(downloader->json());
164+
} else
165+
scratch3.setFileName(fileName);
166+
167+
if (scratch3.isValid())
168+
scratchVersion = ScratchVersion::Scratch3;
169+
170+
if (scratchVersion == ScratchVersion::Invalid)
171+
std::cerr << "Unable to determine Scratch version." << std::endl;
172+
}
173+
84174
void ProjectPrivate::setScratchVersion(ScratchVersion version)
85175
{
86176
// TODO: Use this when more versions become supported
@@ -90,3 +180,8 @@ void ProjectPrivate::setScratchVersion(ScratchVersion version)
90180
else
91181
std::cerr << "Unsupported Scratch version: " << static_cast<int>(version) << std::endl;
92182
}
183+
184+
void ProjectPrivate::setDownloadProgressCallback(const std::function<void(unsigned int, unsigned int)> &f)
185+
{
186+
downloader->setDownloadProgressCallback(f);
187+
}

src/project_p.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ namespace libscratchcpp
99
{
1010

1111
class IEngine;
12+
class IProjectDownloaderFactory;
13+
class IProjectDownloader;
1214

1315
struct ProjectPrivate
1416
{
@@ -23,11 +25,17 @@ struct ProjectPrivate
2325
void run();
2426
void runEventLoop();
2527

28+
void detectScratchVersion();
2629
void setScratchVersion(ScratchVersion version);
2730

31+
void setDownloadProgressCallback(const std::function<void(unsigned int, unsigned int)> &f);
32+
2833
ScratchVersion scratchVersion = ScratchVersion::Invalid;
2934
std::string fileName;
3035
std::shared_ptr<IEngine> engine = nullptr;
36+
37+
static IProjectDownloaderFactory *downloaderFactory;
38+
std::shared_ptr<IProjectDownloader> downloader;
3139
};
3240

3341
}; // namespace libscratchcpp

test/load_project/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ add_executable(
66
target_link_libraries(
77
load_project_test
88
GTest::gtest_main
9+
GTest::gmock_main
910
scratchcpp
11+
scratchcpp_mocks
1012
)
1113

1214
gtest_discover_tests(load_project_test)

0 commit comments

Comments
 (0)