11/*
2- * SonarScanner Java Library
2+ * SonarScanner Download Cache Utility
33 * Copyright (C) 2011-2025 SonarSource SA
44 * mailto:info AT sonarsource DOT com
55 *
1717 * along with this program; if not, write to the Free Software Foundation,
1818 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919 */
20- package org .sonarsource .scanner .lib . internal . cache ;
20+ package org .sonarsource .scanner .downloadcache ;
2121
2222import java .io .IOException ;
2323import java .nio .file .AtomicMoveNotSupportedException ;
2424import java .nio .file .FileAlreadyExistsException ;
2525import java .nio .file .Files ;
2626import java .nio .file .Path ;
2727import java .nio .file .StandardCopyOption ;
28- import javax . annotation . CheckForNull ;
28+ import java . util . Optional ;
2929import org .slf4j .Logger ;
3030import org .slf4j .LoggerFactory ;
3131
3232/**
33- * This class is responsible for managing Sonar batch file cache. You can put file into cache and
34- * later try to retrieve them. The checksum is used to differentiate files (name is not secure as files may come
35- * from different Sonar servers and have same name but be actually different, and same for SNAPSHOTs ).
33+ * This class is responsible for managing Sonar Scanner download cache. You can download files into the cache and
34+ * later try to retrieve them. The file hash is used as the cache index (name is not reliable as files may come
35+ * from different SonarQube servers and have the same name but be actually different).
3636 */
37- public class FileCache {
37+ public class DownloadCache {
3838
39- private static final Logger LOG = LoggerFactory .getLogger (FileCache .class );
39+ private static final Logger LOG = LoggerFactory .getLogger (DownloadCache .class );
4040
41- private final Path dir ;
41+ private final Path baseDir ;
4242 private final Path tmpDir ;
4343 private final FileHashes hashes ;
4444
45- FileCache (Path dir , FileHashes fileHashes ) {
45+ DownloadCache (Path baseDir , FileHashes fileHashes ) {
46+ LOG .debug ("Download cache base directory: {}" , baseDir );
4647 this .hashes = fileHashes ;
47- this .dir = createDir (dir , "user cache: " );
48- LOG .info ("User cache: {}" , dir );
49- this .tmpDir = createDir (dir .resolve ("_tmp" ), "temp dir" );
48+ this .baseDir = mkdirs (baseDir );
49+ this .tmpDir = mkdirs (baseDir .resolve ("_tmp" ));
5050 }
5151
52- public static FileCache create (Path sonarUserHome ) {
53- var dir = sonarUserHome .resolve ("cache" );
54- return new FileCache (dir , new FileHashes ());
52+ public DownloadCache (Path baseDir ) {
53+ this (baseDir , new FileHashes ());
5554 }
5655
57- public Path getDir () {
58- return dir ;
56+ public Path getBaseDir () {
57+ return baseDir ;
5958 }
6059
6160 /**
62- * Look for a file in the cache by its filename and checksum . If the file is not
63- * present then return null .
61+ * Look for a file in the cache by its filename and hash . If the file is not
62+ * present, then return empty .
6463 */
65- @ CheckForNull
66- public Path get (String filename , String hash ) {
67- Path cachedFile = dir .resolve (hash ).resolve (filename );
64+ public Optional <Path > get (String filename , String hash ) {
65+ Path cachedFile = hashDir (hash ).resolve (filename );
6866 if (Files .exists (cachedFile )) {
69- return cachedFile ;
67+ return Optional . of ( cachedFile ) ;
7068 }
71- LOG .debug ("No file found in the cache with name {} and hash {}" , filename , hash );
72- return null ;
73- }
74-
75- @ FunctionalInterface
76- public interface Downloader {
77- void download (String filename , Path toFile ) throws IOException ;
69+ return Optional .empty ();
7870 }
7971
80- public CachedFile getOrDownload (String filename , String hash , String hashAlgorithm , Downloader downloader ) {
72+ public CachedFile getOrDownload (String filename , String expectedFileHash , String hashAlgorithm , Downloader downloader ) throws HashMismatchException {
8173 // Does not fail if another process tries to create the directory at the same time.
82- Path hashDir = hashDir (hash );
74+ Path hashDir = hashDir (expectedFileHash );
8375 Path targetFile = hashDir .resolve (filename );
8476 if (Files .exists (targetFile )) {
8577 return new CachedFile (targetFile , true );
8678 }
87- Path tempFile = newTempFile ();
79+ Path tempFile = newTempFile (filename );
8880 download (downloader , filename , tempFile );
89- String downloadedHash = hashes .of (tempFile .toFile (), hashAlgorithm );
90- if (!hash .equals (downloadedHash )) {
91- throw new HashMismatchException ("INVALID HASH: File " + tempFile .toAbsolutePath () + " was expected to have hash " + hash
92- + " but was downloaded with hash " + downloadedHash );
81+ String downloadedFileHash = hashes .of (tempFile .toFile (), hashAlgorithm );
82+ if (!expectedFileHash .equals (downloadedFileHash )) {
83+ throw new HashMismatchException (expectedFileHash , downloadedFileHash , tempFile .toAbsolutePath ());
9384 }
94- mkdirQuietly (hashDir );
85+ mkdirs (hashDir );
9586 renameQuietly (tempFile , targetFile );
9687 return new CachedFile (targetFile , false );
9788 }
@@ -113,41 +104,42 @@ private static void renameQuietly(Path sourceFile, Path targetFile) {
113104 try {
114105 Files .move (sourceFile , targetFile );
115106 } catch (IOException e ) {
116- throw new IllegalStateException ("Fail to move " + sourceFile .toAbsolutePath () + " to " + targetFile , e );
107+ throw new IllegalStateException ("Failed to move " + sourceFile .toAbsolutePath () + " to " + targetFile , e );
117108 }
118109 } catch (FileAlreadyExistsException e ) {
119- // File was probably cached by another process in the mean time
110+ // File was probably cached by another process in the meantime
120111 } catch (IOException e ) {
121- throw new IllegalStateException ("Fail to move " + sourceFile .toAbsolutePath () + " to " + targetFile , e );
112+ throw new IllegalStateException ("Failed to move " + sourceFile .toAbsolutePath () + " to " + targetFile , e );
122113 }
123114 }
124115
125116 private Path hashDir (String hash ) {
126- return dir .resolve (hash );
117+ return baseDir .resolve (hash );
127118 }
128119
129- private static void mkdirQuietly (Path hashDir ) {
130- try {
131- Files .createDirectories (hashDir );
132- } catch (IOException e ) {
133- throw new IllegalStateException ("Fail to create cache directory: " + hashDir , e );
120+ private Path newTempFile (String filename ) {
121+ int dotLocation = filename .lastIndexOf ("." );
122+ String prefix = filename ;
123+ String suffix = null ;
124+ if (dotLocation > 0 ) {
125+ prefix = filename .substring (0 , dotLocation );
126+ suffix = filename .substring (dotLocation + 1 );
134127 }
135- }
136-
137- private Path newTempFile () {
138128 try {
139- return Files .createTempFile (tmpDir , "fileCache" , null );
129+ return Files .createTempFile (tmpDir , prefix , suffix );
140130 } catch (IOException e ) {
141131 throw new IllegalStateException ("Fail to create temp file in " + tmpDir , e );
142132 }
143133 }
144134
145- private static Path createDir (Path dir , String debugTitle ) {
146- LOG .debug ("Create: {}" , dir );
147- try {
148- Files .createDirectories (dir );
149- } catch (IOException e ) {
150- throw new IllegalStateException ("Unable to create " + debugTitle + dir .toString (), e );
135+ private static Path mkdirs (Path dir ) {
136+ if (!Files .isDirectory (dir )) {
137+ LOG .debug ("Create: {}" , dir );
138+ try {
139+ Files .createDirectories (dir );
140+ } catch (IOException e ) {
141+ throw new IllegalStateException ("Unable to create directory: " + dir , e );
142+ }
151143 }
152144 return dir ;
153145 }
0 commit comments