You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

290 lines
9.6 KiB

#include "media_blocks.h"
#include "../../lib/debug_config.h"
#include <QDir>
#include <QFileInfo>
#include <QDateTime>
#include <QRandomGenerator>
#include <QJsonArray>
#include <QJsonDocument>
#include <cmath>
/* ─── MediaBlockReader ─── */
MediaBlockReader::MediaBlockReader() {}
void MediaBlockReader::initFromAttrs(const QString& mediaDir,
const QString& dt, const QString& basename,
const QString& suffix, const QString& ext,
const QJsonObject& localAttrs,
qint64 blockSize, qint64 totalSize, int totalBlocks)
{
m_mediaDir = mediaDir; m_dt = dt; m_basename = basename;
m_suffix = suffix; m_ext = ext;
m_blockSize = blockSize; m_totalSize = totalSize; m_totalBlocks = totalBlocks;
m_pos = 0; m_valid = true;
QString st = localAttrs.value("st").toString();
if (st == "fl") {
m_mode = SINGLE_FILE;
QString fp = localAttrs.value("fp").toString();
m_filePath = mediaDir + "/" + fp;
m_fileSize = totalSize;
QFileInfo fi(m_filePath);
m_complete = fi.exists() && fi.size() == totalSize;
} else {
m_mode = FRAGMENTED;
QJsonArray bl = localAttrs.value("bl").toArray();
for (const auto& v : bl) m_blocks.append(v.toInt());
m_complete = (m_blocks.size() == totalBlocks);
}
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "MediaBlockReader: mode=%s complete=%d path=%s blocks=%d/%d",
m_mode == SINGLE_FILE ? "single" : "frag", m_complete,
qPrintable(debugPath()), m_blocks.size(), totalBlocks);
}
MediaBlockReader MediaBlockReader::fromLegacyFile(const QString& filePath) {
MediaBlockReader r;
r.m_mode = SINGLE_FILE;
r.m_filePath = filePath;
r.m_valid = true;
QFileInfo fi(filePath);
r.m_fileSize = fi.size();
r.m_complete = fi.exists() && r.m_fileSize > 0;
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "MediaBlockReader: legacy file=%s size=%lld valid=%d",
qPrintable(filePath), (long long)r.m_fileSize, r.m_valid);
return r;
}
qint64 MediaBlockReader::blockSizeForN(int n) const {
if (n < m_totalBlocks - 1) return m_blockSize;
qint64 last = m_totalSize - (m_totalBlocks - 1) * m_blockSize;
if (last <= 0) last = m_totalSize;
return last;
}
QString MediaBlockReader::blockFilePath(int n) const {
return QString("%1/%2_%3_%4_%5.%6")
.arg(m_mediaDir, m_dt).arg(n).arg(m_basename, m_suffix, m_ext);
}
QByteArray MediaBlockReader::readAll() {
if (!m_valid || !m_complete || m_mode == NONE) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "MediaBlockReader::readAll: invalid/incomplete");
return {};
}
if (m_mode == SINGLE_FILE) {
QFile f(m_filePath);
if (!f.open(QIODevice::ReadOnly)) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "MediaBlockReader::readAll: cannot open %s", qPrintable(m_filePath));
return {};
}
QByteArray data = f.readAll();
m_pos = data.size();
return data;
}
QByteArray data; data.resize(m_totalSize);
qint64 offset = 0;
for (int i = 0; i < m_blocks.size(); i++) {
int n = m_blocks[i];
QString path = blockFilePath(n);
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "MediaBlockReader::readAll: cannot open block %d: %s", n, qPrintable(path));
return {};
}
qint64 sz = blockSizeForN(n);
f.read(data.data() + offset, sz);
offset += sz;
}
m_pos = data.size();
return data;
}
QByteArray MediaBlockReader::readRange(qint64 offset, qint64 size) {
if (!m_valid || offset < 0 || size <= 0 || m_mode == NONE) return {};
if (offset >= m_totalSize) return {};
if (offset + size > m_totalSize) size = m_totalSize - offset;
if (m_mode == SINGLE_FILE) {
QFile f(m_filePath);
if (!f.open(QIODevice::ReadOnly)) return {};
f.seek(offset);
QByteArray data = f.read(size);
m_pos = offset + data.size();
return data;
}
int startBlock = offset / m_blockSize;
QByteArray data; data.resize(size);
qint64 copied = 0;
qint64 remaining = size;
int currentN = startBlock;
while (remaining > 0 && currentN < m_totalBlocks && m_blocks.contains(currentN)) {
qint64 blockStart = (qint64)currentN * m_blockSize;
qint64 blockSize = blockSizeForN(currentN);
qint64 inBlockOffset = offset + copied - blockStart;
if (inBlockOffset < 0) inBlockOffset = 0;
qint64 toRead = qMin(remaining, blockSize - inBlockOffset);
if (toRead <= 0) break;
QString path = blockFilePath(currentN);
QFile f(path);
if (f.open(QIODevice::ReadOnly)) {
f.seek(inBlockOffset);
f.read(data.data() + copied, toRead);
}
copied += toRead;
remaining -= toRead;
currentN++;
}
m_pos = offset + copied;
if (copied < size) data.resize(copied);
return data;
}
bool MediaBlockReader::seek(qint64 pos) {
if (!m_valid || pos < 0 || pos > m_totalSize) return false;
m_pos = pos;
return true;
}
bool MediaBlockReader::atEnd() const {
return m_pos >= m_totalSize;
}
bool MediaBlockReader::saveToFile(const QString& destPath) {
if (!m_valid || !m_complete) return false;
QByteArray data = readAll();
if (data.isEmpty()) return false;
QFile f(destPath);
if (!f.open(QIODevice::WriteOnly)) return false;
f.write(data);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "MediaBlockReader: saved to %s (%lld bytes)", qPrintable(destPath), (long long)data.size());
return true;
}
QString MediaBlockReader::assembleAndCleanup() {
if (!m_valid || m_mode != FRAGMENTED || !m_complete) return {};
QString assembledPath = QString("%1/%2_%3_%4.%5")
.arg(m_mediaDir, m_dt, m_basename, m_suffix, m_ext);
QFile af(assembledPath);
if (!af.open(QIODevice::WriteOnly)) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "MediaBlockReader::assemble: cannot create %s", qPrintable(assembledPath));
return {};
}
for (int i = 0; i < m_blocks.size(); i++) {
int n = m_blocks[i];
QString path = blockFilePath(n);
QFile bf(path);
if (bf.open(QIODevice::ReadOnly)) {
af.write(bf.readAll());
bf.close();
QFile::remove(path);
}
}
af.close();
m_mode = SINGLE_FILE;
m_filePath = assembledPath;
m_fileSize = m_totalSize;
m_blocks.clear();
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "MediaBlockReader: assembled %s", qPrintable(assembledPath));
return assembledPath;
}
QJsonObject MediaBlockReader::localAttrsAfterAssembly() const {
QJsonObject obj;
obj["st"] = "fl";
obj["fp"] = QString("%1_%2_%3.%4").arg(m_dt, m_basename, m_suffix, m_ext);
QJsonArray bl;
for (int i = 0; i < m_totalBlocks; i++) bl.append(i);
obj["bl"] = bl;
return obj;
}
QString MediaBlockReader::debugPath() const {
if (m_mode == SINGLE_FILE) return m_filePath;
if (m_mode == FRAGMENTED) return QString("%1/%2_*_%3_%4.%5")
.arg(m_mediaDir, m_dt, m_basename, m_suffix, m_ext);
return "none";
}
/* ─── media_split_to_blocks ─── */
qint64 media_calc_block_size(qint64 fileSize) {
if (fileSize <= 0) return 0;
qint64 target = (fileSize + 29) / 30; /* ceil(file_size / 30) */
if (target < 10 * 1024 * 1024) target = 10 * 1024 * 1024;
if (target > 25 * 1024 * 1024) target = 25 * 1024 * 1024;
return target;
}
MediaBlockInfo media_split_to_blocks(const QString& srcFile,
const QString& mediaDir,
bool removeSrc)
{
MediaBlockInfo info;
QFileInfo fi(srcFile);
if (!fi.exists()) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "media_split_to_blocks: src not found: %s", qPrintable(srcFile));
return info;
}
info.fileSize = fi.size();
if (info.fileSize == 0) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "media_split_to_blocks: empty file: %s", qPrintable(srcFile));
return info;
}
info.blockSize = media_calc_block_size(info.fileSize);
info.numBlocks = (int)((info.fileSize + info.blockSize - 1) / info.blockSize);
info.ext = fi.suffix();
info.basename = fi.completeBaseName();
QDateTime now = QDateTime::currentDateTimeUtc();
info.dt = now.toString("yyyyMMdd-HHmmss");
uint16_t rnd = QRandomGenerator::global()->generate() & 0xFFFF;
info.suffix = QString::number(rnd, 16).rightJustified(4, '0');
QDir().mkpath(mediaDir);
QFile src(srcFile);
if (!src.open(QIODevice::ReadOnly)) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "media_split_to_blocks: cannot open src: %s", qPrintable(srcFile));
return info;
}
qint64 remaining = info.fileSize;
for (int n = 0; n < info.numBlocks; n++) {
qint64 chunk = qMin(remaining, info.blockSize);
QString path = QString("%1/%2_%3_%4_%5.%6")
.arg(mediaDir, info.dt).arg(n).arg(info.basename, info.suffix, info.ext);
QFile dst(path);
if (!dst.open(QIODevice::WriteOnly)) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "media_split_to_blocks: cannot create block %d: %s", n, qPrintable(path));
src.close(); return info;
}
dst.write(src.read(chunk));
dst.close();
remaining -= chunk;
}
src.close();
if (removeSrc) QFile::remove(srcFile);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "media_split_to_blocks: %s -> %d blocks (%lld bytes each) in %s",
qPrintable(fi.fileName()), info.numBlocks, (long long)info.blockSize, qPrintable(mediaDir));
return info;
}