forked from BIOP/biop-bash-scripts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
InstallQuPathExtension.groovy
253 lines (230 loc) · 8.16 KB
/
InstallQuPathExtension.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/**
* Fiji script installing a QuPath extension (QuPath v0.3)
*
* Yes it looks weird but it makes sense, because modifying QuPath jars from within QuPath creates issues:
* jars files are in use, so they can't be deleted.
*
* Also, QuPath uses Java user preferences, which can be easily accessed for any Java software (like Fiji!)
* Thus, this script can look whether a QuPath user path is already defined, and adds the jars
* necessary for a QuPath extension into it.
*
* If the preference of a QuPath user folder does not exist, it it created and will be recognized
* when QuPath is installed
*
* For this to work, the QuPath extension has to be accessible via downloading through a single URL
*
* If the QuPath extension is a single jar, it is downloaded, no problem
* If the QuPath extension consists of multiple jars, the URL has to point towards a zip file
* the zip file should contains all jars dependencies in a flat hierarchy
*
* This script keeps only the latest versions of installed jars BUT it will not remove jars
* which have become useless / obsolete.
*
* It will make this jar filtering and removal for all jars present in the user QuPath extension folder
*
* Example of url extension which can be installed with this script:
*
* BIOP extensions:
* https://github.com/BIOP/qupath-extension-biop/releases/download/v1.0.2/qupath-extension-biop-1.0.2.jar
*
* Warpy:
* https://github.com/BIOP/qupath-extension-warpy/releases/download/0.2.0/qupath-extension-warpy-0.2.0.zip
*
* ABBA:
* https://github.com/BIOP/qupath-extension-abba/releases/download/0.1.1/qupath-extension-abba-0.1.1.zip
*
* Cellpose:
* https://github.com/BIOP/qupath-extension-cellpose/releases/download/v0.3.0/qupath-extension-cellpose-0.3.0.jar
*
* Author: Nicolas Chiaruttini, BIOP, EPFL 2021
**/
#@String(label="QuPath User Folder", value="C:/QuPath Common Data") defaultQuPathUserPath
#@String(label="QuPath Prefs Node", value="io.github.qupath/0.3") quPathPrefsNode
#@String(label="URL of QuPath Extension to install") quPathExtensionURL
#@Boolean(lable="Quit after installation") quitAfterInstall
IJ.log("Coucou")
IJ.log(defaultQuPathUserPath)
IJ.log(quPathPrefsNode)
IJ.log(quPathExtensionURL)
// Check pre-existing QuPath user Path
Preferences prefs = Preferences.userRoot().node(quPathPrefsNode);
def allKeys = prefs.keys() as List
println(allKeys.contains('userPath'))
def userPath = prefs.get("userPath", defaultQuPathUserPath)
if (userPath.equals(defaultQuPathUserPath)) {
IJ.log("Setting java prefs because the pref may not exist")
prefs.put("userPath", defaultQuPathUserPath)
} else {
IJ.log("QuPath user path already exists")
}
IJ.log("QuPath user path: "+userPath)
// Create QuPath user folder if needed
File userPathFile = new File(userPath)
if (!userPathFile.exists()) {
def result = userPathFile.mkdir()
if (!result) {
IJ.log("Could not create folder "+userPath+". Exiting.")
return
}
}
// Create QuPath extensions folder if needed
File extensionsDir = new File(userPath,"extensions")
if (!extensionsDir.exists()) {
def result = extensionsDir.mkdir()
if (!result) {
IJ.log("Could not create folder "+extensionsDir+". Exiting.")
return
}
}
// Copy file from URL to disk
def url = new URL(quPathExtensionURL)
String fName = FilenameUtils.getName(url.getPath())
String extension = FilenameUtils.getExtension(url.getPath())
def outputFile = new File(extensionsDir, fName)
IJ.log("Starting download")
FileUtils.copyURLToFile(url, outputFile);
IJ.log("Download done")
// Unzip file if necessary
if (extension.equals("zip")) {
IJ.log("Unzipping extension")
try (ZipFile zipFile = new ZipFile(outputFile)) {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
File entryDestination = new File(extensionsDir, entry.getName());
if (entry.isDirectory()) {
entryDestination.mkdirs();
} else {
entryDestination.getParentFile().mkdirs();
try (InputStream in = zipFile.getInputStream(entry);
OutputStream out = new FileOutputStream(entryDestination)) {
IOUtils.copy(in, out);
}
}
}
}
IJ.log("Unzipping done")
outputFile.delete()
}
// Cleans old jars
// REGEX: (.+)-([0-9]+).([0-9]+)?.([0-9]+)?(-SNAPSHOT)?(\.jar) to get jars versions:
// name-0.1.0.jar if it doesn't match it's ignored
def pattern_expression = '(.+)-([0-9]+).([0-9]+)?.([0-9]+)?(-SNAPSHOT)?(\\.jar)'
Pattern pattern = Pattern.compile(pattern_expression);
Map<String, RepoAndVersion> jarsMaxVersion = new HashMap();
listJars(extensionsDir.getAbsolutePath())
.stream()
.each{it ->
IJ.log(it.toString())
Matcher matcher = pattern.matcher(it.toString());
if (matcher.find()) {
def v1 = new RepoAndVersion(it.toString(),
matcher.group(1),
matcher.group(2),
matcher.group(3),
matcher.group(4),
matcher.group(5)!=null)
if (jarsMaxVersion.containsKey(v1.repoName)) {
// Fight : who has a higher version ?
def v2 = jarsMaxVersion.get(v1.repoName)
if (v1.hasHigherVersionThan(v2)) {
// Swap the map
jarsMaxVersion.put(v1.repoName, v1)
// Delete v2, because v1>v2
def result = new File(extensionsDir, v2.fileName).delete()
if (!result) IJ.log("Delete failed! Do you have QuPath opened ? Please close it before installing the extension")
IJ.log("Found duplicated jars, deleting older version "+v2.fileName)
} else {
// Delete v1, because v2>v1
def result = new File(extensionsDir, v1.fileName).delete()
if (!result) IJ.log("Delete failed! Do you have QuPath opened ? Please close it before installing the extension")
IJ.log("Found duplicated jars, deleting older version "+v1.fileName)
}
} else {
// No Fight
jarsMaxVersion.put(v1.repoName, v1)
}
}
}
if (quitAfterInstall) {
IJ.run("Quit");
}
"Done"
// List all files ending with jar
public Set listJars(String dir) throws IOException {
try (Stream stream = Files.list(Paths.get(dir))) {
return stream
.filter(file -> !Files.isDirectory(file))
.map(it -> it.getFileName())
.map(it -> it.toString())
.filter(it -> it.endsWith(".jar"))
.collect(Collectors.toSet());
}
}
class RepoAndVersion {
int major
int minor = -1
int patch = -1
boolean snapshot
String fileName
String repoName
public RepoAndVersion(String fileName, String repoName, String major, String minor, String patch, boolean snapshot) {
this.fileName = fileName
this.repoName = repoName
this.major = Integer.valueOf(major)
if (minor!=null) this.minor = Integer.valueOf(minor)
if (patch!=null) this.patch = Integer.valueOf(patch)
this.snapshot = snapshot
}
public boolean hasHigherVersionThan(RepoAndVersion other) {
/*IJ.log("M:"+major+" vs "+other.major)
IJ.log("m:"+minor+" vs "+other.minor)
IJ.log("p:"+patch+" vs "+other.patch)
IJ.log("s:"+snapshot+" vs "+other.snapshot)*/
if (major > other.major) {
return true;
}
if (major < other.major) {
return false;
}
assert major == other.major
if (minor > other.minor) {
return true;
}
if (minor < other.minor) {
return false;
}
assert minor == other.minor
if (patch > other.patch) {
return true;
}
if (patch < other.patch) {
return false;
}
assert patch == other.patch
if ((snapshot)&&(!other.snapshot)) { // 'this' is snapshot, other is not -> return false
return false
}
if ((!snapshot)&&(other.snapshot)) { // 'this' is not snapshot, other is-> return true
return true
}
// Equal versions... should not happen, because files are erased
IJ.error("Is "+fileName+" not identical to "+other.fileName+"?")
return false // nonetheless
}
}
import java.util.regex.Pattern
import java.util.regex.Matcher
import java.util.prefs.Preferences
import ij.IJ
import java.io.File
import org.apache.commons.io.FileUtils
import org.apache.commons.io.FilenameUtils
import org.apache.commons.io.IOUtils
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.stream.Stream
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.Path
import java.util.stream.Collectors