#include "media_blocks.h" #include "../../lib/debug_config.h" #include #include #include #include #include #include #include /* ─── 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; }