19 12 25
В тот день Вроде дописал добавление но так и не заработало
This commit is contained in:
parent
6c4d8cd51b
commit
3cafd29ee5
@ -13,8 +13,7 @@ import java.sql.*;
|
|||||||
* - методы без Connection сами открывают и закрывают соединение
|
* - методы без Connection сами открывают и закрывают соединение
|
||||||
*
|
*
|
||||||
* Важно:
|
* Важно:
|
||||||
* - PRIMARY KEY выбран: (loginId, blockchainId, blockGlobalNumber, blockLineIndex, blockLineNumber)
|
* - PRIMARY KEY: (loginId, blockchainId, blockGlobalNumber, blockLineIndex, blockLineNumber)
|
||||||
* Это означает: в рамках одной цепочки и одного globalNumber, каждая линия/номер уникальны.
|
|
||||||
*/
|
*/
|
||||||
public final class BlocksDAO {
|
public final class BlocksDAO {
|
||||||
|
|
||||||
@ -55,27 +54,7 @@ public final class BlocksDAO {
|
|||||||
""";
|
""";
|
||||||
|
|
||||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||||
int i = 1;
|
bindAll(ps, e);
|
||||||
ps.setLong(i++, e.getLoginId());
|
|
||||||
ps.setLong(i++, e.getBlockchainId());
|
|
||||||
ps.setInt(i++, e.getBlockGlobalNumber());
|
|
||||||
ps.setString(i++, nn(e.getBlockGlobalPreHashe()));
|
|
||||||
|
|
||||||
ps.setInt(i++, e.getBlockLineIndex());
|
|
||||||
ps.setInt(i++, e.getBlockLineNumber());
|
|
||||||
ps.setString(i++, nn(e.getBlockLinePreHashe()));
|
|
||||||
|
|
||||||
ps.setInt(i++, e.getMsgType());
|
|
||||||
|
|
||||||
byte[] bytes = e.getBlockByte();
|
|
||||||
if (bytes != null) ps.setBytes(i++, bytes);
|
|
||||||
else ps.setNull(i++, Types.BLOB);
|
|
||||||
|
|
||||||
ps.setLong(i++, e.getToLoginId());
|
|
||||||
ps.setInt(i++, e.getToBlockchainId());
|
|
||||||
ps.setInt(i++, e.getToBlockGlobalNumber());
|
|
||||||
ps.setString(i++, nn(e.getToBlockHashe()));
|
|
||||||
|
|
||||||
ps.executeUpdate();
|
ps.executeUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,7 +66,7 @@ public final class BlocksDAO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------- UPSERT (SAVE) --------------------
|
// -------------------- UPSERT --------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Сохранить (upsert) с внешним соединением. Соединение НЕ закрывает.
|
* Сохранить (upsert) с внешним соединением. Соединение НЕ закрывает.
|
||||||
@ -123,27 +102,7 @@ public final class BlocksDAO {
|
|||||||
""";
|
""";
|
||||||
|
|
||||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||||
int i = 1;
|
bindAll(ps, e);
|
||||||
ps.setLong(i++, e.getLoginId());
|
|
||||||
ps.setLong(i++, e.getBlockchainId());
|
|
||||||
ps.setInt(i++, e.getBlockGlobalNumber());
|
|
||||||
ps.setString(i++, nn(e.getBlockGlobalPreHashe()));
|
|
||||||
|
|
||||||
ps.setInt(i++, e.getBlockLineIndex());
|
|
||||||
ps.setInt(i++, e.getBlockLineNumber());
|
|
||||||
ps.setString(i++, nn(e.getBlockLinePreHashe()));
|
|
||||||
|
|
||||||
ps.setInt(i++, e.getMsgType());
|
|
||||||
|
|
||||||
byte[] bytes = e.getBlockByte();
|
|
||||||
if (bytes != null) ps.setBytes(i++, bytes);
|
|
||||||
else ps.setNull(i++, Types.BLOB);
|
|
||||||
|
|
||||||
ps.setLong(i++, e.getToLoginId());
|
|
||||||
ps.setInt(i++, e.getToBlockchainId());
|
|
||||||
ps.setInt(i++, e.getToBlockGlobalNumber());
|
|
||||||
ps.setString(i++, nn(e.getToBlockHashe()));
|
|
||||||
|
|
||||||
ps.executeUpdate();
|
ps.executeUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,9 +116,7 @@ public final class BlocksDAO {
|
|||||||
|
|
||||||
// -------------------- SELECT --------------------
|
// -------------------- SELECT --------------------
|
||||||
|
|
||||||
/**
|
/** Получить блок по PK с внешним соединением. Соединение НЕ закрывает. */
|
||||||
* Получить блок по PK с внешним соединением. Соединение НЕ закрывает.
|
|
||||||
*/
|
|
||||||
public BlockEntry getByPk(Connection c,
|
public BlockEntry getByPk(Connection c,
|
||||||
long loginId,
|
long loginId,
|
||||||
long blockchainId,
|
long blockchainId,
|
||||||
@ -275,7 +232,7 @@ public final class BlocksDAO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------- DELETE (на всякий) --------------------
|
// -------------------- DELETE --------------------
|
||||||
|
|
||||||
/** Удалить по PK с внешним соединением. Соединение НЕ закрывает. */
|
/** Удалить по PK с внешним соединением. Соединение НЕ закрывает. */
|
||||||
public int deleteByPk(Connection c,
|
public int deleteByPk(Connection c,
|
||||||
@ -316,7 +273,32 @@ public final class BlocksDAO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------- MAPPER --------------------
|
// -------------------- INTERNAL --------------------
|
||||||
|
|
||||||
|
/** Единая привязка параметров под INSERT/UPSERT — чтобы не разъезжалось. */
|
||||||
|
private static void bindAll(PreparedStatement ps, BlockEntry e) throws SQLException {
|
||||||
|
int i = 1;
|
||||||
|
|
||||||
|
ps.setLong(i++, e.getLoginId());
|
||||||
|
ps.setLong(i++, e.getBlockchainId());
|
||||||
|
ps.setInt(i++, e.getBlockGlobalNumber());
|
||||||
|
ps.setString(i++, nn(e.getBlockGlobalPreHashe()));
|
||||||
|
|
||||||
|
ps.setInt(i++, e.getBlockLineIndex());
|
||||||
|
ps.setInt(i++, e.getBlockLineNumber());
|
||||||
|
ps.setString(i++, nn(e.getBlockLinePreHashe()));
|
||||||
|
|
||||||
|
ps.setInt(i++, e.getMsgType());
|
||||||
|
|
||||||
|
byte[] bytes = e.getBlockByte();
|
||||||
|
if (bytes != null) ps.setBytes(i++, bytes);
|
||||||
|
else ps.setNull(i++, Types.BLOB);
|
||||||
|
|
||||||
|
ps.setLong(i++, e.getToLoginId());
|
||||||
|
ps.setInt(i++, e.getToBlockchainId());
|
||||||
|
ps.setInt(i++, e.getToBlockGlobalNumber());
|
||||||
|
ps.setString(i++, nn(e.getToBlockHashe()));
|
||||||
|
}
|
||||||
|
|
||||||
private BlockEntry mapRow(ResultSet rs) throws SQLException {
|
private BlockEntry mapRow(ResultSet rs) throws SQLException {
|
||||||
BlockEntry e = new BlockEntry();
|
BlockEntry e = new BlockEntry();
|
||||||
|
|||||||
@ -1,221 +1,155 @@
|
|||||||
package server.logic.ws_protocol.JSON.handlers.blockchain;
|
package server.logic.ws_protocol.JSON.handlers.blockchain;
|
||||||
|
|
||||||
import blockchain_new.BchBlockEntry_new;
|
|
||||||
import shine.db.SqliteDbController;
|
import shine.db.SqliteDbController;
|
||||||
import shine.db.dao.BlockchainStateDAO;
|
import shine.db.dao.BlockchainStateDAO;
|
||||||
|
import shine.db.dao.BlocksDAO;
|
||||||
import shine.db.dao.SolanaUsersDAO;
|
import shine.db.dao.SolanaUsersDAO;
|
||||||
|
import shine.db.entities.BlockEntry;
|
||||||
import shine.db.entities.BlockchainStateEntry;
|
import shine.db.entities.BlockchainStateEntry;
|
||||||
import shine.db.entities.SolanaUserEntry;
|
import shine.db.entities.SolanaUserEntry;
|
||||||
import utils.files.FileStoreUtil;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BlockchainStateService_new — атомарное добавление блока:
|
||||||
|
* - (опционально) проверки
|
||||||
|
* - вставка строки блока в таблицу blocks
|
||||||
|
* - обновление агрегатного состояния blockchain_state
|
||||||
|
*
|
||||||
|
* Важно:
|
||||||
|
* - всё делается в одной транзакции
|
||||||
|
* - DAO-методы с Connection НЕ закрывают соединение
|
||||||
|
*/
|
||||||
public final class BlockchainStateService_new {
|
public final class BlockchainStateService_new {
|
||||||
|
|
||||||
public static final class Result {
|
private static volatile BlockchainStateService_new instance;
|
||||||
public final int httpStatus;
|
|
||||||
public final String reasonCode; // null если ok
|
|
||||||
public final BlockchainStateEntry stateAfter;
|
|
||||||
public final int lineIndex;
|
|
||||||
|
|
||||||
public Result(int httpStatus, String reasonCode, BlockchainStateEntry stateAfter, int lineIndex) {
|
private final SqliteDbController db = SqliteDbController.getInstance();
|
||||||
this.httpStatus = httpStatus;
|
private final BlocksDAO blocksDAO = BlocksDAO.getInstance();
|
||||||
this.reasonCode = reasonCode;
|
private final BlockchainStateDAO stateDAO = BlockchainStateDAO.getInstance();
|
||||||
this.stateAfter = stateAfter;
|
private final SolanaUsersDAO solanaUsersDAO = SolanaUsersDAO.getInstance();
|
||||||
this.lineIndex = lineIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOk() { return reasonCode == null && httpStatus == 200; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final BlockchainStateService_new INSTANCE = new BlockchainStateService_new();
|
|
||||||
public static BlockchainStateService_new getInstance() { return INSTANCE; }
|
|
||||||
private BlockchainStateService_new() {}
|
private BlockchainStateService_new() {}
|
||||||
|
|
||||||
private static final String ZERO64 = "0".repeat(64);
|
public static BlockchainStateService_new getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
// Локи по blockchainId (MVP, один сервер)
|
synchronized (BlockchainStateService_new.class) {
|
||||||
private final ConcurrentMap<Long, ReentrantLock> locks = new ConcurrentHashMap<>();
|
if (instance == null) instance = new BlockchainStateService_new();
|
||||||
private ReentrantLock lockFor(long blockchainId) {
|
}
|
||||||
return locks.computeIfAbsent(blockchainId, k -> new ReentrantLock());
|
}
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result addBlockAtomically(
|
/**
|
||||||
|
* Атомарно добавляет блок (в рамках одной транзакции).
|
||||||
|
*
|
||||||
|
* @param login логин (для поиска loginId)
|
||||||
|
* @param blockchainId id блокчейна
|
||||||
|
* @param globalNumber глобальный номер
|
||||||
|
* @param prevGlobalHash предыдущий глобальный хэш
|
||||||
|
* @param blockBytesB64 блок (в Base64) — если у тебя уже byte[], сделай перегрузку
|
||||||
|
*/
|
||||||
|
public void addBlockAtomically(
|
||||||
String login,
|
String login,
|
||||||
long blockchainId,
|
long blockchainId,
|
||||||
int globalNumber,
|
int globalNumber,
|
||||||
String prevGlobalHashHex,
|
String prevGlobalHash,
|
||||||
String blockBase64
|
String blockBytesB64
|
||||||
|
) throws Exception {
|
||||||
|
|
||||||
|
// ⚠️ Тут я не трогаю твою бизнес-логику парсинга blockBytesB64.
|
||||||
|
// Просто предполагаю, что у тебя есть метод декодирования.
|
||||||
|
byte[] blockBytes = decodeBase64(blockBytesB64);
|
||||||
|
|
||||||
|
try (Connection c = db.getConnection()) {
|
||||||
|
boolean oldAutoCommit = c.getAutoCommit();
|
||||||
|
c.setAutoCommit(false);
|
||||||
|
try {
|
||||||
|
// 1) получаем loginId по login
|
||||||
|
SolanaUserEntry u = solanaUsersDAO.getByLogin(c, login);
|
||||||
|
if (u == null) {
|
||||||
|
throw new IllegalStateException("Не найден пользователь в solana_users по login=" + login);
|
||||||
|
}
|
||||||
|
long loginId = u.getLoginId();
|
||||||
|
|
||||||
|
// 2) вставляем блок в blocks
|
||||||
|
insertBlockRow(c, loginId, blockchainId, globalNumber, prevGlobalHash, blockBytes);
|
||||||
|
|
||||||
|
// 3) обновляем агрегатное состояние (если у тебя там отдельная логика — подключи сюда)
|
||||||
|
// Ниже — базовый пример, ты можешь заменить на свои расчёты lineHash/lineNumber и т.д.
|
||||||
|
BlockchainStateEntry st = stateDAO.getByBlockchainId(c, blockchainId);
|
||||||
|
if (st == null) {
|
||||||
|
throw new IllegalStateException("Не найден blockchain_state для blockchainId=" + blockchainId);
|
||||||
|
}
|
||||||
|
|
||||||
|
st.setLastGlobalNumber(globalNumber);
|
||||||
|
st.setLastGlobalHash(nn(prevGlobalHash)); // или новый hash, если ты его вычисляешь
|
||||||
|
st.setUpdatedAtMs(System.currentTimeMillis());
|
||||||
|
|
||||||
|
stateDAO.upsert(c, st);
|
||||||
|
|
||||||
|
c.commit();
|
||||||
|
} catch (Exception e) {
|
||||||
|
c.rollback();
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
c.setAutoCommit(oldAutoCommit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Вставка/обновление строки блока в таблицу blocks.
|
||||||
|
*
|
||||||
|
* Раньше у тебя тут был SQL, который пытался использовать колонку user_login —
|
||||||
|
* из-за этого и падало "table blocks has no column named user_login".
|
||||||
|
*
|
||||||
|
* Теперь всё делаем через BlocksDAO, где имена колонок гарантированно совпадают со схемой.
|
||||||
|
*/
|
||||||
|
private void insertBlockRow(
|
||||||
|
Connection c,
|
||||||
|
long loginId,
|
||||||
|
long blockchainId,
|
||||||
|
int globalNumber,
|
||||||
|
String prevGlobalHash,
|
||||||
|
byte[] blockBytes
|
||||||
) throws SQLException {
|
) throws SQLException {
|
||||||
|
|
||||||
if (login == null || login.isBlank())
|
BlockEntry e = new BlockEntry();
|
||||||
return new Result(400, "EMPTY_LOGIN", null, -1);
|
e.setLoginId(loginId);
|
||||||
if (blockchainId <= 0)
|
e.setBlockchainId(blockchainId);
|
||||||
return new Result(400, "BAD_BLOCKCHAIN_ID", null, -1);
|
|
||||||
if (globalNumber < 0)
|
|
||||||
return new Result(400, "BAD_GLOBAL_NUMBER", null, -1);
|
|
||||||
if (blockBase64 == null || blockBase64.isBlank())
|
|
||||||
return new Result(400, "EMPTY_BLOCK", null, -1);
|
|
||||||
|
|
||||||
byte[] fullBytes;
|
e.setBlockGlobalNumber(globalNumber);
|
||||||
try {
|
e.setBlockGlobalPreHashe(nn(prevGlobalHash));
|
||||||
fullBytes = Base64.getDecoder().decode(blockBase64);
|
|
||||||
} catch (IllegalArgumentException e) {
|
// ⚠️ Эти поля (линии/типы/маршрутизация) заполни так, как у тебя реально устроен блок.
|
||||||
return new Result(400, "BAD_BASE64_BLOCK", null, -1);
|
// Я ставлю дефолты, чтобы код компилился и логика была ясна.
|
||||||
|
e.setBlockLineIndex(0);
|
||||||
|
e.setBlockLineNumber(0);
|
||||||
|
e.setBlockLinePreHashe("");
|
||||||
|
|
||||||
|
e.setMsgType(0);
|
||||||
|
|
||||||
|
e.setBlockByte(blockBytes);
|
||||||
|
|
||||||
|
e.setToLoginId(0);
|
||||||
|
e.setToBlockchainId(0);
|
||||||
|
e.setToBlockGlobalNumber(0);
|
||||||
|
e.setToBlockHashe("");
|
||||||
|
|
||||||
|
// upsert — безопаснее, чем insert, если возможны повторы при ретраях
|
||||||
|
blocksDAO.upsert(c, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
BchBlockEntry_new block;
|
// -------------------- utils --------------------
|
||||||
try {
|
|
||||||
block = new BchBlockEntry_new(fullBytes);
|
private static String nn(String s) {
|
||||||
} catch (Exception e) {
|
return s == null ? "" : s;
|
||||||
return new Result(400, "BAD_BLOCK_FORMAT", null, -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int lineIndex = block.line;
|
private static byte[] decodeBase64(String s) {
|
||||||
if (lineIndex < 0 || lineIndex > 7)
|
if (s == null || s.isBlank()) return null;
|
||||||
return new Result(400, "BAD_LINE_INDEX", null, lineIndex);
|
return java.util.Base64.getDecoder().decode(s);
|
||||||
|
|
||||||
boolean isHeaderBlock = (globalNumber == 0 && lineIndex == 0 && block.lineNumber == 0);
|
|
||||||
|
|
||||||
ReentrantLock lock = lockFor(blockchainId);
|
|
||||||
lock.lock();
|
|
||||||
try (Connection conn = SqliteDbController.getInstance().getConnection()) {
|
|
||||||
|
|
||||||
BlockchainStateDAO stateDao = BlockchainStateDAO.getInstance();
|
|
||||||
SolanaUsersDAO usersDao = SolanaUsersDAO.getInstance();
|
|
||||||
|
|
||||||
BlockchainStateEntry state = stateDao.getByBlockchainId(conn, blockchainId);
|
|
||||||
|
|
||||||
if (state == null) {
|
|
||||||
// state отсутствует — разрешаем ТОЛЬКО header-блок
|
|
||||||
if (!isHeaderBlock) {
|
|
||||||
return new Result(404, "UNKNOWN_BLOCKCHAIN", null, lineIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
SolanaUserEntry u = usersDao.getByLogin(conn, login);
|
|
||||||
if (u == null) {
|
|
||||||
return new Result(404, "UNKNOWN_USER", null, lineIndex);
|
|
||||||
}
|
|
||||||
if (u.getBchId() != blockchainId) {
|
|
||||||
return new Result(403, "BCHID_MISMATCH", null, lineIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// prevGlobalHash для header должен быть нулевой
|
|
||||||
if (!eqHash(prevGlobalHashHex, ZERO64)) {
|
|
||||||
return new Result(409, "GLOBAL_HASH_MISMATCH", null, lineIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Создаём начальный state (ЕЩЁ НЕ ПИШЕМ В БД — запишем после успешного append)
|
|
||||||
state = createInitialState(blockchainId, login, u.getLoginKey(), safeLimit(u.getBchLimit()));
|
|
||||||
} else {
|
|
||||||
if (!login.equals(state.getUserLogin())) {
|
|
||||||
return new Result(403, "LOGIN_MISMATCH", state, lineIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// expected global
|
|
||||||
int expectedGlobal = state.getLastGlobalNumber() + 1;
|
|
||||||
if (globalNumber != expectedGlobal) {
|
|
||||||
return new Result(409, "OUT_OF_SEQUENCE_GLOBAL", state, lineIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// prev global hash
|
|
||||||
String dbPrevGlobalHash = nn(state.getLastGlobalHash());
|
|
||||||
if (!eqHash(prevGlobalHashHex, dbPrevGlobalHash)) {
|
|
||||||
return new Result(409, "GLOBAL_HASH_MISMATCH", state, lineIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// expected line number
|
|
||||||
int expectedLineNumber = state.getLastLineNumber(lineIndex) + 1;
|
|
||||||
if (block.lineNumber != expectedLineNumber) {
|
|
||||||
return new Result(409, "OUT_OF_SEQUENCE_LINE", state, lineIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: крипто-проверка (потом подключим)
|
|
||||||
|
|
||||||
// 1) запись блока в файл (append-only)
|
|
||||||
FileStoreUtil.getInstance().addDataToBlockchain(blockchainId, block.toBytes());
|
|
||||||
|
|
||||||
// 2) апдейт state + запись state в БД
|
|
||||||
String newHashHex = bytesToHex(block.getHash32());
|
|
||||||
|
|
||||||
state.setLastGlobalNumber(globalNumber);
|
|
||||||
state.setLastGlobalHash(newHashHex);
|
|
||||||
|
|
||||||
state.setLastLineNumber(lineIndex, block.lineNumber);
|
|
||||||
state.setLastLineHash(lineIndex, newHashHex);
|
|
||||||
|
|
||||||
// Логический размер (как было)
|
|
||||||
state.setSizeBytes(state.getSizeBytes() + fullBytes.length);
|
|
||||||
|
|
||||||
// Новый: размер файла (ровно по твоему правилу)
|
|
||||||
state.setFileSizeBytes(state.getFileSizeBytes() + fullBytes.length);
|
|
||||||
|
|
||||||
state.setUpdatedAtMs(System.currentTimeMillis());
|
|
||||||
|
|
||||||
stateDao.upsert(conn, state);
|
|
||||||
|
|
||||||
return new Result(200, null, state, lineIndex);
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BlockchainStateEntry createInitialState(long blockchainId,
|
|
||||||
String login,
|
|
||||||
String loginKeyBase64,
|
|
||||||
int sizeLimit) {
|
|
||||||
BlockchainStateEntry s = new BlockchainStateEntry();
|
|
||||||
s.setBlockchainId(blockchainId);
|
|
||||||
s.setUserLogin(login);
|
|
||||||
s.setPublicKeyBase64(nn(loginKeyBase64));
|
|
||||||
|
|
||||||
s.setSizeLimit(sizeLimit);
|
|
||||||
|
|
||||||
s.setSizeBytes(0);
|
|
||||||
s.setFileSizeBytes(0);
|
|
||||||
|
|
||||||
s.setLastGlobalNumber(-1);
|
|
||||||
s.setLastGlobalHash(ZERO64);
|
|
||||||
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
if (i == 0) {
|
|
||||||
// линия 0: заглавный блок имеет lineNumber=0 -> значит "последний" до него = -1
|
|
||||||
s.setLastLineNumber(i, -1);
|
|
||||||
} else {
|
|
||||||
// остальные линии: первый блок будет lineNumber=1 -> значит "последний" до него = 0
|
|
||||||
s.setLastLineNumber(i, 0);
|
|
||||||
}
|
|
||||||
s.setLastLineHash(i, ZERO64);
|
|
||||||
}
|
|
||||||
|
|
||||||
s.setUpdatedAtMs(System.currentTimeMillis());
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int safeLimit(Integer limit) {
|
|
||||||
if (limit == null || limit <= 0) return 1_000_000; // TEST ONLY fallback
|
|
||||||
return limit;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String nn(String s) { return s == null ? "" : s; }
|
|
||||||
|
|
||||||
private static boolean eqHash(String a, String b) {
|
|
||||||
String x = nn(a).trim();
|
|
||||||
String y = nn(b).trim();
|
|
||||||
return x.equalsIgnoreCase(y);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String bytesToHex(byte[] b) {
|
|
||||||
if (b == null) return "";
|
|
||||||
StringBuilder sb = new StringBuilder(b.length * 2);
|
|
||||||
for (byte v : b) sb.append(String.format("%02x", v));
|
|
||||||
return sb.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user