| 2244 | |
| 2245 | == Kotlin == |
| 2246 | Kotlin implementation by taohid32[at]gmail com |
| 2247 | {{{ |
| 2248 | import java.io.* |
| 2249 | import java.nio.ByteBuffer |
| 2250 | import java.nio.ByteOrder |
| 2251 | import java.nio.channels.FileChannel |
| 2252 | import kotlin.math.max |
| 2253 | import kotlin.math.min |
| 2254 | |
| 2255 | class MovieHashUtil { |
| 2256 | |
| 2257 | companion object { |
| 2258 | private const val HASH_CHUNK_SIZE = 64 * 1024 |
| 2259 | } |
| 2260 | |
| 2261 | @Throws(IOException::class) |
| 2262 | fun computeHash(file: File): String { |
| 2263 | val size = file.length() |
| 2264 | val chunkSizeForFile = min(HASH_CHUNK_SIZE.toLong(), size) |
| 2265 | FileInputStream(file).channel.use { fileChannel -> |
| 2266 | val head = computeChunkHash( |
| 2267 | fileChannel.map( |
| 2268 | FileChannel.MapMode.READ_ONLY, |
| 2269 | 0, |
| 2270 | chunkSizeForFile |
| 2271 | ) |
| 2272 | ) |
| 2273 | val tail = computeChunkHash( |
| 2274 | fileChannel.map( |
| 2275 | FileChannel.MapMode.READ_ONLY, |
| 2276 | max(size - HASH_CHUNK_SIZE, 0), |
| 2277 | chunkSizeForFile |
| 2278 | ) |
| 2279 | ) |
| 2280 | return String.format("%016x", size + head + tail) |
| 2281 | } |
| 2282 | } |
| 2283 | |
| 2284 | @Throws(IOException::class) |
| 2285 | fun computeHash(stream: InputStream?, length: Long): String { |
| 2286 | val chunkSizeForFile = min(HASH_CHUNK_SIZE.toLong(), length) |
| 2287 | .toInt() |
| 2288 | |
| 2289 | // buffer that will contain the head and the tail chunk, chunks will overlap if length is smaller than two chunks |
| 2290 | val chunkBytes = ByteArray( |
| 2291 | min(2 * HASH_CHUNK_SIZE.toLong(), length).toInt() |
| 2292 | ) |
| 2293 | val inputStream = DataInputStream(stream) |
| 2294 | |
| 2295 | // first chunk |
| 2296 | inputStream.readFully(chunkBytes, 0, chunkSizeForFile) |
| 2297 | var position = chunkSizeForFile.toLong() |
| 2298 | val tailChunkPosition = length - chunkSizeForFile |
| 2299 | |
| 2300 | // seek to position of the tail chunk, or not at all if length is smaller than two chunks |
| 2301 | while (position < tailChunkPosition && inputStream.skip(tailChunkPosition - position) |
| 2302 | .let { position += it; position } >= 0 |
| 2303 | ); |
| 2304 | |
| 2305 | // second chunk, or the rest of the data if length is smaller than two chunks |
| 2306 | inputStream.readFully(chunkBytes, chunkSizeForFile, chunkBytes.size - chunkSizeForFile) |
| 2307 | val head = computeChunkHash(ByteBuffer.wrap(chunkBytes, 0, chunkSizeForFile)) |
| 2308 | val tail = computeChunkHash( |
| 2309 | ByteBuffer.wrap( |
| 2310 | chunkBytes, |
| 2311 | chunkBytes.size - chunkSizeForFile, |
| 2312 | chunkSizeForFile |
| 2313 | ) |
| 2314 | ) |
| 2315 | return String.format("%016x", length + head + tail) |
| 2316 | } |
| 2317 | |
| 2318 | private fun computeChunkHash(buffer: ByteBuffer): Long { |
| 2319 | val longBuffer = buffer.order(ByteOrder.LITTLE_ENDIAN).asLongBuffer() |
| 2320 | var hash: Long = 0 |
| 2321 | while (longBuffer.hasRemaining()) { |
| 2322 | hash += longBuffer.get() |
| 2323 | } |
| 2324 | return hash |
| 2325 | } |
| 2326 | |
| 2327 | } |
| 2328 | }}} |