source: launchers/common/src/main/scala/net/i2p/launchers/PartialDeployment.scala @ 5f81a8de

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

Mac OS X Launcher - reborn - ALPHA!

TLDR;
Howto? ant osxLauncher
Privacy Notes? If you don't got SBT, a bash script will trigger

download of SBT for you with task osxLauncher.

Results? open ./launchers/output
"Binary" App Bundle name: I2P.app
Runtime base directory? ~/Library/I2P
Runtime config directory? untouched.

After talk on IRC with zzz, I rewrote the logic since we could
start with a simple deploy, for a faster alpha version ready :)

SBT will build a zip file from the content of pkg-temp, which
CompleteDeployment?.scala will again unzip in runtime. Right now
it's quite basic, but the plan is to add version detection, so
it's capable of upgrading a already deployed I2P base directory.

OSXDeployment.scala is renamed to PartialDeployment?.scala for usage
in the browser bundle launcher, since it's going to be a subset of
the files found in pkg-temp.

A Info.plist is added to the launchers/macosx which is added to the
application bundle under building. Note that this differ from the one
in Start i2p router.app that's been here for years now.

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