source: launchers/common/src/main/scala/net/i2p/launchers/OSXDeployment.scala @ 70e994fc

Last change on this file since 70e994fc was 70e994fc, checked in by meeh <meeh@…>, 3 years ago

Some changes, after much testing I find it best to spawn an sub java
process for I2P rather than trying to load Router via reflection or
anything runtime fancy stuff. Shell script is implemented in SBT now,
and can now link to download JRE script and so on.

  • Property mode set to 100644
File size: 8.0 KB
Line 
1package net.i2p.launchers
2
3import java.io.{File, IOException}
4import java.nio.file.FileAlreadyExistsException
5import java.util.zip.ZipFile
6
7import java.nio.file.StandardCopyOption.REPLACE_EXISTING
8import java.nio.file.Files.copy
9import java.nio.file.Paths.get
10
11import collection.JavaConverters._
12
13/**
14  *
15  * OSXDeployment
16  *
17  * This class can be a bit new for java developers. In Scala, when inherit other classes,
18  * you would need to define their arguments if the super class only has constructors taking arguments.
19  *
20  * This child class don't take arguments, but rather get them from the signleton OSXDefaults.
21  *
22  * This class should be able to copy recursive resources into correct position for a normal deployment.
23  *
24  *
25  * This class might look like black magic for some. But what it does is to deploy a structure like for example:
26  * tree ~/Library/I2P
27  * /Users/mikalv/Library/I2P
28  * ├── blocklist.txt
29  * ├── certificates
30  * │   ├── family
31  * │   │   ├── gostcoin.crt
32  * │   │   ├── i2p-dev.crt
33  * │   │   ├── i2pd-dev.crt
34  * │   │   └── volatile.crt
35  * │   ├── i2ptunnel
36  * │   ├── news
37  * │   │   ├── ampernand_at_gmail.com.crt
38  * │   │   ├── echelon_at_mail.i2p.crt
39  * │   │   ├── str4d_at_mail.i2p.crt
40  * │   │   └── zzz_at_mail.i2p.crt
41  * │   ├── plugin
42  * │   │   ├── cacapo_at_mail.i2p.crt
43  * │   │   ├── str4d_at_mail.i2p.crt
44  * │   │   └── zzz-plugin_at_mail.i2p.crt
45  * │   ├── reseed
46  * │   │   ├── atomike_at_mail.i2p.crt
47  * │   │   ├── backup_at_mail.i2p.crt
48  * │   │   ├── bugme_at_mail.i2p.crt
49  * │   │   ├── creativecowpat_at_mail.i2p.crt
50  * │   │   ├── echelon_at_mail.i2p.crt
51  * │   │   ├── hottuna_at_mail.i2p.crt
52  * │   │   ├── igor_at_novg.net.crt
53  * │   │   ├── lazygravy_at_mail.i2p.crt
54  * │   │   ├── meeh_at_mail.i2p.crt
55  * │   │   └── zmx_at_mail.i2p.crt
56  * │   ├── revocations
57  * │   ├── router
58  * │   │   ├── echelon_at_mail.i2p.crt
59  * │   │   ├── str4d_at_mail.i2p.crt
60  * │   │   └── zzz_at_mail.i2p.crt
61  * │   └── ssl
62  * │       ├── echelon.reseed2017.crt
63  * │       ├── i2p.mooo.com.crt
64  * │       ├── i2pseed.creativecowpat.net.crt
65  * │       ├── isrgrootx1.crt
66  * │       └── reseed.onion.im.crt
67  * ├── clients.config
68  * ├── geoip
69  * │   ├── continents.txt
70  * │   ├── countries.txt
71  * │   ├── geoip.txt
72  * │   └── geoipv6.dat.gz
73  * ├── hosts.txt
74  * └── i2ptunnel.config
75  *
76  * @author Meeh
77  * @since 0.9.35
78  */
79class OSXDeployment extends
80  DeployProfile(
81    OSXDefaults.getOSXConfigDirectory.getAbsolutePath,
82    OSXDefaults.getOSXBaseDirectory.getAbsolutePath
83  ) {
84
85
86
87  /**
88    * This list is a micro DSL for how files should
89    * be deployed to the filesystem in the base
90    * directory.
91    */
92  val staticFilesFromResources = List(
93    new FDObjFile("blocklist.txt"),
94    new FDObjFile("clients.config"),
95    new FDObjFile("hosts.txt"),
96    new FDObjDir("geoip", files = List(
97      "continents.txt",
98      "countries.txt",
99      "geoip.txt",
100      "geoipv6.dat.gz")),
101    new FDObjFile("i2ptunnel.config"),
102    new FDObjDir("certificates", List(
103      "family",
104      "i2ptunnel",
105      "news",
106      "plugin",
107      "reseed",
108      "revocations",
109      "router",
110      "ssl"
111    ),subDirectories=true),
112    new FDObjDir("themes",List(
113      "console",
114      "imagegen",
115      "snark",
116      "susidns",
117      "susimail"
118    ),true)
119  )
120
121  /**
122    * This function copies an directory of files from the jar
123    * to the base directory defined in the launcher.
124    * @param dir
125    * @return
126    */
127  def copyDirFromRes(dir: File): Unit = {
128    // A small hack
129    try {
130      val zipFile = new ZipFile(DeployProfile.executingJarFile.getFile)
131      zipFile.entries().asScala.toList.filter(_.toString.startsWith(dir.getPath)).filter(!_.isDirectory).map { entry =>
132        new File(DeployProfile.pathJoin(baseDir,entry.getName)).getParentFile.mkdirs()
133        if (entry.isDirectory) {
134          createFileOrDirectory(new File(DeployProfile.pathJoin(baseDir,entry.getName)), true)
135        } else {
136          copyBaseFileResToDisk(entry.getName, getClass.getResourceAsStream("/".concat(entry.getName)))
137        }
138      }
139    } catch {
140      case _:FileAlreadyExistsException => {} // Ignored
141      case ex:IOException => println(s"Error! Exception ${ex}")
142    }
143  }
144
145  /**
146    * This function will depending on directory or not copy either the file
147    * or create the directory and copy directory content if any.
148    *
149    * @param file
150    * @param isDir
151    * @return
152    */
153  def createFileOrDirectory(file: File, isDir: Boolean = false): Unit = {
154    if (file != null) {
155      //println(s"createFileOrDirectory(${file},${isDir})")
156      try {
157        // Make sure subject exists if directory
158        if (!new File(DeployProfile.pathJoin(baseDir,file.getPath)).exists()) {
159          if (isDir) new File(DeployProfile.pathJoin(baseDir,file.getPath)).mkdirs()
160        }
161        if (isDir) {
162          // Handle dir
163          copyDirFromRes(file)
164        } else {
165          // Handle file
166          copyBaseFileResToDisk(file.getPath, getClass.getResourceAsStream("/".concat(file.getName)))
167        }
168      } catch {
169        case _:FileAlreadyExistsException => {} // Ignored
170        case ex:IOException => println(s"Error! Exception ${ex}")
171      }
172    }
173  }
174
175  if (!new File(baseDir).exists()) {
176    new File(baseDir).mkdirs()
177  }
178
179  implicit def toPath (filename: String) = get(filename)
180
181  val selfFile = new File(DeployProfile.executingJarFile.getFile)
182  val selfDir = selfFile.getParentFile
183  val resDir = new File(selfDir.getParent, "Resources")
184  val i2pBaseBundleDir = new File(resDir, "i2pbase")
185  val i2pBundleJarDir = new File(i2pBaseBundleDir, "lib")
186
187  val i2pBaseDir = OSXDefaults.getOSXBaseDirectory
188  val i2pDeployJarDir = new File(i2pBaseDir, "lib")
189  if (!i2pDeployJarDir.exists()) {
190    i2pDeployJarDir.mkdirs()
191    i2pBundleJarDir.list().toList.map {
192      jar => {
193        copy (
194          DeployProfile.pathJoin(i2pBundleJarDir.getAbsolutePath, jar),
195          DeployProfile.pathJoin(i2pDeployJarDir.getAbsolutePath, jar),
196          REPLACE_EXISTING)
197        println(s"Copied ${jar} to right place")
198      }
199    }
200  }
201
202  /**
203    * Please note that in Scala, the constructor body is same as class body.
204    * What's defined outside of methods is considered constructor code and
205    * executed as it.
206    *
207    *
208    *
209    * a map function work as a loop with some built in security
210    * for "null" objects.
211    * What happens here is "for each staticFilesFromResources" do =>
212    *
213    * Then, based upon if it's a file or a directory, different actions take place.
214    *
215    * the match case is controlling the flow based upon which type of object it is.
216    */
217  staticFilesFromResources.map {
218    fd => fd.getContent match {
219        // Case subject is a file/resource
220      case Left(is) => {
221        // Write file
222        val f = DeployProfile.pathJoin(baseDir, fd.getPath)
223        println(s"f: ${f.toString}")
224        if (!new File(f).exists()) {
225          //println(s"copyBaseFileResToDisk(${fd.getPath})")
226          try {
227            copyBaseFileResToDisk(fd.getPath, is)
228          } catch {
229            case ex:IOException => println(s"Error! Exception ${ex}")
230            case _:FileAlreadyExistsException => {} // Ignored
231          }
232        }
233      }
234        // Case subject is a directory
235      case Right(dir) => {
236        // Ensure directory
237        //println(s"Directory(${fd.getPath})")
238        if (!new File(DeployProfile.pathJoin(baseDir,fd.getPath)).exists()) {
239          new File(DeployProfile.pathJoin(baseDir,fd.getPath)).mkdirs()
240        }
241        dir.map { f => createFileOrDirectory(f,fd.filesIsDirectories) }
242      }
243    }
244  }
245
246}
Note: See TracBrowser for help on using the repository browser.