1 /*
2 * Copyright (C) 2014 Emeric Poupon
3 *
4 * This file is part of LMS.
5 *
6 * LMS is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * LMS is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with LMS. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "DownloadResource.hpp"
21
22 #include <array>
23 #include <iostream>
24 #include <iomanip>
25
26 #include <Wt/Http/Response.h>
27 #include <Wt/WLocalDateTime.h>
28
29 #include "database/Artist.hpp"
30 #include "database/Release.hpp"
31 #include "database/Session.hpp"
32 #include "database/Track.hpp"
33 #include "utils/Exception.hpp"
34 #include "utils/Logger.hpp"
35 #include "utils/Zipper.hpp"
36
37 #include "LmsApplication.hpp"
38
39 #define LOG(level) LMS_LOG(UI, level) << "Download resource: "
40
41 namespace UserInterface {
42
~DownloadResource()43 DownloadResource::~DownloadResource()
44 {
45 beingDeleted();
46 }
47
48 void
handleRequest(const Wt::Http::Request & request,Wt::Http::Response & response)49 DownloadResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
50 {
51 try
52 {
53 std::shared_ptr<Zip::Zipper> zipper;
54
55 // First, see if this request is for a continuation
56 Wt::Http::ResponseContinuation *continuation = request.continuation();
57 if (continuation)
58 zipper = Wt::cpp17::any_cast<std::shared_ptr<Zip::Zipper>>(continuation->data());
59 else
60 {
61 zipper = createZipper();
62 response.setContentLength(zipper->getTotalZipFile());
63 response.setMimeType("application/zip");
64 }
65
66 if (!zipper)
67 return;
68
69 std::array<std::byte, bufferSize> buffer;
70 Zip::SizeType nbWrittenBytes {zipper->writeSome(buffer.data(), buffer.size())};
71
72 response.out().write(reinterpret_cast<const char *>(buffer.data()), nbWrittenBytes);
73
74 if (!zipper->isComplete())
75 {
76 auto* continuation {response.createContinuation()};
77 continuation->setData(zipper);
78 }
79 }
80 catch (Zip::ZipperException& exception)
81 {
82 LOG(ERROR) << "Zipper exception: " << exception.what();
83 }
84 }
85
86
87 static
88 std::string
getArtistPathName(Database::Artist::pointer artist)89 getArtistPathName(Database::Artist::pointer artist)
90 {
91 return StringUtils::replaceInString(artist->getName(), "/", "_");
92 }
93
94 static
95 std::string
getReleaseArtistPathName(Database::Release::pointer release)96 getReleaseArtistPathName(Database::Release::pointer release)
97 {
98 std::string releaseArtistName;
99
100 std::vector<Wt::Dbo::ptr<Database::Artist>> artists;
101
102 artists = release->getReleaseArtists();
103 if (artists.empty())
104 artists = release->getArtists();
105
106 if (artists.size() > 1)
107 releaseArtistName = Wt::WString::tr("Lms.Explore.various-artists").toUTF8();
108 else if (artists.size() == 1)
109 releaseArtistName = artists.front()->getName();
110
111 releaseArtistName = StringUtils::replaceInString(releaseArtistName, "/", "_");
112
113 return releaseArtistName;
114 }
115
116 static
117 std::string
getReleasePathName(Database::Release::pointer release)118 getReleasePathName(Database::Release::pointer release)
119 {
120 std::string releaseName;
121
122 if (auto releaseYear {release->getReleaseYear()})
123 releaseName += std::to_string(*releaseYear) + " - ";
124 releaseName += StringUtils::replaceInString(release->getName(), "/", "_");
125
126 return releaseName;
127 }
128
129 static
130 std::string
getTrackPathName(Database::Track::pointer track)131 getTrackPathName(Database::Track::pointer track)
132 {
133 std::ostringstream fileName;
134
135 if (auto discNumber {track->getDiscNumber()})
136 fileName << *discNumber << ".";
137 if (auto trackNumber {track->getTrackNumber()})
138 fileName << std::setw(2) << std::setfill('0') << *trackNumber << " - ";
139
140 fileName << StringUtils::replaceInString(track->getName(), "/", "_") << track->getPath().filename().extension().string();
141
142 return fileName.str();
143 }
144
145 static
146 std::unique_ptr<Zip::Zipper>
createZipper(const std::vector<Database::Track::pointer> & tracks)147 createZipper(const std::vector<Database::Track::pointer>& tracks)
148 {
149 std::map<std::string, std::filesystem::path> files;
150
151 for (const Database::Track::pointer& track : tracks)
152 {
153 std::string releaseName;
154 std::string releaseArtistName;
155 if (auto release {track->getRelease()})
156 {
157 releaseName = getReleasePathName(release);
158 releaseArtistName = getReleaseArtistPathName(release);
159 }
160
161 std::string fileName;
162 if (!releaseArtistName.empty())
163 fileName += releaseArtistName + "/";
164 if (!releaseName.empty())
165 fileName += releaseName + "/";
166 fileName += getTrackPathName(track);
167
168 files.emplace(fileName, track->getPath());
169 }
170
171 return std::make_unique<Zip::Zipper>(files, Wt::WLocalDateTime::currentDateTime().toUTC());
172 }
173
DownloadArtistResource(Database::IdType artistId)174 DownloadArtistResource::DownloadArtistResource(Database::IdType artistId)
175 : _artistId {artistId}
176 {
177 auto transaction {LmsApp->getDbSession().createSharedTransaction()};
178
179 Database::Artist::pointer artist {Database::Artist::getById(LmsApp->getDbSession(), artistId)};
180 if (artist)
181 suggestFileName(getArtistPathName(artist) + ".zip");
182 }
183
184 std::unique_ptr<Zip::Zipper>
createZipper()185 DownloadArtistResource::createZipper()
186 {
187 auto transaction {LmsApp->getDbSession().createSharedTransaction()};
188
189 const Database::Artist::pointer artist {Database::Artist::getById(LmsApp->getDbSession(), _artistId)};
190 if (!artist)
191 {
192 LOG(DEBUG) << "Cannot find artist";
193 return {};
194 }
195
196 return UserInterface::createZipper(artist->getTracks());
197 }
198
DownloadReleaseResource(Database::IdType releaseId)199 DownloadReleaseResource::DownloadReleaseResource(Database::IdType releaseId)
200 : _releaseId {releaseId}
201 {
202 auto transaction {LmsApp->getDbSession().createSharedTransaction()};
203
204 Database::Release::pointer release {Database::Release::getById(LmsApp->getDbSession(), releaseId)};
205 if (release)
206 suggestFileName(getReleasePathName(release) + ".zip");
207 }
208
209
210 std::unique_ptr<Zip::Zipper>
createZipper()211 DownloadReleaseResource::createZipper()
212 {
213 auto transaction {LmsApp->getDbSession().createSharedTransaction()};
214
215 const Database::Release::pointer release {Database::Release::getById(LmsApp->getDbSession(), _releaseId)};
216 if (!release)
217 {
218 LOG(DEBUG) << "Cannot find release";
219 return {};
220 }
221
222 return UserInterface::createZipper(release->getTracks());
223 }
224
DownloadTrackResource(Database::IdType trackId)225 DownloadTrackResource::DownloadTrackResource(Database::IdType trackId)
226 : _trackId {trackId}
227 {
228 auto transaction {LmsApp->getDbSession().createSharedTransaction()};
229
230 Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
231 if (track)
232 suggestFileName(getTrackPathName(track) + ".zip");
233 }
234
235 std::unique_ptr<Zip::Zipper>
createZipper()236 DownloadTrackResource::createZipper()
237 {
238 auto transaction {LmsApp->getDbSession().createSharedTransaction()};
239
240 const Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), _trackId)};
241 if (!track)
242 {
243 LOG(DEBUG) << "Cannot find track";
244 return {};
245 }
246
247 return UserInterface::createZipper({track});
248 }
249
250 } // namespace UserInterface
251