LCOV - code coverage report
Current view: top level - xenolith/resources/assets - XLAsset.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 121 383 31.6 %
Date: 2024-05-12 00:16:13 Functions: 17 48 35.4 %

          Line data    Source code
       1             : /**
       2             :  Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
       3             : 
       4             :  Permission is hereby granted, free of charge, to any person obtaining a copy
       5             :  of this software and associated documentation files (the "Software"), to deal
       6             :  in the Software without restriction, including without limitation the rights
       7             :  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       8             :  copies of the Software, and to permit persons to whom the Software is
       9             :  furnished to do so, subject to the following conditions:
      10             : 
      11             :  The above copyright notice and this permission notice shall be included in
      12             :  all copies or substantial portions of the Software.
      13             : 
      14             :  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      15             :  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      16             :  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      17             :  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      18             :  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      19             :  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
      20             :  THE SOFTWARE.
      21             :  **/
      22             : 
      23             : #include "XLAsset.h"
      24             : #include "XLAssetLibrary.h"
      25             : #include "XLNetworkRequest.h"
      26             : #include "XLApplication.h"
      27             : 
      28             : #include "curl/curl.h"
      29             : 
      30             : #if WIN32
      31             : #undef interface
      32             : #endif
      33             : 
      34             : namespace STAPPLER_VERSIONIZED stappler::xenolith::storage {
      35             : 
      36           0 : AssetLock::~AssetLock() {
      37           0 :         if (_releaseFunction) {
      38           0 :                 _releaseFunction(_lockedVersion);
      39             :         }
      40           0 :         _asset = nullptr;
      41           0 : }
      42             : 
      43           0 : StringView AssetLock::getCachePath() const {
      44           0 :         return _asset->getCachePath();
      45             : }
      46             : 
      47           0 : AssetLock::AssetLock(Rc<Asset> &&asset, const AssetVersionData &data, Function<void(const AssetVersionData &)> &&cb, Ref *owner)
      48           0 : : _lockedVersion(data), _releaseFunction(move(cb)), _asset(move(asset)), _owner(owner) { }
      49             : 
      50          21 : Asset::Asset(AssetLibrary *lib, const db::Value &val) : _library(lib) {
      51          21 :         bool resumeDownload = false;
      52          21 :         const db::Value *versions = nullptr;
      53         147 :         for (auto &it : val.asDict()) {
      54         126 :                 if (it.first == "__oid") {
      55          21 :                         _id = reinterpretValue<uint64_t>(it.second.getInteger());
      56         105 :                 } else if (it.first == "url") {
      57          21 :                         _url = StringView(it.second.getString()).str<Interface>();
      58          84 :                 } else if (it.first == "data") {
      59           0 :                         _data = Value(it.second);
      60          84 :                 } else if (it.first == "mtime") {
      61          21 :                         _mtime = Time(it.second.getInteger());
      62          63 :                 } else if (it.first == "touch") {
      63          21 :                         _touch = Time(it.second.getInteger());
      64          42 :                 } else if (it.first == "ttl") {
      65          21 :                         _ttl = TimeInterval(it.second.getInteger());
      66          21 :                 } else if (it.first == "download") {
      67          21 :                         resumeDownload = it.second.getBool();
      68           0 :                 } else if (it.first == "versions") {
      69           0 :                         versions = &it.second;
      70             :                 }
      71             :         }
      72             : 
      73          21 :         _path = AssetLibrary::getAssetPath(_id);
      74          21 :         _cache = toString(_path, "/cache");
      75             : 
      76          21 :         filesystem::mkdir(_path);
      77          21 :         filesystem::mkdir(_cache);
      78             : 
      79          21 :         if (versions) {
      80           0 :                 parseVersions(*versions);
      81             :         }
      82             : 
      83          21 :         if (resumeDownload) {
      84           0 :                 download();
      85             :         }
      86          21 : }
      87             : 
      88          42 : Asset::~Asset() {
      89          21 :         _library->removeAsset(this);
      90          42 : }
      91             : 
      92           0 : Rc<AssetLock> Asset::lockVersion(int64_t id, Ref *owner) {
      93           0 :         std::unique_lock ctx(_mutex);
      94           0 :         for (auto &it : _versions) {
      95           0 :                 if (it.id == id && it.complete) {
      96           0 :                         ++ it.locked;
      97           0 :                         auto ret = new AssetLock(this, it, [this] (const VersionData &data) {
      98           0 :                                 releaseLock(data);
      99           0 :                         }, owner);
     100           0 :                         auto ref = Rc<AssetLock>(ret);
     101           0 :                         ret->release(0);
     102           0 :                         return ref;
     103           0 :                 }
     104             :         }
     105           0 :         return nullptr;
     106           0 : }
     107             : 
     108           0 : Rc<AssetLock> Asset::lockReadableVersion(Ref *owner) {
     109           0 :         std::unique_lock ctx(_mutex);
     110           0 :         for (auto &it : _versions) {
     111           0 :                 if (it.complete && filesystem::exists(it.path)) {
     112           0 :                         ++ it.locked;
     113           0 :                         auto ret = new AssetLock(this, it, [this] (const VersionData &data) {
     114           0 :                                 releaseLock(data);
     115           0 :                         }, owner);
     116           0 :                         auto ref = Rc<AssetLock>(ret);
     117           0 :                         ret->release(0);
     118           0 :                         return ref;
     119           0 :                 }
     120             :         }
     121           0 :         return nullptr;
     122           0 : }
     123             : 
     124           0 : StringView Asset::getContentType() const {
     125           0 :         std::unique_lock ctx(_mutex);
     126           0 :         if (auto ver = getReadableVersion()) {
     127           0 :                 return ver->contentType;
     128             :         } else {
     129           0 :                 for (auto &it : _versions) {
     130           0 :                         if (!it.contentType.empty()) {
     131           0 :                                 return it.contentType;
     132             :                         }
     133             :                 }
     134             :         }
     135           0 :         return StringView();
     136           0 : }
     137             : 
     138          21 : bool Asset::download() {
     139          21 :         std::unique_lock ctx(_mutex);
     140          21 :         if (_download) {
     141           0 :                 return true;
     142             :         }
     143             : 
     144             :         do {
     145          21 :                 auto it = _versions.begin();
     146          21 :                 while (it != _versions.end()) {
     147           0 :                         if (it->complete && !filesystem::exists(it->path)) {
     148           0 :                                 dropVersion(*it);
     149           0 :                                 it = _versions.erase(it);
     150             :                         } else {
     151           0 :                                 ++ it;
     152             :                         }
     153             :                 }
     154             :         } while (0);
     155             : 
     156          21 :         auto it = _versions.begin();
     157          21 :         while (it != _versions.end()) {
     158           0 :                 if (!it->complete) {
     159           0 :                         if (resumeDownload(*it)) {
     160           0 :                                 return true;
     161             :                         } else {
     162           0 :                                 dropVersion(*it);
     163           0 :                                 it = _versions.erase(it);
     164             :                         }
     165             :                 } else {
     166           0 :                         ++ it;
     167             :                 }
     168             :         }
     169             : 
     170          21 :         if (!_versions.empty()) {
     171           0 :                 if (filesystem::exists(_versions.front().path)) {
     172           0 :                         return startNewDownload(_versions.front().ctime, _versions.front().etag);
     173             :                 }
     174             :         }
     175             : 
     176          21 :         return startNewDownload(Time(), StringView());
     177          21 : }
     178             : 
     179           0 : void Asset::touch(Time t) {
     180           0 :         std::unique_lock ctx(_mutex);
     181           0 :         _touch = t;
     182           0 :         _dirty = true;
     183           0 : }
     184             : 
     185           0 : void Asset::clear() {
     186           0 :         std::unique_lock ctx(_mutex);
     187           0 :         auto it = _versions.begin();
     188           0 :         while (it != _versions.end()) {
     189           0 :                 if (it->complete) {
     190           0 :                         dropVersion(*it);
     191           0 :                         it = _versions.erase(it);
     192             :                 } else {
     193           0 :                         ++ it;
     194             :                 }
     195             :         }
     196           0 :         setDirty(Flags(CacheDataUpdated | DownloadFailed));
     197           0 : }
     198             : 
     199           0 : bool Asset::isDownloadAvailable() const {
     200           0 :         std::unique_lock ctx(_mutex);
     201           0 :         return _versions.empty() || (!_versions.empty() && !_versions.back().complete);
     202           0 : }
     203             : 
     204           0 : bool Asset::isDownloadInProgress() const {
     205           0 :         std::unique_lock ctx(_mutex);
     206           0 :         return _download;
     207           0 : }
     208             : 
     209           0 : float Asset::getProgress() const {
     210           0 :         std::unique_lock ctx(_mutex);
     211           0 :         for (auto &it : _versions) {
     212           0 :                 if (it.id == _downloadId) {
     213           0 :                         return it.progress;
     214             :                 }
     215             :         }
     216           0 :         return _versions.empty() ? 0.0f : (_versions.front().complete ? 1.0f : 0.0f);
     217           0 : }
     218             : 
     219           0 : int64_t Asset::getReadableVersionId() const {
     220           0 :         std::unique_lock ctx(_mutex);
     221           0 :         if (auto v = getReadableVersion()) {
     222           0 :                 return v->id;
     223             :         }
     224           0 :         return 0;
     225           0 : }
     226             : 
     227           0 : void Asset::setData(const Value &d) {
     228           0 :         std::unique_lock ctx(_mutex);
     229           0 :         _data = d;
     230           0 :         _dirty = true;
     231           0 : }
     232             : 
     233           0 : void Asset::setData(Value &&d) {
     234           0 :         std::unique_lock ctx(_mutex);
     235           0 :         _data = std::move(d);
     236           0 :         _dirty = true;
     237           0 : }
     238             : 
     239           0 : Value Asset::encode() const {
     240           0 :         std::unique_lock ctx(_mutex);
     241             :         return Value({
     242           0 :                 pair("ttl", Value(_ttl.toMicros())),
     243           0 :                 pair("touch", Value(_touch.toMicros())),
     244           0 :                 pair("data", Value(_data)),
     245           0 :         });
     246           0 : }
     247             : 
     248           0 : const AssetVersionData * Asset::getReadableVersion() const {
     249           0 :         for (auto &it : _versions) {
     250           0 :                 if (it.complete && filesystem::exists(it.path)) {
     251           0 :                         return &it;
     252             :                 }
     253             :         }
     254           0 :         return nullptr;
     255             : }
     256             : 
     257           0 : void Asset::parseVersions(const db::Value &downloads) {
     258           0 :         std::unique_lock ctx(_mutex);
     259             : 
     260           0 :         Set<String> paths;
     261           0 :         Set<String> pathsToRemove;
     262             : 
     263           0 :         for (auto &download : downloads.asArray()) {
     264           0 :                 VersionData data;
     265           0 :                 for (auto &it : download.asDict()) {
     266           0 :                         if (it.first == "__oid") {
     267           0 :                                 data.id = it.second.getInteger();
     268           0 :                         } else if (it.first == "etag") {
     269           0 :                                 data.etag = StringView(it.second.getString()).str<Interface>();
     270           0 :                         } else if (it.first == "ctime") {
     271           0 :                                 data.ctime = Time(it.second.getInteger());
     272           0 :                         } else if (it.first == "mtime") {
     273           0 :                                 data.mtime = Time(it.second.getInteger());
     274           0 :                         } else if (it.first == "size") {
     275           0 :                                 data.size = it.second.getInteger();
     276           0 :                         } else if (it.first == "type") {
     277           0 :                                 data.contentType = StringView(it.second.getString()).str<Interface>();
     278           0 :                         } else if (it.first == "complete") {
     279           0 :                                 data.complete = it.second.getBool();
     280             :                         }
     281             :                 }
     282             : 
     283           0 :                 auto tag = StringView(data.etag);
     284           0 :                 tag.trimChars<StringView::Chars<'"', '\'', ' ', '-'>>();
     285           0 :                 auto versionPath = toString(_path, "/", data.ctime.toMicros(), "-", tag);
     286           0 :                 auto iit = paths.find(versionPath);
     287           0 :                 if (iit != paths.end()) {
     288           0 :                         _library->eraseVersion(data.id);
     289             :                 } else {
     290           0 :                         if (filesystem::exists(versionPath)) {
     291           0 :                                 auto &v = _versions.emplace_back(move(data));
     292           0 :                                 v.path = move(versionPath);
     293           0 :                                 v.download = true;
     294             : 
     295           0 :                                 paths.emplace(v.path);
     296             :                         } else {
     297           0 :                                 _library->eraseVersion(data.id);
     298             :                         }
     299             :                 }
     300           0 :         }
     301             : 
     302           0 :         filesystem::ftw(_path, [&, this] (StringView path, bool isFile) {
     303           0 :                 if (!isFile && path != _cache && path != _path) {
     304           0 :                         auto it = paths.find(path);
     305           0 :                         if (it == paths.end()) {
     306           0 :                                 pathsToRemove.emplace(path.str<Interface>());
     307             :                         }
     308             :                 }
     309           0 :         }, 1);
     310             : 
     311           0 :         for (auto &it : pathsToRemove) {
     312           0 :                 filesystem::remove(it, true, true);
     313             :         }
     314             : 
     315           0 :         bool localFound = false;
     316           0 :         bool pendingFound = false;
     317             : 
     318           0 :         auto it = _versions.begin();
     319           0 :         while (it != _versions.end()) {
     320           0 :                 if (it->complete) {
     321           0 :                         if (!localFound) {
     322           0 :                                 localFound = true;
     323           0 :                                 ++ it;
     324             :                         } else {
     325           0 :                                 _library->eraseVersion(it->id);
     326           0 :                                 it = _versions.erase(it);
     327             :                         }
     328             :                 } else {
     329           0 :                         if (!pendingFound) {
     330           0 :                                 pendingFound = true;
     331           0 :                                 ++ it;
     332             :                         } else {
     333           0 :                                 _library->eraseVersion(it->id);
     334           0 :                                 it = _versions.erase(it);
     335             :                         }
     336             :                 }
     337             :         }
     338           0 : }
     339             : 
     340             : struct AssetDownloadData : Ref {
     341             :         Rc<Asset> asset;
     342             :         Asset::VersionData data;
     343             :         FILE *inputFile = nullptr;
     344             :         bool valid = true;
     345             :         float progress = 0.0f;
     346             : 
     347          21 :         AssetDownloadData(Rc<Asset> &&a)
     348          21 :         : asset(move(a)) { }
     349             : 
     350           0 :         AssetDownloadData(Rc<Asset> &&a, Asset::VersionData &data)
     351           0 :         : asset(move(a)), data(data) { }
     352             : };
     353             : 
     354          21 : bool Asset::startNewDownload(Time ctime, StringView etag) {
     355          21 :         auto data = Rc<AssetDownloadData>::alloc(this);
     356             : 
     357          42 :         auto req = Rc<network::Request>::create([&, this] (network::Handle &handle) {
     358          21 :                 handle.init(network::Method::Get, _url);
     359             : 
     360          21 :                 handle.setMTime(ctime.toMicros());
     361          21 :                 handle.setETag(etag);
     362             : 
     363          21 :                 handle.setHeaderCallback([data = data.get()] (StringView key, StringView value) {
     364         210 :                         if (key == "last-modified") {
     365          21 :                                 data->data.ctime = std::max(Time::fromHttp(value), data->data.ctime);
     366         189 :                         } else if (key == "x-filemodificationtime") {
     367          21 :                                 if (uint64_t v = value.readInteger(10).get(0)) {
     368          21 :                                         data->data.ctime = std::max(Time::microseconds(v), data->data.ctime);
     369             :                                 }
     370         168 :                         } else if (key == "etag") {
     371          21 :                                 data->data.etag = value.str<Interface>();
     372         147 :                         } else if (key == "content-length") {
     373          21 :                                 data->data.size = std::max(size_t(value.readInteger(10).get(0)), data->data.size);
     374         126 :                         } else if (key == "x-filesize") {
     375          21 :                                 data->data.size = std::max(size_t(value.readInteger(10).get(0)), data->data.size);
     376         105 :                         } else if (key == "content-type") {
     377          21 :                                 data->data.contentType = value.str<Interface>();
     378             :                         }
     379         210 :                 });
     380          21 :                 handle.setReceiveCallback([this, data = data.get()] (char *bytes, size_t size) {
     381         147 :                         if (!data->valid) {
     382           0 :                                 return size_t(CURL_WRITEFUNC_ERROR);
     383             :                         }
     384             : 
     385         147 :                         if (!data->inputFile) {
     386          21 :                                 auto tag = StringView(data->data.etag);
     387          21 :                                 tag.trimChars<StringView::Chars<'"', '\'', ' ', '-'>>();
     388          21 :                                 data->data.path = toString(_path, "/", data->data.ctime.toMicros(), "-", tag);
     389          21 :                                 data->inputFile = filesystem::native::fopen_fn(data->data.path.data(), "w");
     390          21 :                                 if (!data->inputFile) {
     391           0 :                                         return size_t(CURL_WRITEFUNC_ERROR);
     392             :                                 }
     393          21 :                                 addVersion(data);
     394             :                         }
     395             : 
     396         147 :                         return size_t(fwrite(bytes, size, 1, data->inputFile) * size);
     397             :                 });
     398          21 :                 return true;
     399          21 :         }, data);
     400             : 
     401          21 :         req->setDownloadProgress([this, data = data.get()] (const network::Request &, int64_t total, int64_t now) {
     402          21 :                 data->progress = float(now) / float(total);
     403          21 :                 setDownloadProgress(data->data.id, data->progress);
     404          21 :         });
     405             : 
     406          21 :         _download = true;
     407          21 :         _library->setAssetDownload(_id, _download);
     408             : 
     409          21 :         req->perform(_library->getController(), [this, data = data.get()] (const network::Request &req, bool success) {
     410          21 :                 if (data->inputFile) {
     411          21 :                         fclose(data->inputFile);
     412          21 :                         data->inputFile = nullptr;
     413             : 
     414          21 :                         setDownloadComplete(data->data, data->valid && success);
     415          21 :                         return;
     416             :                 } else {
     417           0 :                         auto code = req.getHandle().getResponseCode();
     418           0 :                         if (code >= 300 && code < 400) {
     419           0 :                                 setFileValidated(success);
     420           0 :                                 return;
     421             :                         }
     422             :                 }
     423             : 
     424           0 :                 setDownloadComplete(data->data, data->valid && false);
     425             :         });
     426          21 :         return true;
     427          21 : }
     428             : 
     429           0 : bool Asset::resumeDownload(VersionData &d) {
     430           0 :         filesystem::Stat stat;
     431           0 :         if (!filesystem::stat(d.path, stat)) {
     432           0 :                 return false;
     433             :         }
     434             : 
     435           0 :         auto data = Rc<AssetDownloadData>::alloc(this, d);
     436             : 
     437           0 :         auto req = Rc<network::Request>::create([&, this] (network::Handle &handle) {
     438           0 :                 handle.init(network::Method::Get, _url);
     439             : 
     440           0 :                 handle.setResumeOffset(stat.size);
     441           0 :                 handle.setHeaderCallback([data = data.get()] (StringView key, StringView value) {
     442           0 :                         if (key == "last-modified") {
     443           0 :                                 if (Time::fromHttp(value) > data->data.ctime) {
     444           0 :                                         data->valid = false;
     445             :                                 }
     446           0 :                         } else if (key == "etag") {
     447           0 :                                 if (data->data.etag != value) {
     448           0 :                                         data->valid = false;
     449             :                                 }
     450             :                         }
     451           0 :                 });
     452           0 :                 handle.setReceiveCallback([data = data.get()] (char *bytes, size_t size) {
     453           0 :                         if (!data->valid) {
     454           0 :                                 return size_t(CURL_WRITEFUNC_ERROR);
     455             :                         }
     456             : 
     457           0 :                         if (!data->inputFile) {
     458           0 :                                 data->inputFile = filesystem::native::fopen_fn(data->data.path.data(), "a");
     459           0 :                                 if (!data->inputFile) {
     460           0 :                                         return size_t(CURL_WRITEFUNC_ERROR);
     461             :                                 }
     462             :                         }
     463             : 
     464           0 :                         return size_t(fwrite(bytes, size, 1, data->inputFile) * size);
     465             :                 });
     466           0 :                 return true;
     467           0 :         }, data);
     468             : 
     469           0 :         req->setDownloadProgress([this, data = data.get()] (const network::Request &, int64_t total, int64_t now) {
     470           0 :                 data->progress = float(now) / float(total);
     471           0 :                 setDownloadProgress(data->data.id, data->progress);
     472           0 :         });
     473             : 
     474           0 :         _downloadId = d.id;
     475           0 :         _download = true;
     476           0 :         _library->setAssetDownload(_id, _download);
     477             : 
     478           0 :         req->perform(_library->getController(), [this, data = data.get()] (const network::Request &req, bool success) {
     479           0 :                 if (data->inputFile) {
     480           0 :                         fclose(data->inputFile);
     481           0 :                         data->inputFile = nullptr;
     482             :                 }
     483             : 
     484           0 :                 setDownloadComplete(data->data, data->valid && success);
     485           0 :         });
     486           0 :         return true;
     487           0 : }
     488             : 
     489          21 : void Asset::setDownloadProgress(int64_t id, float progress) {
     490          21 :         std::unique_lock ctx(_mutex);
     491          21 :         for (auto &it : _versions) {
     492           0 :                 if (it.id == id) {
     493           0 :                         it.progress = progress;
     494           0 :                         setDirty(Flags(Update::DownloadProgress));
     495             :                 }
     496             :         }
     497          21 : }
     498             : 
     499          21 : void Asset::setDownloadComplete(VersionData &data, bool success) {
     500          21 :         std::unique_lock ctx(_mutex);
     501          21 :         data.complete = success;
     502             : 
     503          21 :         _download = false;
     504          21 :         _library->setAssetDownload(_id, _download);
     505             : 
     506          21 :         if (success) {
     507          21 :                 for (auto &it : _versions) {
     508          21 :                         if (it.id == data.id) {
     509          21 :                                 replaceVersion(data);
     510          21 :                                 setDirty(Flags(Update::DownloadCompleted | Update::DownloadSuccessful | Update::CacheDataUpdated));
     511          21 :                                 _library->setVersionComplete(data.id, true);
     512          21 :                                 return;
     513             :                         }
     514             :                 }
     515             :         } else {
     516           0 :                 auto it = _versions.begin();
     517           0 :                 while (it != _versions.end()) {
     518           0 :                         if (it->id == data.id) {
     519           0 :                                 dropVersion(*it);
     520           0 :                                 it = _versions.erase(it);
     521           0 :                                 setDirty(Flags(Update::DownloadCompleted | Update::DownloadFailed));
     522             :                         } else {
     523           0 :                                 ++ it;
     524             :                         }
     525             :                 }
     526             :         }
     527             : 
     528           0 :         _downloadId = 0;
     529          21 : }
     530             : 
     531           0 : void Asset::setFileValidated(bool success) {
     532           0 :         std::unique_lock ctx(_mutex);
     533           0 :         _download = false;
     534           0 :         _library->setAssetDownload(_id, _download);
     535           0 :         _downloadId = 0;
     536             : 
     537           0 :         setDirty(Flags(CacheDataUpdated));
     538           0 : }
     539             : 
     540          21 : void Asset::replaceVersion(VersionData &data) {
     541          42 :         for (auto &it : _versions) {
     542          21 :                 if (it.id != data.id) {
     543           0 :                         dropVersion(it);
     544             :                 }
     545             :         }
     546             : 
     547          21 :         _versions.clear();
     548          21 :         _versions.emplace_back(data);
     549          21 :         _touch = Time::now();
     550          21 : }
     551             : 
     552          21 : void Asset::addVersion(AssetDownloadData *data) {
     553          21 :         _library->perform([this, data] (const Server &, const db::Transaction &t) {
     554          21 :                 auto id = _library->addVersion(t, _id, data->data);
     555          21 :                 _library->getApplication()->performOnMainThread([this, id, data] {
     556          21 :                         std::unique_lock ctx(_mutex);
     557          21 :                         _downloadId = data->data.id = id;
     558          21 :                         _versions.emplace_back(data->data);
     559          21 :                         setDirty(Flags(Update::DownloadStarted));
     560          21 :                 }, data);
     561          21 :                 return true;
     562             :         }, data);
     563          21 : }
     564             : 
     565           0 : void Asset::dropVersion(const VersionData &data) {
     566           0 :         if (!data.locked) {
     567           0 :                 filesystem::remove(data.path, true, true);
     568             :         }
     569           0 :         _library->eraseVersion(data.id);
     570           0 : }
     571             : 
     572           0 : void Asset::releaseLock(const VersionData &data) {
     573           0 :         std::unique_lock ctx(_mutex);
     574           0 :         for (auto &it : _versions) {
     575           0 :                 if (it.id == data.id) {
     576           0 :                         -- it.locked;
     577           0 :                         return;
     578             :                 }
     579             :         }
     580             : 
     581           0 :         filesystem::remove(data.path, true, true);
     582           0 : }
     583             : 
     584             : }

Generated by: LCOV version 1.14