11import java .io .File
2- import java .net .{ HttpURLConnection , URL , UnknownHostException }
2+ import java .net .URLClassLoader
33import java .nio .file .Paths
44
5- import scala .sys .process ._
6-
75/**
86 * The bootstrap launcher downloads all required libraries and starts chat overflow with the correct parameters.
97 */
@@ -18,6 +16,10 @@ object Bootstrap {
1816 // Chat Overflow Launcher / Main class (should not change anymore)
1917 val chatOverflowMainClass = " org.codeoverflow.chatoverflow.Launcher"
2018
19+ val classloader = new URLClassLoader (
20+ new File (" bin" ).listFiles().filter(_.getName.endsWith(" .jar" )).map(_.toURI.toURL)
21+ )
22+
2123 /**
2224 * Software entry point
2325 *
@@ -29,13 +31,14 @@ object Bootstrap {
2931 if (testValidity()) {
3032 println(" Valid ChatOverflow installation. Checking libraries..." )
3133
32- if (checkLibraries(args)) {
34+ val deps = DependencyDownloader .fetchDependencies().map(u => new File (u.getFile))
35+ if (deps.nonEmpty) {
3336 val javaPath = createJavaPath()
3437 if (javaPath.isDefined) {
3538 println(" Found java installation. Starting ChatOverflow..." )
3639
3740 // Start chat overflow!
38- val process = new java.lang.ProcessBuilder (javaPath.get, " -cp" , s " bin/* ${File .pathSeparator} lib/* " , chatOverflowMainClass)
41+ val process = new java.lang.ProcessBuilder (javaPath.get, " -cp" , s " bin/* ${deps.mkString( File .pathSeparator, File .pathSeparator, " " )} " , chatOverflowMainClass)
3942 .inheritIO().start()
4043
4144 val exitCode = process.waitFor()
@@ -56,7 +59,7 @@ object Bootstrap {
5659 *
5760 * @return the path to the java runtime or none, if the file was not found
5861 */
59- def createJavaPath (): Option [String ] = {
62+ private def createJavaPath (): Option [String ] = {
6063
6164 // Check validity of java.home path first
6265 if (! new File (javaHomePath).exists()) {
@@ -80,204 +83,6 @@ object Bootstrap {
8083
8184 }
8285
83- /**
84- * Checks if the library folder exists or the reload-flag is set. Triggers the download-process if libraries are missing.
85- *
86- * @param args the args, the launcher has been called with
87- * @return false, if there is a serious problem
88- */
89- def checkLibraries (args : Array [String ]): Boolean = {
90-
91- val libFolder = new File (s " $currentFolderPath/lib " )
92-
93- // Create folder for libs if missing
94- if (! libFolder.exists()) {
95- try {
96- libFolder.mkdir()
97- } catch {
98- case e : Exception => println(s " Unable to create library directory. Message: ${e.getMessage}" )
99- return false
100- }
101- }
102-
103- // --reload flags instructs to delete all downloaded libraries and to re-download them
104- if (args.contains(" --reload" )) {
105- for (libFile <- libFolder.listFiles()) {
106- try {
107- libFile.delete()
108- } catch {
109- case e : Exception => println(s " Unable to delete file ' ${libFile.getName}'. Message: ${e.getMessage}" )
110- return false
111- }
112- }
113- }
114-
115- val dependencies = getDependencies
116-
117- // Download all libraries
118- // try downloading libs and only if it succeeded (returned true) then try to delete older libs
119- downloadMissingLibraries(dependencies) && deleteUndesiredLibraries(dependencies)
120- }
121-
122- /**
123- * Reads the dependency xml file and tries to download every library that is missing.
124- *
125- * @return false, if there is a serious problem
126- */
127- private def downloadMissingLibraries (dependencies : List [(String , String )]): Boolean = {
128- val pb = new ProgressBar (dependencies.length)
129-
130- // using par here to make multiple http requests in parallel, otherwise its awfully slow on internet connections with high RTTs
131- val missing = dependencies.par.filterNot(dep => {
132- val (name, url) = dep
133- pb.countUp()
134- pb.updateDescription(s " $name@ $url" )
135-
136- isLibraryDownloaded(url)
137- }).toList
138-
139- pb.finish()
140-
141- if (missing.isEmpty) {
142- println(" All required libraries are already downloaded." )
143- } else {
144- println(s " Downloading ${missing.length} missing libraries... " )
145-
146- val pb = new ProgressBar (missing.length)
147-
148- for ((name, url) <- missing) {
149- pb.countUp()
150- pb.updateDescription(s " $name@ $url" )
151-
152- if (! downloadLibrary(name, url)) {
153- // Second try, just in case
154- if (! downloadLibrary(name, url)) {
155- return false // error has been displayed, stop bootstrapper from starting with missing lib
156- }
157- }
158- }
159-
160- pb.finish()
161- }
162- true // everything went fine
163- }
164-
165- /**
166- * Deletes all undesired libraries. Currently these are all libs that aren't on the list of dependencies.
167- * The main responsibility is to delete old libs that got updated or libs that aren't required anymore by Chat Overflow.
168- *
169- * @param dependencies the libs that should be kept
170- * @return false, if a file couldn't be deleted
171- */
172- private def deleteUndesiredLibraries (dependencies : List [(String , String )]): Boolean = {
173- val libDir = new File (s " $currentFolderPath/lib " )
174- if (libDir.exists() && libDir.isDirectory) {
175- // Desired filenames
176- val libraryFilenames = dependencies.map(d => libraryFile(d._2).getName)
177-
178- val undesiredFiles = libDir.listFiles().filterNot(file => libraryFilenames.contains(file.getName)) // filter out libs on the dependency list
179-
180- // Count errors while trying to remove undesired files
181- val errorCount = undesiredFiles.count(file => {
182- println(s " Deleting old or unnecessary library at $file" )
183- if (file.delete()) {
184- false // no error
185- } else {
186- println(s " Error: Couldn't delete file $file. " )
187- true // error
188- }
189- })
190- errorCount == 0 // return false if at least one error occurred
191- } else {
192- // Shouldn't be possible, because this is called from checkLibraries, which creates this directory.
193- true
194- }
195- }
196-
197- /**
198- * Downloads a specified library
199- */
200- private def downloadLibrary (libraryName : String , libraryURL : String ): Boolean = {
201- val url = new URL (libraryURL)
202-
203- try {
204- val connection = url.openConnection().asInstanceOf [HttpURLConnection ]
205- connection.setConnectTimeout(3000 )
206- connection.setReadTimeout(3000 )
207- connection.connect()
208-
209- if (connection.getResponseCode >= 400 ) {
210- println(" Error: Unable to download library." )
211- false
212- }
213- else {
214- // Save file in the lib folder (keeping the name and type)
215- try {
216- url #> libraryFile(libraryURL) !!
217-
218- true
219- } catch {
220- case e : Exception =>
221- println(s " Error: Unable to save library. Message: ${e.getMessage}" )
222- false
223- } finally {
224- connection.disconnect()
225- }
226- }
227- } catch {
228- case e : UnknownHostException =>
229- println(s " Error. Unable to connect to the url ' $url'. Message: ${e.getMessage}" )
230- false
231- }
232- }
233-
234- /**
235- * Gets all required dependencies from the dependencies.xml in the jar file
236- *
237- * @return a list of tuples that contain the name (e.g. log4j) without org or version and the url.
238- */
239- private def getDependencies : List [(String , String )] = {
240- val stream = getClass.getResourceAsStream(" /dependencies.xml" )
241- val depXml = xml.XML .load(stream)
242- val dependencies = depXml \\ " dependency"
243- val dependencyTuples = dependencies.map(dep => {
244- val name = (dep \ " name" ).text.trim
245- val url = (dep \ " url" ).text.trim
246- (name, url)
247- })
248-
249- dependencyTuples.toList
250- }
251-
252- /**
253- * Checks whether this library is fully downloaded
254- *
255- * @param libraryURL the url of the library
256- * @return true if it is completely downloaded, false if only partially downloaded or not downloaded at all
257- */
258- private def isLibraryDownloaded (libraryURL : String ): Boolean = {
259- val f = libraryFile(libraryURL)
260-
261- if (! f.exists()) {
262- false
263- } else {
264- try {
265- // We assume here that the libs don't change at the repo.
266- // While this is true for Maven Central, which is immutable once a file has been uploaded, its not for JCenter.
267- // Updating a released artifact generally isn't valued among developers
268- // and the odds of the updated artifact having the same size is very unlikely.
269- val url = new URL (libraryURL)
270- url.openConnection().getContentLengthLong == f.length()
271- } catch {
272- case _ : Exception => false
273- }
274- }
275- }
276-
277- private def libraryFile (libraryURL : String ): File = {
278- new File (s " $currentFolderPath/lib/ ${libraryURL.substring(libraryURL.lastIndexOf(" /" ))}" )
279- }
280-
28186 /**
28287 * Checks, if the installation is valid
28388 */
0 commit comments