4040import java .nio .channels .ReadableByteChannel ;
4141import java .nio .file .FileVisitResult ;
4242import java .nio .file .Files ;
43+ import java .nio .file .InvalidPathException ;
4344import java .nio .file .Path ;
4445import java .nio .file .Paths ;
4546import java .nio .file .SimpleFileVisitor ;
4647import java .nio .file .attribute .BasicFileAttributes ;
4748import java .security .MessageDigest ;
4849import java .security .NoSuchAlgorithmException ;
4950import java .util .Locale ;
51+ import java .util .regex .Pattern ;
5052import java .util .zip .ZipEntry ;
5153import java .util .zip .ZipInputStream ;
5254
@@ -62,17 +64,85 @@ public class LocalGcdHelper {
6264 public static final String DEFAULT_PROJECT_ID = "projectid1" ;
6365 public static final int PORT = 8080 ;
6466 private static final String GCD = "gcd-v1beta2-rev1-2.1.2b" ;
67+ private static final String GCD_VERSION = "v1beta2" ;
6568 private static final String GCD_FILENAME = GCD + ".zip" ;
6669 private static final String MD5_CHECKSUM = "d84384cdfa8658e1204f4f8be51300e8" ;
6770 private static final URL GCD_URL ;
71+ private static final String GCLOUD = "gcloud" ;
72+ private static final Path INSTALLED_GCD_PATH ;
6873
6974 static {
70- try {
71- GCD_URL = new URL ("http://storage.googleapis.com/gcd/tools/" + GCD_FILENAME );
72- } catch (MalformedURLException e ) {
73- throw new RuntimeException (e );
75+ INSTALLED_GCD_PATH = installedGcdPath ();
76+ if (INSTALLED_GCD_PATH != null ) {
77+ GCD_URL = null ;
78+ } else {
79+ try {
80+ GCD_URL = new URL ("http://storage.googleapis.com/gcd/tools/" + GCD_FILENAME );
81+ } catch (MalformedURLException e ) {
82+ throw new RuntimeException (e );
83+ }
7484 }
7585 }
86+
87+ private static Path installedGcdPath () {
88+ Path gcloudPath = executablePath (GCLOUD );
89+ gcloudPath = (gcloudPath == null ) ? null : gcloudPath .getParent ();
90+ if (gcloudPath == null ) {
91+ return null ;
92+ }
93+ Path installedGcdPath = gcloudPath .resolve ("platform" ).resolve ("gcd" );
94+ if (Files .exists (installedGcdPath )) {
95+ try {
96+ String installedVersion = installedGcdVersion ();
97+ if (installedVersion != null && installedVersion .startsWith (GCD_VERSION )) {
98+ return installedGcdPath ;
99+ }
100+ } catch (IOException | InterruptedException ignore ) {
101+ // ignore
102+ }
103+ }
104+ return null ;
105+ }
106+
107+ private static String installedGcdVersion () throws IOException , InterruptedException {
108+ ProcessBuilder processBuilder = new ProcessBuilder ()
109+ .redirectErrorStream (true );
110+ if (isWindows ()) {
111+ processBuilder .command ("cmd" , "/C" , "gcloud" , "version" );
112+ } else {
113+ processBuilder .command ("bash" , "gcloud" , "version" );
114+ }
115+ Process process = processBuilder .start ();
116+ process .waitFor ();
117+ try (BufferedReader reader =
118+ new BufferedReader (new InputStreamReader (process .getInputStream ()))) {
119+ for (String line = reader .readLine (); line != null ; line = reader .readLine ()) {
120+ if (line .startsWith ("gcd-emulator" )) {
121+ String [] lineComponents = line .split (" " );
122+ if (lineComponents .length > 1 ) {
123+ return lineComponents [1 ];
124+ }
125+ }
126+ }
127+ return null ;
128+ }
129+
130+ }
131+
132+ private static Path executablePath (String cmd ) {
133+ String [] paths = System .getenv ("PATH" ).split (Pattern .quote (File .pathSeparator ));
134+ for (String pathString : paths ) {
135+ try {
136+ Path path = Paths .get (pathString );
137+ if (Files .exists (path .resolve (cmd ))) {
138+ return path ;
139+ }
140+ } catch (InvalidPathException ignore ) {
141+ // ignore
142+ }
143+ }
144+ return null ;
145+ }
76146
77147 private static class ProcessStreamReader extends Thread {
78148
@@ -136,6 +206,18 @@ public void start() throws IOException, InterruptedException {
136206 File gcdFolder = gcdPath .toFile ();
137207 gcdFolder .deleteOnExit ();
138208
209+ Path gcdExecutablePath ;
210+ // If cloud is available we use it, otherwise we download and start gcd
211+ if (INSTALLED_GCD_PATH == null ) {
212+ downloadGcd ();
213+ gcdExecutablePath = gcdPath .resolve (GCD );
214+ } else {
215+ gcdExecutablePath = INSTALLED_GCD_PATH ;
216+ }
217+ startGcd (gcdExecutablePath );
218+ }
219+
220+ private void downloadGcd () throws IOException {
139221 // check if we already have a local copy of the gcd utility and download it if not.
140222 File gcdZipFile = new File (System .getProperty ("java.io.tmpdir" ), GCD_FILENAME );
141223 if (!gcdZipFile .exists () || !MD5_CHECKSUM .equals (md5 (gcdZipFile ))) {
@@ -148,7 +230,7 @@ public void start() throws IOException, InterruptedException {
148230 try (ZipInputStream zipIn = new ZipInputStream (new FileInputStream (gcdZipFile ))) {
149231 ZipEntry entry = zipIn .getNextEntry ();
150232 while (entry != null ) {
151- File filePath = new File (gcdFolder , entry .getName ());
233+ File filePath = new File (gcdPath . toFile () , entry .getName ());
152234 if (!entry .isDirectory ()) {
153235 extractFile (zipIn , filePath );
154236 } else {
@@ -157,36 +239,43 @@ public void start() throws IOException, InterruptedException {
157239 zipIn .closeEntry ();
158240 entry = zipIn .getNextEntry ();
159241 }
160- }
242+ }
243+ }
244+
245+ private void startGcd (Path executablePath ) throws IOException , InterruptedException {
161246 // cleanup any possible data for the same project
162- File datasetFolder = new File (gcdFolder , GCD + '/' + projectId );
247+ File datasetFolder = new File (gcdPath . toFile (), projectId );
163248 deleteRecurse (datasetFolder .toPath ());
164249
165250 // create the datastore for the project
166251 ProcessBuilder processBuilder = new ProcessBuilder ()
167252 .redirectError (ProcessBuilder .Redirect .INHERIT )
168- .directory (new File ( gcdFolder , GCD ));
253+ .directory (gcdPath . toFile ( ));
169254 if (isWindows ()) {
170- processBuilder .command ("cmd" , "/C" , "gcd.cmd" , "create" , "-p" , projectId , projectId );
171- processBuilder .redirectOutput (new File ("NULL:" ));
255+ Path gcdAbsolutePath = executablePath .toAbsolutePath ().resolve ("gcd.cmd" );
256+ processBuilder .command ("cmd" , "/C" , gcdAbsolutePath .toString (), "create" ,
257+ "-p" , projectId , projectId );
172258 } else {
173- processBuilder .redirectOutput (new File ("/dev/null" ));
174- processBuilder .command ("bash" , "gcd.sh" , "create" , "-p" , projectId , projectId );
259+ Path gcdAbsolutePath = executablePath .toAbsolutePath ().resolve ("gcd.sh" );
260+ processBuilder .command ("bash" , gcdAbsolutePath .toString (), "create" ,
261+ "-p" , projectId , projectId );
175262 }
176263
177264 Process temp = processBuilder .start ();
178265 temp .waitFor ();
179266
180267 // start the datastore for the project
181268 processBuilder = new ProcessBuilder ()
182- .directory (new File ( gcdFolder , GCD ))
269+ .directory (gcdPath . toFile ( ))
183270 .redirectErrorStream (true );
184271 if (isWindows ()) {
185- processBuilder .command ("cmd" , "/C" , "gcd.cmd" , "start" , "--testing" ,
272+ Path gcdAbsolutePath = executablePath .toAbsolutePath ().resolve ("gcd.cmd" );
273+ processBuilder .command ("cmd" , "/C" , gcdAbsolutePath .toString (), "start" , "--testing" ,
186274 "--allow_remote_shutdown" , projectId );
187275 } else {
188- processBuilder .command ("bash" , "gcd.sh" , "start" , "--testing" , "--allow_remote_shutdown" ,
189- projectId );
276+ Path gcdAbsolutePath = executablePath .toAbsolutePath ().resolve ("gcd.sh" );
277+ processBuilder .command ("bash" , gcdAbsolutePath .toString (), "start" , "--testing" ,
278+ "--allow_remote_shutdown" , projectId );
190279 }
191280 temp = processBuilder .start ();
192281 processReader = ProcessStreamReader .start (temp , "Dev App Server is now running" );
0 commit comments