| //======================================================================== |
| // |
| // CachedFile.cc |
| // |
| // This file is licensed under the GPLv2 or later |
| // |
| // Copyright 2009 Stefan Thomas <thomas@eload24.com> |
| // Copyright 2010, 2011 Hib Eris <hib@hiberis.nl> |
| // Copyright 2010, 2018-2020, 2022 Albert Astals Cid <aacid@kde.org> |
| // Copyright (C) 2013 Julien Nabet <serval2412@yahoo.fr> |
| // |
| //======================================================================== |
| |
| #include <config.h> |
| #include "CachedFile.h" |
| |
| //------------------------------------------------------------------------ |
| // CachedFile |
| //------------------------------------------------------------------------ |
| |
| CachedFile::CachedFile(CachedFileLoader *cacheLoader) |
| { |
| loader = cacheLoader; |
| |
| streamPos = 0; |
| chunks = new std::vector<Chunk>(); |
| length = 0; |
| |
| length = loader->init(this); |
| refCnt = 1; |
| |
| if (length != ((size_t)-1)) { |
| chunks->resize(length / CachedFileChunkSize + 1); |
| } else { |
| error(errInternal, -1, "Failed to initialize file cache."); |
| chunks->resize(0); |
| } |
| } |
| |
| CachedFile::~CachedFile() |
| { |
| delete loader; |
| delete chunks; |
| } |
| |
| void CachedFile::incRefCnt() |
| { |
| refCnt++; |
| } |
| |
| void CachedFile::decRefCnt() |
| { |
| if (--refCnt == 0) { |
| delete this; |
| } |
| } |
| |
| long int CachedFile::tell() |
| { |
| return streamPos; |
| } |
| |
| int CachedFile::seek(long int offset, int origin) |
| { |
| if (origin == SEEK_SET) { |
| streamPos = offset; |
| } else if (origin == SEEK_CUR) { |
| streamPos += offset; |
| } else { |
| streamPos = length + offset; |
| } |
| |
| if (streamPos > length) { |
| streamPos = 0; |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| int CachedFile::cache(const std::vector<ByteRange> &origRanges) |
| { |
| std::vector<int> loadChunks; |
| int numChunks = length / CachedFileChunkSize + 1; |
| std::vector<bool> chunkNeeded(numChunks); |
| int startChunk, endChunk; |
| std::vector<ByteRange> chunk_ranges, all; |
| ByteRange range; |
| const std::vector<ByteRange> *ranges = &origRanges; |
| |
| if (ranges->empty()) { |
| range.offset = 0; |
| range.length = length; |
| all.push_back(range); |
| ranges = &all; |
| } |
| |
| for (int i = 0; i < numChunks; ++i) { |
| chunkNeeded[i] = false; |
| } |
| for (const ByteRange &r : *ranges) { |
| |
| if (r.length == 0) { |
| continue; |
| } |
| if (r.offset >= length) { |
| continue; |
| } |
| |
| const size_t start = r.offset; |
| size_t end = start + r.length - 1; |
| if (end >= length) { |
| end = length - 1; |
| } |
| |
| startChunk = start / CachedFileChunkSize; |
| endChunk = end / CachedFileChunkSize; |
| for (int chunk = startChunk; chunk <= endChunk; chunk++) { |
| if ((*chunks)[chunk].state == chunkStateNew) { |
| chunkNeeded[chunk] = true; |
| } |
| } |
| } |
| |
| int chunk = 0; |
| while (chunk < numChunks) { |
| while (!chunkNeeded[chunk] && (++chunk != numChunks)) { |
| ; |
| } |
| if (chunk == numChunks) { |
| break; |
| } |
| startChunk = chunk; |
| loadChunks.push_back(chunk); |
| |
| while ((++chunk != numChunks) && chunkNeeded[chunk]) { |
| loadChunks.push_back(chunk); |
| } |
| endChunk = chunk - 1; |
| |
| range.offset = startChunk * CachedFileChunkSize; |
| range.length = (endChunk - startChunk + 1) * CachedFileChunkSize; |
| |
| chunk_ranges.push_back(range); |
| } |
| |
| if (chunk_ranges.size() > 0) { |
| CachedFileWriter writer = CachedFileWriter(this, &loadChunks); |
| return loader->load(chunk_ranges, &writer); |
| } |
| |
| return 0; |
| } |
| |
| size_t CachedFile::read(void *ptr, size_t unitsize, size_t count) |
| { |
| size_t bytes = unitsize * count; |
| if (length < (streamPos + bytes)) { |
| bytes = length - streamPos; |
| } |
| |
| if (bytes == 0) { |
| return 0; |
| } |
| |
| // Load data |
| if (cache(streamPos, bytes) != 0) { |
| return 0; |
| } |
| |
| // Copy data to buffer |
| size_t toCopy = bytes; |
| while (toCopy) { |
| int chunk = streamPos / CachedFileChunkSize; |
| int offset = streamPos % CachedFileChunkSize; |
| size_t len = CachedFileChunkSize - offset; |
| |
| if (len > toCopy) { |
| len = toCopy; |
| } |
| |
| memcpy(ptr, (*chunks)[chunk].data + offset, len); |
| streamPos += len; |
| toCopy -= len; |
| ptr = (char *)ptr + len; |
| } |
| |
| return bytes; |
| } |
| |
| int CachedFile::cache(size_t rangeOffset, size_t rangeLength) |
| { |
| std::vector<ByteRange> r; |
| ByteRange range; |
| range.offset = rangeOffset; |
| range.length = rangeLength; |
| r.push_back(range); |
| return cache(r); |
| } |
| |
| //------------------------------------------------------------------------ |
| // CachedFileWriter |
| //------------------------------------------------------------------------ |
| |
| CachedFileWriter::CachedFileWriter(CachedFile *cachedFileA, std::vector<int> *chunksA) |
| { |
| cachedFile = cachedFileA; |
| chunks = chunksA; |
| |
| if (chunks) { |
| offset = 0; |
| it = (*chunks).begin(); |
| } |
| } |
| |
| CachedFileWriter::~CachedFileWriter() { } |
| |
| size_t CachedFileWriter::write(const char *ptr, size_t size) |
| { |
| const char *cp = ptr; |
| size_t len = size; |
| size_t nfree, ncopy; |
| size_t written = 0; |
| size_t chunk; |
| |
| if (!len) { |
| return 0; |
| } |
| |
| while (len) { |
| if (chunks) { |
| if (offset == CachedFileChunkSize) { |
| ++it; |
| if (it == (*chunks).end()) { |
| return written; |
| } |
| offset = 0; |
| } |
| chunk = *it; |
| } else { |
| offset = cachedFile->length % CachedFileChunkSize; |
| chunk = cachedFile->length / CachedFileChunkSize; |
| } |
| |
| if (chunk >= cachedFile->chunks->size()) { |
| cachedFile->chunks->resize(chunk + 1); |
| } |
| |
| nfree = CachedFileChunkSize - offset; |
| ncopy = (len >= nfree) ? nfree : len; |
| memcpy(&((*cachedFile->chunks)[chunk].data[offset]), cp, ncopy); |
| len -= ncopy; |
| cp += ncopy; |
| offset += ncopy; |
| written += ncopy; |
| |
| if (!chunks) { |
| cachedFile->length += ncopy; |
| } |
| |
| if (offset == CachedFileChunkSize) { |
| (*cachedFile->chunks)[chunk].state = CachedFile::chunkStateLoaded; |
| } |
| } |
| |
| if ((chunk == (cachedFile->length / CachedFileChunkSize)) && (offset == (cachedFile->length % CachedFileChunkSize))) { |
| (*cachedFile->chunks)[chunk].state = CachedFile::chunkStateLoaded; |
| } |
| |
| return written; |
| } |
| |
| CachedFileLoader::~CachedFileLoader() = default; |
| |
| //------------------------------------------------------------------------ |