source: java/installer2/src/net/oni2/aeinstaller/backend/oni/management/Installer.java@ 880

Last change on this file since 880 was 810, checked in by alloc, 12 years ago

AEI2.03:

  • Fixes #7
  • Fixes regression introduced by fixing #8
File size: 19.7 KB
Line 
1package net.oni2.aeinstaller.backend.oni.management;
2
3import java.io.File;
4import java.io.FileFilter;
5import java.io.FileNotFoundException;
6import java.io.FilenameFilter;
7import java.io.IOException;
8import java.io.InputStream;
9import java.io.PrintWriter;
10import java.text.SimpleDateFormat;
11import java.util.Date;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.List;
15import java.util.TreeMap;
16import java.util.TreeSet;
17import java.util.Vector;
18import java.util.regex.Pattern;
19
20import net.oni2.SettingsManager;
21import net.oni2.aeinstaller.AEInstaller2;
22import net.oni2.aeinstaller.backend.CaseInsensitiveFile;
23import net.oni2.aeinstaller.backend.Paths;
24import net.oni2.aeinstaller.backend.oni.OniSplit;
25import net.oni2.aeinstaller.backend.oni.PersistDat;
26import net.oni2.aeinstaller.backend.oni.XMLTools;
27import net.oni2.aeinstaller.backend.oni.management.tools.ToolInstallationList;
28import net.oni2.aeinstaller.backend.packages.EBSLInstallType;
29import net.oni2.aeinstaller.backend.packages.Package;
30import net.oni2.aeinstaller.backend.packages.PackageManager;
31import net.oni2.platformtools.PlatformInformation;
32import net.oni2.platformtools.PlatformInformation.Platform;
33import net.oni2.platformtools.applicationinvoker.ApplicationInvocationResult;
34
35import org.apache.commons.io.FileUtils;
36import org.apache.commons.io.filefilter.RegexFileFilter;
37import org.apache.commons.io.filefilter.TrueFileFilter;
38import org.javabuilders.swing.SwingJavaBuilder;
39
40import com.paour.NaturalOrderComparator;
41
42/**
43 * @author Christian Illy
44 */
45public class Installer {
46 private static FileFilter dirFileFilter = new FileFilter() {
47 @Override
48 public boolean accept(File pathname) {
49 return pathname.isDirectory();
50 }
51 };
52
53 /**
54 * Verify that the Edition is within a subfolder to vanilla Oni
55 * (..../Oni/Edition/AEInstaller)
56 *
57 * @return true if GDF can be found in the parent's parent-path
58 */
59 public static boolean verifyRunningDirectory() {
60 return Paths.getVanillaGDF().exists()
61 && Paths.getVanillaGDF().isDirectory();
62 }
63
64 /**
65 * @return Is Edition Core initialized
66 */
67 public static boolean isEditionInitialized() {
68 return Paths.getVanillaOnisPath().exists();
69 }
70
71 /**
72 * Install the given set of mods
73 *
74 * @param mods
75 * Mods to install
76 * @param listener
77 * Listener for install progress updates
78 */
79 public static void install(TreeSet<Package> mods,
80 InstallProgressListener listener) {
81 File logFile = new File(Paths.getInstallerPath(), "Installation.log");
82 Logger log = null;
83 try {
84 log = new Logger(logFile);
85 } catch (FileNotFoundException e) {
86 e.printStackTrace();
87 }
88 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
89 Date start = new Date();
90 log.println("Installation of mods started at " + sdf.format(start));
91
92 log.println();
93 log.println("AEI2 version: "
94 + SwingJavaBuilder.getConfig().getResource("appversion"));
95
96 ToolInstallationList til = ToolInstallationList.getInstance();
97 log.println("Installed tools:");
98 for (Package t : PackageManager.getInstance().getInstalledTools()) {
99 log.println(String.format(" - %s (%s)", t.getName(), t.getVersion())
100 + (til.isModified(t.getPackageNumber()) ? " (! LOCALLY MODIFIED !)"
101 : ""));
102 }
103 log.println("Installing mods:");
104 for (Package m : mods) {
105 log.println(String.format(" - %s (%s)", m.getName(), m.getVersion()));
106 }
107 log.println();
108
109 HashSet<String> levelsAffectedBefore = null;
110 if (ModInstallationList.getInstance().isLoadedFromFile()) {
111 levelsAffectedBefore = ModInstallationList.getInstance()
112 .getAffectedLevels();
113 }
114 HashSet<String> levelsAffectedNow = new HashSet<String>();
115
116 File IGMD = new File(Paths.getEditionGDF(), "IGMD");
117 if (IGMD.exists()) {
118 for (File f : IGMD.listFiles(new FileFilter() {
119 @Override
120 public boolean accept(File pathname) {
121 return pathname.isDirectory();
122 }
123 })) {
124 File ignore = CaseInsensitiveFile.getCaseInsensitiveFile(f,
125 "ignore.txt");
126 if (!ignore.exists()) {
127 try {
128 FileUtils.deleteDirectory(f);
129 } catch (IOException e) {
130 e.printStackTrace();
131 }
132 }
133 }
134 }
135
136 TreeSet<Integer> unlockLevels = new TreeSet<Integer>();
137
138 Vector<File> foldersOni = new Vector<File>();
139 foldersOni.add(Paths.getVanillaOnisPath());
140
141 Vector<File> foldersPatches = new Vector<File>();
142
143 for (Package m : mods) {
144 for (int lev : m.getUnlockLevels())
145 unlockLevels.add(lev);
146
147 File oni = CaseInsensitiveFile.getCaseInsensitiveFile(
148 m.getLocalPath(), "oni");
149 if (oni.exists()) {
150 if (m.hasSeparatePlatformDirs()) {
151 File oniCommon = CaseInsensitiveFile
152 .getCaseInsensitiveFile(oni, "common");
153 File oniMac = CaseInsensitiveFile.getCaseInsensitiveFile(
154 oni, "mac_only");
155 File oniWin = CaseInsensitiveFile.getCaseInsensitiveFile(
156 oni, "win_only");
157 if (oniCommon.exists())
158 foldersOni.add(oniCommon);
159 if (PlatformInformation.getPlatform() == Platform.MACOS
160 && oniMac.exists())
161 foldersOni.add(oniMac);
162 else if (oniWin.exists())
163 foldersOni.add(oniWin);
164 } else {
165 foldersOni.add(oni);
166 }
167 }
168
169 File patches = CaseInsensitiveFile.getCaseInsensitiveFile(
170 m.getLocalPath(), "patches");
171 if (patches.exists()) {
172 if (m.hasSeparatePlatformDirs()) {
173 File patchesCommon = CaseInsensitiveFile
174 .getCaseInsensitiveFile(patches, "common");
175 File patchesMac = CaseInsensitiveFile
176 .getCaseInsensitiveFile(patches, "mac_only");
177 File patchesWin = CaseInsensitiveFile
178 .getCaseInsensitiveFile(patches, "win_only");
179 if (patchesCommon.exists())
180 foldersPatches.add(patchesCommon);
181 if (PlatformInformation.getPlatform() == Platform.MACOS
182 && patchesMac.exists())
183 foldersPatches.add(patchesMac);
184 else if (patchesWin.exists())
185 foldersPatches.add(patchesWin);
186 } else {
187 foldersPatches.add(patches);
188 }
189 }
190 }
191
192 TreeMap<String, Vector<File>> levels = new TreeMap<String, Vector<File>>(
193 new NaturalOrderComparator());
194 for (File path : foldersOni) {
195 for (File levelF : path.listFiles()) {
196 String fn = levelF.getName().toLowerCase();
197 String levelN = null;
198 if (levelF.isDirectory()) {
199 levelN = fn;
200 levelsAffectedNow.add(fn.toLowerCase());
201 } else if (fn.endsWith(".dat")) {
202 levelN = fn.substring(0, fn.lastIndexOf('.')).toLowerCase();
203 }
204 if (levelN != null) {
205 if (!levels.containsKey(levelN))
206 levels.put(levelN, new Vector<File>());
207 levels.get(levelN).add(levelF);
208 }
209 }
210 }
211
212 Paths.getEditionGDF().mkdirs();
213 for (File f : Paths.getEditionGDF().listFiles(new FilenameFilter() {
214 public boolean accept(File arg0, String arg1) {
215 String s = arg1.toLowerCase();
216 return s.endsWith(".dat")
217 || s.endsWith(".raw")
218 || s.endsWith(".sep")
219 || (s.equals("intro.bik") && !SettingsManager
220 .getInstance().get("copyintro", false))
221 || (s.equals("outro.bik") && !SettingsManager
222 .getInstance().get("copyoutro", false));
223 }
224 })) {
225 String l = f.getName().toLowerCase();
226 l = l.substring(0, l.length() - 4);
227 if ((levelsAffectedBefore == null)
228 || levelsAffectedBefore.contains(l)
229 || levelsAffectedNow.contains(l))
230 f.delete();
231 }
232
233 applyPatches(levels, foldersPatches, listener, log);
234
235 TreeSet<String> levelsAffectedBoth = null;
236 if (levelsAffectedBefore != null) {
237 levelsAffectedBoth = new TreeSet<String>();
238 levelsAffectedBoth.addAll(levelsAffectedBefore);
239 levelsAffectedBoth.addAll(levelsAffectedNow);
240 }
241
242 combineBinaryFiles(levels, levelsAffectedBoth, listener, log);
243 combineBSLFolders(mods, listener, log);
244
245 copyVideos(log);
246
247 if (unlockLevels.size() > 0) {
248 unlockLevels(unlockLevels, log);
249 }
250
251 ModInstallationList mil = ModInstallationList.getInstance();
252 mil.setAffectedLevels(levelsAffectedNow);
253 TreeSet<Integer> modsInstalled = new TreeSet<Integer>();
254 for (Package p : mods) {
255 modsInstalled.add(p.getPackageNumber());
256 }
257 mil.setInstalledMods(modsInstalled);
258 mil.saveList();
259
260 log.println();
261 Date end = new Date();
262 log.println("Installation ended at " + sdf.format(end));
263 log.println("Process took "
264 + ((end.getTime() - start.getTime()) / 1000) + " seconds");
265 log.close();
266 }
267
268 private static void combineBSLFolders(TreeSet<Package> mods,
269 InstallProgressListener listener, Logger log) {
270 listener.installProgressUpdate(95, 100, "Installing BSL files");
271 log.println();
272 log.println("Installing BSL files");
273
274 HashMap<EBSLInstallType, Vector<Package>> modsToInclude = new HashMap<EBSLInstallType, Vector<Package>>();
275 modsToInclude.put(EBSLInstallType.NORMAL, new Vector<Package>());
276 modsToInclude.put(EBSLInstallType.ADDON, new Vector<Package>());
277
278 for (Package m : mods.descendingSet()) {
279 File bsl = CaseInsensitiveFile.getCaseInsensitiveFile(
280 m.getLocalPath(), "bsl");
281 if (bsl.exists()) {
282 if (m.hasSeparatePlatformDirs()) {
283 File bslCommon = CaseInsensitiveFile
284 .getCaseInsensitiveFile(bsl, "common");
285 File bslMac = CaseInsensitiveFile.getCaseInsensitiveFile(
286 bsl, "mac_only");
287 File bslWin = CaseInsensitiveFile.getCaseInsensitiveFile(
288 bsl, "win_only");
289 if ((PlatformInformation.getPlatform() == Platform.MACOS && bslMac
290 .exists())
291 || ((PlatformInformation.getPlatform() == Platform.WIN || PlatformInformation
292 .getPlatform() == Platform.LINUX) && bslWin
293 .exists()) || bslCommon.exists()) {
294 modsToInclude.get(m.getBSLInstallType()).add(m);
295 }
296 } else {
297 modsToInclude.get(m.getBSLInstallType()).add(m);
298 }
299 }
300 }
301
302 for (Package m : modsToInclude.get(EBSLInstallType.NORMAL)) {
303 copyBSL(m, false, log);
304 }
305 Vector<Package> addons = modsToInclude.get(EBSLInstallType.ADDON);
306 for (int i = addons.size() - 1; i >= 0; i--) {
307 copyBSL(addons.get(i), true, log);
308 }
309 }
310
311 private static void copyBSL(Package sourceMod, boolean addon, Logger log) {
312 File targetBaseFolder = new File(Paths.getEditionGDF(), "IGMD");
313 if (!targetBaseFolder.exists())
314 targetBaseFolder.mkdir();
315
316 Vector<File> sources = new Vector<File>();
317 File bsl = CaseInsensitiveFile.getCaseInsensitiveFile(
318 sourceMod.getLocalPath(), "bsl");
319 if (sourceMod.hasSeparatePlatformDirs()) {
320 File bslCommon = CaseInsensitiveFile.getCaseInsensitiveFile(bsl,
321 "common");
322 File bslMac = CaseInsensitiveFile.getCaseInsensitiveFile(bsl,
323 "mac_only");
324 File bslWin = CaseInsensitiveFile.getCaseInsensitiveFile(bsl,
325 "win_only");
326 if (PlatformInformation.getPlatform() == Platform.MACOS
327 && bslMac.exists()) {
328 for (File f : bslMac.listFiles(dirFileFilter)) {
329 File targetBSL = new File(targetBaseFolder, f.getName());
330 if (addon || !targetBSL.exists())
331 sources.add(f);
332 }
333 }
334 if ((PlatformInformation.getPlatform() == Platform.WIN || PlatformInformation
335 .getPlatform() == Platform.LINUX) && bslWin.exists()) {
336 for (File f : bslWin.listFiles(dirFileFilter)) {
337 File targetBSL = new File(targetBaseFolder, f.getName());
338 if (addon || !targetBSL.exists())
339 sources.add(f);
340 }
341 }
342 if (bslCommon.exists()) {
343 for (File f : bslCommon.listFiles(dirFileFilter)) {
344 File targetBSL = new File(targetBaseFolder, f.getName());
345 if (addon || !targetBSL.exists())
346 sources.add(f);
347 }
348 }
349 } else {
350 for (File f : bsl.listFiles(dirFileFilter)) {
351 File targetBSL = new File(targetBaseFolder, f.getName());
352 if (addon || !targetBSL.exists())
353 sources.add(f);
354 }
355 }
356
357 log.println("\tMod \"" + sourceMod.getName() + "\"");
358 for (File f : sources) {
359 log.println("\t\t" + f.getName());
360 File targetPath = new File(targetBaseFolder, f.getName());
361 if (!targetPath.exists())
362 targetPath.mkdir();
363 if (!(CaseInsensitiveFile.getCaseInsensitiveFile(targetPath,
364 "ignore.txt").exists())) {
365 for (File fbsl : f.listFiles()) {
366 if (fbsl.getName().toLowerCase().endsWith(".bsl")) {
367 File targetFile = new File(targetPath, fbsl.getName());
368 if (addon || !targetFile.exists()) {
369 try {
370 FileUtils.copyFile(fbsl, targetFile);
371 } catch (IOException e) {
372 e.printStackTrace();
373 }
374 }
375 }
376 }
377 }
378 }
379 }
380
381 private static void applyPatches(
382 TreeMap<String, Vector<File>> oniLevelFolders,
383 List<File> patchFolders, InstallProgressListener listener,
384 Logger log) {
385 log.println();
386 log.println("Applying XML patches");
387 listener.installProgressUpdate(0, 1, "Applying XML patches");
388
389 long startMS = new Date().getTime();
390
391 String tmpFolderName = "installrun_temp-"
392 + new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss")
393 .format(new Date());
394 File tmpFolder = new File(Paths.getTempPath(), tmpFolderName);
395 tmpFolder.mkdir();
396
397 TreeMap<String, Vector<File>> patches = new TreeMap<String, Vector<File>>(
398 new NaturalOrderComparator());
399 for (File patchFolder : patchFolders) {
400 for (File levelFolder : patchFolder.listFiles(dirFileFilter)) {
401 String lvlName = levelFolder.getName().toLowerCase();
402 for (File f : FileUtils.listFiles(levelFolder,
403 new String[] { "oni-patch" }, true)) {
404 if (!patches.containsKey(lvlName))
405 patches.put(lvlName, new Vector<File>());
406 patches.get(lvlName).add(f);
407 }
408 }
409 }
410
411 for (String level : patches.keySet()) {
412 File levelFolder = new File(tmpFolder, level);
413 levelFolder.mkdir();
414
415 log.println("\t\tPatches for " + level);
416
417 Vector<String> exportPatterns = new Vector<String>();
418 // Get files to be patched from vanilla.dat
419 for (File patch : patches.get(level)) {
420 String patternWildcard = patch.getName();
421 patternWildcard = patternWildcard.substring(0,
422 patternWildcard.indexOf(".oni-patch"));
423 patternWildcard = patternWildcard.replace('-', '*');
424 exportPatterns.add(patternWildcard);
425 }
426 for (File srcFolder : oniLevelFolders.get(level)) {
427 if (srcFolder.isFile()) {
428 if (srcFolder.getPath().toLowerCase().contains("vanilla")) {
429 // Extract from .dat
430 ApplicationInvocationResult res = OniSplit.export(
431 levelFolder, srcFolder, exportPatterns);
432 log.logAppOutput(res, true);
433 }
434 }
435 }
436
437 // Get files to be patched from packages
438 for (File patch : patches.get(level)) {
439 String patternWildcard = patch.getName();
440 patternWildcard = patternWildcard.substring(0,
441 patternWildcard.indexOf(".oni-patch"));
442 patternWildcard = patternWildcard.replace('-', '*');
443 patternWildcard = patternWildcard + ".oni";
444 Vector<String> patterns = new Vector<String>();
445 patterns.add(patternWildcard);
446 final Pattern patternRegex = Pattern.compile(
447 patternWildcard.replaceAll("\\*", ".\\*"),
448 Pattern.CASE_INSENSITIVE);
449
450 for (File srcFolder : oniLevelFolders.get(level)) {
451 if (srcFolder.isFile()) {
452 if (!srcFolder.getPath().toLowerCase()
453 .contains("vanilla")) {
454 // Extract from .dat
455 ApplicationInvocationResult res = OniSplit.export(
456 levelFolder, srcFolder, patterns);
457 log.logAppOutput(res, true);
458 }
459 } else {
460 // Copy from folder with overwrite
461 for (File f : FileUtils.listFiles(srcFolder,
462 new RegexFileFilter(patternRegex),
463 TrueFileFilter.TRUE)) {
464 try {
465 FileUtils.copyFileToDirectory(f, levelFolder);
466 } catch (IOException e) {
467 e.printStackTrace();
468 }
469 }
470 }
471 }
472 }
473
474 // Extract files to XML
475 File levelFolderXML = new File(levelFolder, "xml");
476 Vector<File> files = new Vector<File>();
477 files.add(new File(levelFolder, "*.oni"));
478 ApplicationInvocationResult res = OniSplit.convertOniToXML(
479 levelFolderXML, files);
480 log.logAppOutput(res, true);
481
482 // Create masterpatch file (containing calls to all individual
483 // patches)
484 File masterpatch = new File(levelFolderXML, "masterpatch.oni-patch");
485 PrintWriter masterpatchWriter = null;
486 try {
487 masterpatchWriter = new PrintWriter(masterpatch);
488 } catch (FileNotFoundException e) {
489 e.printStackTrace();
490 }
491 for (File patch : patches.get(level)) {
492 String patternWildcard = patch.getName();
493 patternWildcard = patternWildcard.substring(0,
494 patternWildcard.indexOf(".oni-patch"));
495 patternWildcard = patternWildcard + ".xml";
496 patternWildcard = patternWildcard.replace('-', '*');
497 File xmlFilePath = new File(levelFolderXML, patternWildcard);
498 masterpatchWriter
499 .println(String
500 .format("@COMMAND patchfile -filename:\"%s\" -forceinfiles:\"%s\"",
501 patch.getPath(), xmlFilePath.getPath()));
502 }
503 masterpatchWriter.close();
504 // Apply patches through masterpatch in levelFolderXML
505 res = XMLTools.patch(masterpatch, null);
506 log.logAppOutput(res, false);
507
508 // Create .oni files from XML
509 files.clear();
510 files.add(new File(levelFolderXML, "*.xml"));
511 res = OniSplit.convertXMLtoOni(levelFolder, files);
512 log.logAppOutput(res, true);
513
514 // Remove XML folder as import will only require .oni's
515 try {
516 FileUtils.deleteDirectory(levelFolderXML);
517 } catch (IOException e) {
518 e.printStackTrace();
519 }
520
521 oniLevelFolders.get(level).add(levelFolder);
522 }
523
524 log.println("Applying XML patches took "
525 + (new Date().getTime() - startMS) + " ms");
526 }
527
528 private static void combineBinaryFiles(
529 TreeMap<String, Vector<File>> oniLevelFolders,
530 TreeSet<String> levelsUpdated, InstallProgressListener listener,
531 Logger log) {
532 long startMS = new Date().getTime();
533
534 int totalSteps = oniLevelFolders.size() + 1;
535 int stepsDone = 0;
536
537 log.println();
538 log.println("Importing levels");
539 for (String l : oniLevelFolders.keySet()) {
540 log.println("\tLevel " + l);
541 listener.installProgressUpdate(stepsDone, totalSteps,
542 "Installing level " + l);
543
544 if ((levelsUpdated == null)
545 || levelsUpdated.contains(l.toLowerCase())) {
546 ApplicationInvocationResult res = OniSplit.packLevel(
547 oniLevelFolders.get(l), new File(Paths.getEditionGDF(),
548 sanitizeLevelName(l) + ".dat"));
549 log.logAppOutput(res, true);
550 } else {
551 log.println("\t\tLevel not affected by new mod selection");
552 log.println();
553 }
554
555 stepsDone++;
556 }
557
558 log.println("Importing levels took " + (new Date().getTime() - startMS)
559 + " ms");
560 log.println();
561 }
562
563 private static void copyVideos(Logger log) {
564 log.println();
565 if (SettingsManager.getInstance().get("copyintro", false)) {
566 File src = new File(Paths.getVanillaGDF(), "intro.bik");
567 File target = new File(Paths.getEditionGDF(), "intro.bik");
568 log.println("Copying intro");
569 if (src.exists() && !target.exists()) {
570 try {
571 FileUtils.copyFileToDirectory(src, Paths.getEditionGDF());
572 } catch (IOException e) {
573 e.printStackTrace();
574 }
575 }
576 } else {
577 log.println("NOT copying intro");
578 }
579 if (SettingsManager.getInstance().get("copyoutro", true)) {
580 File src = new File(Paths.getVanillaGDF(), "outro.bik");
581 File target = new File(Paths.getEditionGDF(), "outro.bik");
582 log.println("Copying outro");
583 if (src.exists() && !target.exists()) {
584 try {
585 FileUtils.copyFileToDirectory(src, Paths.getEditionGDF());
586 } catch (IOException e) {
587 e.printStackTrace();
588 }
589 }
590 } else {
591 log.println("NOT copying outro");
592 }
593 }
594
595 private static void unlockLevels(TreeSet<Integer> unlockLevels, Logger log) {
596 File dat = new File(Paths.getEditionBasePath(), "persist.dat");
597 log.println();
598 log.println("Unlocking levels: " + unlockLevels.toString());
599 if (!dat.exists()) {
600 InputStream is = AEInstaller2.class
601 .getResourceAsStream("/net/oni2/aeinstaller/resources/persist.dat");
602 try {
603 FileUtils.copyInputStreamToFile(is, dat);
604 } catch (IOException e) {
605 e.printStackTrace();
606 }
607 }
608 PersistDat save = new PersistDat(dat);
609 HashSet<Integer> currentlyUnlocked = save.getUnlockedLevels();
610 currentlyUnlocked.addAll(unlockLevels);
611 save.setUnlockedLevels(currentlyUnlocked);
612 save.close();
613 }
614
615 private static String sanitizeLevelName(String ln) {
616 int ind = ln.indexOf("_");
617 String res = ln.substring(0, ind + 1);
618 res += ln.substring(ind + 1, ind + 2).toUpperCase();
619 res += ln.substring(ind + 2);
620 return res;
621 }
622
623}
Note: See TracBrowser for help on using the repository browser.