1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.timezone.location.storage.block.write; 18 19 import com.android.timezone.location.storage.block.read.BlockData; 20 import com.android.timezone.location.storage.block.read.BlockInfo; 21 import com.android.timezone.location.storage.io.write.TypedOutputStream; 22 23 import java.io.File; 24 import java.io.FileInputStream; 25 import java.io.FileOutputStream; 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.nio.ByteBuffer; 29 import java.util.ArrayList; 30 import java.util.List; 31 import java.util.Objects; 32 33 /** Writes a block file. */ 34 public final class BlockFileWriter implements AutoCloseable { 35 36 private final char mMagic; 37 38 private final int mVersion; 39 40 private final List<BlockInfo> mBlockInfos = new ArrayList<>(); 41 42 private File mOutputFile; 43 44 private File mTempFile; 45 46 private TypedOutputStream mTempFileOutputStream; 47 48 private long mCurrentBlockByteCount = 0; 49 50 private int mBlockInfoExtraBytesCount; 51 BlockFileWriter(char magic, int version)52 private BlockFileWriter(char magic, int version) { 53 mMagic = magic; 54 mVersion = version; 55 } 56 57 /** 58 * Creates a {@link BlockFileWriter} ready for adding block to. The block file is created with 59 * the specified magic and version. 60 */ open(char magic, int version, File file)61 public static BlockFileWriter open(char magic, int version, File file) throws IOException { 62 BlockFileWriter writer = new BlockFileWriter(magic, version); 63 writer.open(file); 64 return writer; 65 } 66 open(File file)67 private void open(File file) throws IOException { 68 mOutputFile = file; 69 mTempFile = File.createTempFile("block", "temp"); 70 mTempFileOutputStream = new TypedOutputStream(new FileOutputStream(mTempFile)); 71 } 72 73 /** 74 * Add the supplied block info / block to the block file. Blocks written in order and assigned 75 * their ID based on their position. 76 */ addBlock(int blockType, byte[] blockInfoExtraBytes, BlockData blockData)77 public void addBlock(int blockType, byte[] blockInfoExtraBytes, BlockData blockData) 78 throws IOException { 79 Objects.requireNonNull(blockInfoExtraBytes); 80 Objects.requireNonNull(blockData); 81 82 int blockId = mBlockInfos.size(); 83 long startOfBlock = mCurrentBlockByteCount; 84 85 // Don't write anything for empty blocks. 86 if (blockData.getSize() != 0) { 87 // Write the block's header. 88 mTempFileOutputStream.writeInt(blockId); 89 mCurrentBlockByteCount += Integer.BYTES; 90 mTempFileOutputStream.writeInt(blockType); 91 mCurrentBlockByteCount += Integer.BYTES; 92 93 // Write the block's data. 94 mCurrentBlockByteCount += copyAll(blockData, mTempFileOutputStream); 95 } 96 long endOfBlock = mCurrentBlockByteCount; 97 long blockSizeBytes = endOfBlock - startOfBlock; 98 BlockInfo blockInfo = new BlockInfo( 99 blockId, blockType, startOfBlock, blockSizeBytes, blockInfoExtraBytes); 100 mBlockInfoExtraBytesCount += blockInfoExtraBytes.length; 101 mBlockInfos.add(blockInfo); 102 } 103 104 /** Finishes writing the file. Signals no more blocks are to be added. */ 105 @Override close()106 public void close() throws IOException { 107 mTempFileOutputStream.close(); 108 writeHeaderAndAppendTempFile(); 109 } 110 writeHeaderAndAppendTempFile()111 private void writeHeaderAndAppendTempFile() throws IOException { 112 try (TypedOutputStream bos = new TypedOutputStream(new FileOutputStream(mOutputFile))) { 113 // File header 114 bos.writeChar(mMagic); 115 bos.writeInt(mVersion); 116 bos.writeBytes(new byte[10]); // Reserved 117 bos.writeInt(mBlockInfos.size()); 118 119 int blockInfoLengthWithoutExtraBytes = 120 Integer.BYTES /* blockInfoSize */ 121 + Integer.BYTES /* blockType */ 122 + Long.BYTES /* blockOffset */ 123 + Long.BYTES /* blockSizeBytes */ 124 + Byte.BYTES /* extraBytesLength */; 125 int headerBytesAdjustment = 126 Character.BYTES /* magic */ 127 + Integer.BYTES /* version */ 128 + (10 * Byte.BYTES) /* reserved */ 129 + Integer.BYTES /* blockInfos.size() */ 130 + (mBlockInfos.size() * blockInfoLengthWithoutExtraBytes) 131 + mBlockInfoExtraBytesCount; 132 133 // Write block info to the TypedOutputStream. 134 for (BlockInfo blockInfo : mBlockInfos) { 135 byte[] extraBytes = blockInfo.getExtraBytes(); 136 int blockInfoSize = blockInfoLengthWithoutExtraBytes + extraBytes.length; 137 bos.writeInt(blockInfoSize); 138 bos.writeInt(blockInfo.getType()); 139 bos.writeLong(blockInfo.getBlockStartByteOffset() + headerBytesAdjustment); 140 bos.writeLong(blockInfo.getBlockSizeBytes()); 141 bos.writeTinyByteArray(extraBytes); 142 } 143 144 // Copy the block data from the temp file. 145 try (FileInputStream fis = new FileInputStream(mTempFile)) { 146 copyAll(fis, bos); 147 } 148 } 149 } 150 copyAll(InputStream inputStream, TypedOutputStream outputStream)151 private static int copyAll(InputStream inputStream, TypedOutputStream outputStream) 152 throws IOException { 153 byte[] buffer = new byte[8192]; 154 int totalByteCount = 0; 155 int bytesRead; 156 while ((bytesRead = inputStream.read(buffer)) >= 0) { 157 outputStream.writeBytes(buffer, 0, bytesRead); 158 totalByteCount += bytesRead; 159 } 160 inputStream.close(); 161 return totalByteCount; 162 } 163 copyAll(BlockData blockData, TypedOutputStream outputStream)164 private static int copyAll(BlockData blockData, TypedOutputStream outputStream) 165 throws IOException { 166 byte[] buffer = new byte[8192]; 167 ByteBuffer byteBuffer = blockData.getByteBuffer(); 168 int totalByteCount = 0; 169 while (byteBuffer.remaining() > 0) { 170 int byteCount = Math.min(buffer.length, byteBuffer.remaining()); 171 byteBuffer.get(buffer, 0, byteCount); 172 outputStream.writeBytes(buffer, 0, byteCount); 173 totalByteCount += byteCount; 174 } 175 return totalByteCount; 176 } 177 } 178