package org.fastcatsearch.transport.common; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.io.FileUtils; import org.fastcatsearch.common.Strings; import org.fastcatsearch.env.Path; import org.fastcatsearch.ir.io.DataInput; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class FileTransportHandler { private static Logger logger = LoggerFactory.getLogger(FileTransportHandler.class); private Map<String, FileStreamHandle> fileMap; private Path path; public FileTransportHandler(Path path){ fileMap = new ConcurrentHashMap<String, FileStreamHandle>(); this.path = path; } //마지막 청크기록으로 모두 끝났다면 true 전송. //IO에러 또는 체크섬 불일치 에러는 IOException를 던진다. public boolean handleFile(int seq, String filePath, long fileSize, long checksumCRC32, String fileKey, DataInput input) throws IOException { FileStreamHandle fileHandle = fileMap.get(fileKey); if(seq == 0){ //새로 생성. if(fileHandle == null){ File file = path.makePath(filePath).file(); logger.debug("## {} >> {}", filePath, file); try { // File file = new File(absolutePath); File dir = file.getParentFile(); if(dir != null){ dir.mkdirs(); } fileHandle = new FileStreamHandle(file, fileSize, checksumCRC32); fileMap.put(fileKey, fileHandle); logger.debug("File handle map size = {}", fileMap.size()); } catch (FileNotFoundException e) { logger.error("파일을 생성할수 없습니다.", e); throw new IOException("파일을 생성할수 없습니다.", e); } }else{ //발생하면 안되는 경우. logger.error("seq가 0인데 map에 동일 파일수신이 남아있습니다.seq={}, filePath={}", seq, filePath); } } if(fileHandle == null){ //null일경우는 이전 seq에서 에러발생한 경우. //더 이상 파일을 기록하지 않는다. throw new IOException("파일핸들이 없습니다."); // return; } try{ fileHandle.write(input); }catch(IOException e){ fileMap.remove(fileKey); throw e; } if(fileHandle.isDone()){ try{ fileHandle.close(); } finally { fileMap.remove(fileKey); } fileHandle.doChecksumValidation(); //모두 기록했다면 true return true; } return false; } class FileStreamHandle { String filePath; long fileSize; long checksumCRC32; File file; FileOutputStream fos; long wroteBytes; byte[] buf = new byte[1024]; long startTime = System.currentTimeMillis(); public FileStreamHandle(File file, long fileSize, long checksumCRC32) throws IOException{ this.file = file; this.filePath = file.getAbsolutePath(); this.fileSize = fileSize; this.checksumCRC32 = checksumCRC32; try { fos = new FileOutputStream(filePath); } catch (FileNotFoundException e) { logger.error("파일을 생성할수 없습니다.", e); throw new IOException("파일을 생성할수 없습니다.", e); } } public void close() throws IOException { fos.flush(); fos.close(); } public long wroteBytes(){ return wroteBytes; } public long fileSize(){ return fileSize; } public boolean isDone() { // logger.debug("isDone wrote = {}/ {}", wroteBytes, fileSize); if(wroteBytes == fileSize){ logger.info("파일수신완료. time={}, file={}", Strings.getHumanReadableTimeInterval(System.currentTimeMillis() - startTime), filePath); }else if(wroteBytes > fileSize) { logger.error("파일 사이즈가 더 큽니다. actual={}, expected={}, file={}", new Object[]{wroteBytes, fileSize, filePath}); } return wroteBytes == fileSize; } public void doChecksumValidation() throws IOException{ //checksumCRC32 long actualChecksum = FileUtils.checksumCRC32(file); if(actualChecksum != checksumCRC32){ throw new IOException("파일의 checksum이 일치하지 않습니다.expected="+checksumCRC32+", actual="+actualChecksum+", file="+filePath); }else{ logger.debug("파일검증완료. checksum={}, file={}", checksumCRC32, filePath); } } public synchronized void write(DataInput input) throws IOException { int dataLength = input.readVInt(); logger.debug("fileHandler write dataLength={}", dataLength); int nRead = 0; //파일사이즈가 0이라면 루프를 돌지 않는다. while(nRead < dataLength){ int n = input.read(buf); //읽은 데이터가 0개 인경우 재시도. if(n < 0){ logger.error("읽을 데이터가 없습니다.filePath={}", filePath); break; } fos.write(buf, 0, n); wroteBytes += n; nRead += n; } } } }