@@ -34,11 +34,6 @@ object Bootstrap {
34
34
if (javaPath.isDefined) {
35
35
println(" Found java installation. Starting ChatOverflow..." )
36
36
37
- // Create config folder, if not existent
38
- if (! new File (" config/" ).exists()) {
39
- new File (" config/" ).mkdir()
40
- }
41
-
42
37
// Start chat overflow!
43
38
val process = new java.lang.ProcessBuilder (javaPath.get, " -cp" , s " bin/* ${File .pathSeparator}lib/* " , chatOverflowMainClass)
44
39
.inheritIO().start()
@@ -85,69 +80,118 @@ object Bootstrap {
85
80
}
86
81
87
82
/**
88
- * Checks if the library folder exists or the reload-flag is set. Triggers the download-process.
83
+ * Checks if the library folder exists or the reload-flag is set. Triggers the download-process if libraries are missing .
89
84
*
90
85
* @param args the args, the launcher has been called with
91
86
* @return false, if there is a serious problem
92
87
*/
93
88
def checkLibraries (args : Array [String ]): Boolean = {
94
89
95
- // TODO: Someday in the future, we need incremental library checking to manage updates without full download
96
-
97
90
val libFolder = new File (s " $currentFolderPath/lib " )
98
- // Args contains --reload or lib folder is non existent?
99
- if ((args.length > 0 && args.head == " --reload" ) || ! libFolder.exists()) {
100
-
101
- // Create or clean directory
102
- if (libFolder.exists()) {
103
- for (libFile <- libFolder.listFiles()) {
104
- try {
105
- libFile.delete()
106
- } catch {
107
- case e : Exception => println(s " Unable to delete file ' ${libFile.getName}'. Message: ${e.getMessage}" )
108
- }
109
- }
110
- } else {
91
+
92
+ // Create folder for libs if missing
93
+ if (! libFolder.exists()) {
94
+ try {
95
+ libFolder.mkdir()
96
+ } catch {
97
+ case e : Exception => println(s " Unable to create library directory. Message: ${e.getMessage}" )
98
+ return false
99
+ }
100
+ }
101
+
102
+ // --reload flags instructs to delete all downloaded libraries and to re-download them
103
+ if (args.contains(" --reload" )) {
104
+ for (libFile <- libFolder.listFiles()) {
111
105
try {
112
- libFolder.mkdir ()
106
+ libFile.delete ()
113
107
} catch {
114
- case e : Exception => println(s " Unable to create library directory. Message: ${e.getMessage}" )
108
+ case e : Exception => println(s " Unable to delete file ' ${libFile.getName}'. Message: ${e.getMessage}" )
109
+ return false
115
110
}
116
111
}
112
+ }
117
113
118
- // Download all libraries
119
- // TODO: Check validity if everything is downloaded
120
- println(" Downloading libraries..." )
121
- downloadLibraries()
114
+ val dependencies = getDependencies
122
115
123
- } else {
124
- println( " Found libraries folder. Assuming all dependencies are available properly. " )
125
- true
126
- }
116
+ // Download all libraries
117
+ // TODO: Check validity if everything is downloaded
118
+ // try downloading libs and only if it succeeded (returned true) then try to delete older libs
119
+ downloadMissingLibraries(dependencies) && deleteUndesiredLibraries(dependencies)
127
120
}
128
121
129
122
/**
130
- * Reads the dependency xml file and tries to download every library.
123
+ * Reads the dependency xml file and tries to download every library that is missing .
131
124
*
132
125
* @return false, if there is a serious problem
133
126
*/
134
- def downloadLibraries (): Boolean = {
135
-
136
- // Get dependency xml and read dependencies with their download URL
137
- val dependencyStream = getClass.getResourceAsStream(" /dependencies.xml" )
138
- val dependencyXML = xml.XML .load(dependencyStream)
139
- val dependencies = for (dependency <- dependencyXML \\ " dependency" )
140
- yield ((dependency \ " name" ).text.trim, (dependency \ " url" ).text.trim)
141
-
142
- for (i <- dependencies.indices) {
143
- val dependency = dependencies(i)
144
- println(s " [ ${i + 1 }/ ${dependencies.length}] ${dependency._1} ( ${dependency._2}) " )
145
- if (! downloadLibrary(dependency._1, dependency._2)) {
146
- // Second try, just in case
147
- downloadLibrary(dependency._1, dependency._2)
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
+ }
148
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
149
194
}
150
- true
151
195
}
152
196
153
197
/**
@@ -169,7 +213,7 @@ object Bootstrap {
169
213
else {
170
214
// Save file in the lib folder (keeping the name and type)
171
215
try {
172
- url #> new File ( s " $currentFolderPath /lib/ ${ libraryURL.substring(libraryURL.lastIndexOf( " / " ))} " ) !!
216
+ url #> libraryFile( libraryURL) !!
173
217
174
218
true
175
219
} catch {
@@ -187,6 +231,53 @@ object Bootstrap {
187
231
}
188
232
}
189
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
+
190
281
/**
191
282
* Checks, if the installation is valid
192
283
*/
0 commit comments