|
1 | 1 | package com.appland.appmap.record; |
2 | 2 |
|
3 | 3 | import java.io.IOException; |
| 4 | +import java.nio.charset.StandardCharsets; |
| 5 | +import java.security.MessageDigest; |
| 6 | +import java.security.NoSuchAlgorithmException; |
4 | 7 | import java.util.ArrayList; |
5 | 8 | import java.util.Iterator; |
6 | 9 | import java.util.List; |
|
13 | 16 | import com.appland.appmap.output.v1.CodeObject; |
14 | 17 | import com.appland.appmap.output.v1.Event; |
15 | 18 | import com.appland.appmap.util.Logger; |
| 19 | +import org.apache.commons.lang3.RandomStringUtils; |
16 | 20 |
|
17 | 21 | /** |
18 | 22 | * Keep track of what's going on in the current thread. |
@@ -51,14 +55,44 @@ public class Recorder { |
51 | 55 | private static final String ERROR_SESSION_PRESENT = "an active recording session already exists"; |
52 | 56 | private static final String ERROR_NO_SESSION = "there is no active recording session"; |
53 | 57 |
|
| 58 | + private static final Integer FILENAME_MAX_LENGTH = 255; // Max length of a filename on most filesystems |
| 59 | + private static final Integer HASH_LENGTH = 7; // Arbitrary but Git provides it's a reasonable value |
| 60 | + private static final String APPMAP_SUFFIX = ".appmap.json"; |
| 61 | + |
54 | 62 | private static final Recorder instance = new Recorder(); |
55 | 63 |
|
56 | 64 | private final ActiveSession activeSession = new ActiveSession(); |
57 | 65 | private final CodeObjectTree globalCodeObjects = new CodeObjectTree(); |
58 | 66 | private final Map<Long, ThreadState> threadState = new ConcurrentHashMap<>(); |
59 | 67 |
|
60 | 68 | public static String sanitizeFilename(String filename) { |
61 | | - return filename.replaceAll("[^a-zA-Z0-9-_]", "_"); |
| 69 | + String sanitizedFilename = filename.replaceAll("[^a-zA-Z0-9-_]", "_"); |
| 70 | + |
| 71 | + if (sanitizedFilename.length() > FILENAME_MAX_LENGTH - APPMAP_SUFFIX.length()) { |
| 72 | + int part = FILENAME_MAX_LENGTH - APPMAP_SUFFIX.length() - 1 - HASH_LENGTH; |
| 73 | + sanitizedFilename = sanitizedFilename.substring(0, part) + "-" + hashFilename(sanitizedFilename.substring(part)); |
| 74 | + } |
| 75 | + |
| 76 | + return sanitizedFilename + APPMAP_SUFFIX; |
| 77 | + } |
| 78 | + |
| 79 | + private static String hashFilename(String filename) { |
| 80 | + MessageDigest digest; |
| 81 | + try { |
| 82 | + digest = MessageDigest.getInstance("SHA-256"); |
| 83 | + } catch (NoSuchAlgorithmException e) { |
| 84 | + return RandomStringUtils.random(HASH_LENGTH); |
| 85 | + } |
| 86 | + byte[] hash = digest.digest(filename.getBytes(StandardCharsets.UTF_8)); |
| 87 | + StringBuilder hexString = new StringBuilder(2 * hash.length); |
| 88 | + for (byte b : hash) { |
| 89 | + String hex = Integer.toHexString(0xff & b); |
| 90 | + if (hex.length() == 1) { |
| 91 | + hexString.append('0'); |
| 92 | + } |
| 93 | + hexString.append(hex); |
| 94 | + } |
| 95 | + return hexString.substring(0, HASH_LENGTH); |
62 | 96 | } |
63 | 97 |
|
64 | 98 | /** |
@@ -363,7 +397,7 @@ public void record(String name, Runnable fn) throws ActiveSessionException, IOEx |
363 | 397 | this.start(metadata); |
364 | 398 | fn.run(); |
365 | 399 | Recording recording = this.stop(); |
366 | | - recording.moveTo(fileName + ".appmap.json"); |
| 400 | + recording.moveTo(fileName); |
367 | 401 | } |
368 | 402 |
|
369 | 403 | // Mockito can't stub methods on the Collection<ThreadState> |
|
0 commit comments