forked from I2P_Developers/i2p.i2p
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.
This commit is contained in:
47
launchers/macosx/Info.plist
Normal file
47
launchers/macosx/Info.plist
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>I2P</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Public Domain</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>0.9.35-experimental</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>i2p</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>net.i2p</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>I2P</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.0.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>I2P</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.0.1</string>
|
||||
<key>NSAppleScriptEnabled</key>
|
||||
<true/>
|
||||
<key>CGDisableCoalescedUpdates</key>
|
||||
<true/>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.5</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>I2P</string>
|
||||
<key>LSMinimumSystemVersionByArchitecture</key>
|
||||
<dict>
|
||||
<key>i386</key>
|
||||
<string>10.5.0</string>
|
||||
<key>x86_64</key>
|
||||
<string>10.6.0</string>
|
||||
</dict>
|
||||
<key>LSUIElement</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,14 +1,12 @@
|
||||
import sbtassembly.AssemblyPlugin.defaultShellScript
|
||||
import sbt._
|
||||
import Keys._
|
||||
import sbt.io.IO
|
||||
import java.io.File
|
||||
import java.io.{File, FileNotFoundException, FileOutputStream}
|
||||
import java.util.zip._
|
||||
|
||||
lazy val i2pVersion = "0.9.34"
|
||||
|
||||
lazy val cleanAllTask = taskKey[Unit]("Clean up and remove the OSX bundle")
|
||||
lazy val buildAppBundleTask = taskKey[Unit](s"Build an Mac OS X bundle for I2P ${i2pVersion}.")
|
||||
lazy val bundleBuildPath = file("./output")
|
||||
lazy val buildDeployZipTask = taskKey[String](s"Build an zipfile with base directory for I2P ${i2pVersion}.")
|
||||
lazy val bundleBuildPath = new File("./output")
|
||||
|
||||
lazy val staticFiles = List(
|
||||
"blocklist.txt",
|
||||
@@ -32,7 +30,7 @@ def defaultOSXLauncherShellScript(javaOpts: Seq[String] = Seq.empty): Seq[String
|
||||
Seq(
|
||||
"#!/usr/bin/env sh",
|
||||
s"""
|
||||
|echo "Yo"
|
||||
|echo "I2P - Mac OS X Launcher starting up"
|
||||
|export I2P=$$HOME/Library/I2P
|
||||
|for jar in `ls $${I2P}/lib/*.jar`; do
|
||||
| if [ ! -z $$CP ]; then
|
||||
@@ -66,7 +64,6 @@ assemblyOption in assembly := (assemblyOption in assembly).value.copy(
|
||||
"-Dwrapper.logfile.loglevel=DEBUG",
|
||||
"-Dwrapper.java.pidfile=/tmp/routerjvm.pid",
|
||||
"-Dwrapper.console.loglevel=DEBUG",
|
||||
"-Djava.awt.headless=true",
|
||||
"-Di2p.dir.base=$I2P",
|
||||
"-Djava.library.path=$I2P"
|
||||
)))
|
||||
@@ -92,36 +89,66 @@ cleanAllTask := {
|
||||
IO.delete(bundleBuildPath)
|
||||
}
|
||||
|
||||
buildDeployZipTask := {
|
||||
println(s"Starting the zip file build process. This might take a while..")
|
||||
if (!bundleBuildPath.exists()) bundleBuildPath.mkdir()
|
||||
val sourceDir = i2pBuildDir
|
||||
def recursiveListFiles(f: File): Array[File] = {
|
||||
val these = f.listFiles
|
||||
these ++ these.filter { f => f.isDirectory }.flatMap(recursiveListFiles).filter(!_.isDirectory)
|
||||
}
|
||||
def zip(out: String, files: Iterable[String]) = {
|
||||
import java.io.{ BufferedInputStream, FileInputStream, FileOutputStream }
|
||||
import java.util.zip.{ ZipEntry, ZipOutputStream }
|
||||
|
||||
val zip = new ZipOutputStream(new FileOutputStream(out))
|
||||
|
||||
files.foreach { name =>
|
||||
val fname = sourceDir.toURI.relativize(new File(name).toURI).toString
|
||||
//println(s"Zipping ${fname}")
|
||||
if (!new File(name).isDirectory) {
|
||||
zip.putNextEntry(new ZipEntry(fname))
|
||||
val in = new BufferedInputStream(new FileInputStream(name))
|
||||
var b = in.read()
|
||||
while (b > -1) {
|
||||
zip.write(b)
|
||||
b = in.read()
|
||||
}
|
||||
in.close()
|
||||
zip.closeEntry()
|
||||
}
|
||||
}
|
||||
zip.close()
|
||||
}
|
||||
val fileList = recursiveListFiles(sourceDir.getCanonicalFile).toList
|
||||
val zipFileName = new File(bundleBuildPath, "i2pbase.zip").getCanonicalPath
|
||||
zip(zipFileName, fileList.map { f => f.toString }.toIterable)
|
||||
zipFileName.toString
|
||||
}
|
||||
|
||||
buildAppBundleTask := {
|
||||
println(s"Building Mac OS X bundle for I2P version ${i2pVersion}.")
|
||||
bundleBuildPath.mkdir()
|
||||
if (!bundleBuildPath.exists()) bundleBuildPath.mkdir()
|
||||
val paths = Map[String,File](
|
||||
"execBundlePath" -> new File(bundleBuildPath, "I2P.app/Contents/MacOS"),
|
||||
"resBundlePath" -> new File(bundleBuildPath, "I2P.app/Contents/Resources"),
|
||||
"i2pbaseBunldePath" -> new File(bundleBuildPath, "I2P.app/Contents/Resources/i2pbase"),
|
||||
"i2pJarsBunldePath" -> new File(bundleBuildPath, "I2P.app/Contents/Resources/i2pbase/lib"),
|
||||
"webappsBunldePath" -> new File(bundleBuildPath, "I2P.app/Contents/Resources/i2pbase/webapps")
|
||||
"resBundlePath" -> new File(bundleBuildPath, "I2P.app/Contents/Resources")
|
||||
)
|
||||
paths.map { case (s,p) => p.mkdirs() }
|
||||
val dirsToCopy = List("certificates","locale","man")
|
||||
|
||||
val launcherBinary = Some(assembly.value)
|
||||
launcherBinary.map { l => IO.copyFile( new File(l.toString), new File(paths.get("execBundlePath").get, "I2P") ) }
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* First of, if "map" is unknown for you - shame on you :p
|
||||
*
|
||||
* It's a loop basically where it loops through a list/array
|
||||
* with the current indexed item as subject.
|
||||
*
|
||||
* The code bellow takes the different lists and
|
||||
* copy all the directories or files from the i2p.i2p build dir,
|
||||
* and into the bundle so the launcher will know where to find i2p.
|
||||
*
|
||||
*/
|
||||
dirsToCopy.map { d => IO.copyDirectory( new File(resDir, d), new File(paths.get("i2pbaseBunldePath").get, d) ) }
|
||||
warsForCopy.map { w => IO.copyFile( new File(new File(i2pBuildDir, "webapps"), w), new File(paths.get("webappsBunldePath").get, w) ) }
|
||||
jarsForCopy.map { j => IO.copyFile( new File(new File(i2pBuildDir, "lib"), j), new File(paths.get("i2pJarsBunldePath").get, j) ) }
|
||||
val plistFile = new File("./macosx/Info.plist")
|
||||
if (plistFile.exists()) {
|
||||
println(s"Adding Info.plist...")
|
||||
IO.copyFile(plistFile, new File(bundleBuildPath, "I2P.app/Contents/Info.plist"))
|
||||
}
|
||||
|
||||
val zipFilePath = Some(buildDeployZipTask.value)
|
||||
|
||||
val zipFileOrigin = new File(zipFilePath.get)
|
||||
IO.copyFile(zipFileOrigin, new File(paths.get("resBundlePath").get, "i2pbase.zip"))
|
||||
println(s"Zip placed into bundle :)")
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
package net.i2p.launchers.osx
|
||||
|
||||
import java.awt.SystemTray
|
||||
import java.io.File
|
||||
|
||||
import net.i2p.launchers.{DeployProfile, OSXDefaults, OSXDeployment}
|
||||
import net.i2p.launchers.{CompleteDeployment, OSXDefaults}
|
||||
|
||||
import scala.concurrent.{Await, Future}
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.duration._
|
||||
import scala.sys.process.Process
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -36,14 +43,53 @@ object LauncherAppMain extends App {
|
||||
|
||||
val i2pBaseDir = OSXDefaults.getOSXBaseDirectory
|
||||
|
||||
new OSXDeployment()
|
||||
val selfDirPath = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath).getParentFile
|
||||
|
||||
// Tricky to get around, but feels hard to use a "var" which means mutable..
|
||||
// It's like cursing in the church... Worse.
|
||||
var sysTray: Option[SystemTrayManager] = None
|
||||
|
||||
val deployment = new CompleteDeployment(new File(selfDirPath.getPath, "../Resources/i2pbase.zip"), i2pBaseDir)
|
||||
|
||||
val depProc = deployment.makeDeployment
|
||||
// Change directory to base dir
|
||||
System.setProperty("user.dir", i2pBaseDir.getAbsolutePath)
|
||||
|
||||
// System shutdown hook
|
||||
sys.ShutdownHookThread {
|
||||
println("exiting launcher process")
|
||||
}
|
||||
|
||||
Await.ready(depProc, 60000 millis)
|
||||
|
||||
println("I2P Base Directory Extracted.")
|
||||
|
||||
try {
|
||||
MacOSXRouterLauncher.runRouter(i2pBaseDir, args)
|
||||
val routerProcess: Future[Process] = MacOSXRouterLauncher.runRouter(i2pBaseDir, args)
|
||||
|
||||
if (SystemTray.isSupported) {
|
||||
sysTray = Some(new SystemTrayManager)
|
||||
}
|
||||
|
||||
routerProcess onComplete {
|
||||
case Success(forkResult) => {
|
||||
println(s"Router started successfully!")
|
||||
try {
|
||||
val routerPID = MacOSXRouterLauncher.pid(forkResult)
|
||||
println(s"PID is ${routerPID}")
|
||||
} catch {
|
||||
case ex:java.lang.RuntimeException => println(s"Minor error: ${ex.getMessage}")
|
||||
}
|
||||
if (!sysTray.isEmpty) sysTray.get.setRunning(true)
|
||||
}
|
||||
case Failure(fail) => {
|
||||
println(s"Router failed to start, error is: ${fail.toString}")
|
||||
}
|
||||
}
|
||||
|
||||
//Await.result(routerProcess, 5000 millis)
|
||||
|
||||
} finally {
|
||||
System.out.println("Exit.")
|
||||
System.out.println("Exit?")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package net.i2p.launchers.osx
|
||||
|
||||
import java.io.File
|
||||
import java.lang.reflect.Field
|
||||
|
||||
import scala.sys.process.Process
|
||||
import net.i2p.launchers.RouterLauncher
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
@@ -13,25 +17,73 @@ import net.i2p.launchers.RouterLauncher
|
||||
*/
|
||||
object MacOSXRouterLauncher extends RouterLauncher {
|
||||
|
||||
override def runRouter(args: Array[String]): Unit = {}
|
||||
def pid(p: Process): Long = {
|
||||
val procField = p.getClass.getDeclaredField("p")
|
||||
procField.synchronized {
|
||||
procField.setAccessible(true)
|
||||
val proc = procField.get(p)
|
||||
try {
|
||||
proc match {
|
||||
case unixProc
|
||||
if unixProc.getClass.getName == "java.lang.UNIXProcess" => {
|
||||
val pidField = unixProc.getClass.getDeclaredField("pid")
|
||||
pidField.synchronized {
|
||||
pidField.setAccessible(true)
|
||||
try {
|
||||
pidField.getLong(unixProc)
|
||||
} finally {
|
||||
pidField.setAccessible(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
case procImpl:java.lang.Process => {
|
||||
val f: Field = p.getClass().getDeclaredField("p")
|
||||
val f2: Field = f.get(p).getClass.getDeclaredField("pid")
|
||||
try {
|
||||
f.setAccessible(true)
|
||||
f2.setAccessible(true)
|
||||
val pid = f2.getLong(p)
|
||||
pid
|
||||
} finally {
|
||||
f2.setAccessible(false)
|
||||
f.setAccessible(false)
|
||||
}
|
||||
}
|
||||
// If someone wants to add support for Windows processes,
|
||||
// this would be the right place to do it:
|
||||
case _ => throw new RuntimeException(
|
||||
"Cannot get PID of a " + proc.getClass.getName)
|
||||
}
|
||||
} finally {
|
||||
procField.setAccessible(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def runRouter(basePath: File, args: Array[String]): Unit = {
|
||||
lazy val javaOpts = Seq(
|
||||
"-Xmx512M",
|
||||
"-Xms128m",
|
||||
"-Dwrapper.logfile=/tmp/router.log",
|
||||
"-Dwrapper.logfile.loglevel=DEBUG",
|
||||
"-Dwrapper.java.pidfile=/tmp/routerjvm.pid",
|
||||
"-Dwrapper.console.loglevel=DEBUG",
|
||||
s"-Di2p.dir.base=${basePath}",
|
||||
s"-Djava.library.path=${basePath}"
|
||||
)
|
||||
val javaOptsString = javaOpts.map(_ + " ").mkString
|
||||
val cli = s"""java -cp "${new File(basePath, "lib").listFiles().map{f => f.toPath.toString.concat(":")}.mkString}." ${javaOptsString} net.i2p.router.Router"""
|
||||
println(s"CLI => ${cli}")
|
||||
val pb = Process(cli)
|
||||
// Use "run" to let it fork in behind
|
||||
val exitCode = pb.!
|
||||
|
||||
// ??? equals "throw not implemented" IIRC - it compiles at least :)
|
||||
override def runRouter(args: Array[String]): Future[Process] = ???
|
||||
|
||||
def runRouter(basePath: File, args: Array[String]): Future[Process] = {
|
||||
Future {
|
||||
lazy val javaOpts = Seq(
|
||||
"-Xmx512M",
|
||||
"-Xms128m",
|
||||
"-Djava.awt.headless=true",
|
||||
"-Dwrapper.logfile=/tmp/router.log",
|
||||
"-Dwrapper.logfile.loglevel=DEBUG",
|
||||
"-Dwrapper.java.pidfile=/tmp/routerjvm.pid",
|
||||
"-Dwrapper.console.loglevel=DEBUG",
|
||||
s"-Di2p.dir.base=${basePath}",
|
||||
s"-Djava.library.path=${basePath}"
|
||||
)
|
||||
val javaOptsString = javaOpts.map(_ + " ").mkString
|
||||
val cli = s"""java -cp "${new File(basePath, "lib").listFiles().map{f => f.toPath.toString.concat(":")}.mkString}." ${javaOptsString} net.i2p.router.Router"""
|
||||
println(s"CLI => ${cli}")
|
||||
val pb = Process(cli)
|
||||
// Use "run" to let it fork in behind
|
||||
pb.run
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.i2p.launchers.osx
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Meeh
|
||||
* @since 0.9.35
|
||||
*/
|
||||
class SystemTrayManager {
|
||||
|
||||
object RouterState {
|
||||
var isRunning: Boolean = false
|
||||
var startupTime: Long = 0L
|
||||
}
|
||||
|
||||
def isRunning = RouterState.isRunning
|
||||
|
||||
def setRunning(runs: Boolean): Unit = {
|
||||
if (runs) setStartupTime()
|
||||
RouterState.isRunning = runs
|
||||
}
|
||||
|
||||
def setStartupTime() = {
|
||||
RouterState.startupTime = System.currentTimeMillis / 1000
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user