1818 */
1919package org .grails .forge .io ;
2020
21- import org .grails .forge .application .Project ;
22- import org .grails .forge .template .Template ;
23- import org .grails .forge .template .Writable ;
24-
2521import java .io .File ;
2622import java .io .IOException ;
2723import java .io .OutputStream ;
2824import java .nio .file .Files ;
25+ import java .nio .file .Path ;
26+ import java .nio .file .attribute .FileTime ;
27+ import java .time .Instant ;
28+ import java .util .ArrayList ;
29+ import java .util .List ;
30+ import java .util .Objects ;
31+
32+ import org .grails .forge .application .Project ;
33+ import org .grails .forge .template .Template ;
34+ import org .grails .forge .template .Writable ;
2935
3036public class FileSystemOutputHandler implements OutputHandler {
3137
3238 File applicationDirectory ;
3339 private final ConsoleOutput console ;
40+ private final Instant lastModified ;
3441
3542 public FileSystemOutputHandler (Project project , boolean inplace , ConsoleOutput console ) throws IOException {
3643 this .console = console ;
@@ -43,11 +50,13 @@ public FileSystemOutputHandler(Project project, boolean inplace, ConsoleOutput c
4350 if (applicationDirectory .exists () && !inplace ) {
4451 throw new IllegalArgumentException ("Cannot create the project because the target directory already exists" );
4552 }
53+ lastModified = OutputUtils .createLastModified (null );
4654 }
4755
4856 public FileSystemOutputHandler (File directory , ConsoleOutput console ) throws IOException {
4957 this .console = console ;
5058 this .applicationDirectory = directory ;
59+ lastModified = OutputUtils .createLastModified (null );
5160 }
5261
5362 /**
@@ -56,14 +65,8 @@ public FileSystemOutputHandler(File directory, ConsoleOutput console) throws IOE
5665 * @throws IOException If it cannot be resolved
5766 */
5867 public static File getDefaultBaseDirectory () throws IOException {
59- File baseDirectory ;
6068 String userDir = System .getProperty ("user.dir" );
61- if (userDir != null ) {
62- baseDirectory = new File (userDir ).getCanonicalFile ();
63- } else {
64- baseDirectory = new File ("" ).getCanonicalFile ();
65- }
66- return baseDirectory ;
69+ return new File (Objects .requireNonNullElse (userDir , "" )).getCanonicalFile ();
6770 }
6871
6972 @ Override
@@ -91,13 +94,51 @@ public File write(String path, Writable contents) throws IOException {
9194 if ('/' != File .separatorChar ) {
9295 path = path .replace ('/' , File .separatorChar );
9396 }
97+
9498 File targetFile = new File (applicationDirectory , path );
95- targetFile . getParentFile ().mkdirs ();
96- targetFile .createNewFile ();
99+ Path base = applicationDirectory . toPath ().toAbsolutePath (). normalize ();
100+ Path parent = targetFile .getParentFile (). toPath (). toAbsolutePath (). normalize ();
97101
102+ // 1) Determine which parent directories don't exist yet
103+ List <Path > createdDirs = new ArrayList <>();
104+ if (!parent .startsWith (base )) {
105+ throw new IOException ("Refusing to write outside base directory: " + parent );
106+ }
107+ Path p = base ;
108+ for (Path seg : base .relativize (parent )) {
109+ p = p .resolve (seg );
110+ if (Files .notExists (p )) {
111+ createdDirs .add (p );
112+ }
113+ }
114+
115+ // 2) Create the needed directories
116+ Files .createDirectories (parent );
117+
118+ // 3) Write the file
119+ Files .deleteIfExists (targetFile .toPath ());
98120 try (OutputStream os = Files .newOutputStream (targetFile .toPath ())) {
99121 contents .write (os );
100122 }
123+
124+ // Should we set a specific mtime (SOURCE_DATE_EPOCH)
125+ if (lastModified != null ) {
126+ // 4) Set the file mtime
127+ FileTime mtime = FileTime .from (lastModified );
128+ Files .setLastModifiedTime (targetFile .toPath (), mtime );
129+
130+ // 5) Set the mtime on only the directories we created
131+ // Do this after writing the file, since step 3 bumps the parent dir's mtime.
132+ for (int i = createdDirs .size () - 1 ; i >= 0 ; i --) {
133+ try {
134+ Files .setLastModifiedTime (createdDirs .get (i ), mtime );
135+ } catch (IOException ignore ) {
136+ // Non-fatal: some file systems may restrict touching dir times
137+ console .warning ("Could not set mtime for dir: " + createdDirs .get (i ));
138+ }
139+ }
140+ }
141+
101142 return targetFile ;
102143 }
103144
0 commit comments