Build: Remove OSX and browser launcher code

Will probably be replaced with jpackage in i2p-jpackage-mac repo
If we do resurrect the launcher, it will be moved to a separate repo
This commit is contained in:
zzz
2021-04-05 09:14:34 -04:00
parent e16c80516d
commit 039d918454
175 changed files with 0 additions and 16822 deletions

View File

@ -31,8 +31,6 @@
<echo message=" installer-windows: build the GUI installer (Windows only)" />
<echo message=" installer-nowindows: build the GUI installer (all but Windows)" />
<echo message=" installer5, installer5-linux, installer5-nowindows, installer5-windows: use IzPack 5" />
<echo message=" osxLauncher: build the Mac OS X router/GUI launcher (OSX only)" />
<echo message=" bbLauncher: build the Browser Bundle router launcher" />
<echo message=" bundle: (GIT ONLY!) generate a git bundle and a corresponding torrent." />
<echo message=" tarball: tar the full install into i2p.tar.bz2 (extracts to build a new clean install)" />
<echo message=" git-bundle: (GIT ONLY!) generate a git bundle and a corresponding torrent." />
@ -313,28 +311,6 @@
<echo message="Epoch is: ${epoch}" />
</target>
<target name="bbLauncher" depends="build">
<sequential>
<exec executable="./sbt" dir="launchers" failonerror="true">
<arg value="browserbundle:clean" />
</exec>
<exec executable="./sbt" dir="launchers" failonerror="true">
<arg value="browserbundle:assembly" />
</exec>
</sequential>
</target>
<target name="osxLauncher" depends="build,preppkg-osx">
<sequential>
<exec executable="./sbt" dir="launchers" failonerror="true">
<arg value="macosx:cleanAllTask" />
</exec>
<exec executable="./sbt" dir="launchers" failonerror="true">
<arg value="macosx:buildAppBundleTask" />
</exec>
</sequential>
</target>
<target name="buildBOB" depends="buildMinistreaming" >
<ant dir="apps/BOB/" target="jar" />
<copy file="apps/BOB/dist/BOB.jar" todir="build/" />
@ -2804,7 +2780,6 @@
<fileset dir="../i2p-${Extended.Version}/" includes="Docker*" />
<file name="../i2p-${Extended.Version}/Makefile.gcj" />
<fileset dir="../i2p-${Extended.Version}/docs" />
<fileset dir="../i2p-${Extended.Version}/launchers" />
<file name="../i2p-${Extended.Version}/.travis.yml" />
<file name="../i2p-${Extended.Version}/.gitignore" />
<!-- gradle files -->
@ -2881,7 +2856,6 @@
<fileset dir="../i2p-${Extended.Version}/" includes="Docker*" />
<file name="../i2p-${Extended.Version}/Makefile.gcj" />
<fileset dir="../i2p-${Extended.Version}/docs" />
<fileset dir="../i2p-${Extended.Version}/launchers" />
<file name="../i2p-${Extended.Version}/.travis.yml" />
<file name="../i2p-${Extended.Version}/.gitignore" />
<!-- gradle files -->
@ -2961,7 +2935,6 @@
<fileset dir="../i2p-${Extended.Version}/" includes="Docker*" />
<file name="../i2p-${Extended.Version}/Makefile.gcj" />
<fileset dir="../i2p-${Extended.Version}/docs" />
<fileset dir="../i2p-${Extended.Version}/launchers" />
<file name="../i2p-${Extended.Version}/.travis.yml" />
<file name="../i2p-${Extended.Version}/.gitignore" />
<!-- gradle files -->
@ -3028,7 +3001,6 @@
<fileset dir="../i2p-${Extended.Version}/" includes="Docker*" />
<file name="../i2p-${Extended.Version}/Makefile.gcj" />
<fileset dir="../i2p-${Extended.Version}/docs" />
<fileset dir="../i2p-${Extended.Version}/launchers" />
<file name="../i2p-${Extended.Version}/.travis.yml" />
<file name="../i2p-${Extended.Version}/.gitignore" />
<!-- gradle files -->

View File

@ -1,11 +0,0 @@
# The Launcher
Here you'll find the code for the next generation Mac OS X launcher, as well as the Browser Bundle i2p launcher.
Anyway, if you wanna build it and have fun, just do `./sbt`
Scala isn't scary - it's java except the ;;;;;;;; ;)
https://www.scala-lang.org/
https://www.scala-sbt.org/

View File

@ -1,5 +0,0 @@
# The browser bundle i2p launcher
This code won't really make any sense for other than in the portable i2p that's shipped with the browser bundle.

View File

@ -1,23 +0,0 @@
assemblyExcludedJars in assembly := {
val donts = List(
"BOB.jar",
"sam.jar",
"desktopgui.jar",
"i2ptunnel-ui.jar",
"i2psnark.jar",
"jetty-sslengine.jar"
)
val cp = (fullClasspath in assembly).value
cp filter { s => donts.contains(s.data.getName)}
}
// Unmanaged base will be included in a fat jar
unmanagedBase := baseDirectory.value / ".." / ".." / "build"
// Unmanaged classpath will be available at compile time
unmanagedClasspath in Compile ++= Seq(
baseDirectory.value / ".." / ".." / "build" / "*.jar"
)

View File

@ -1,108 +0,0 @@
package net.i2p
import java.io.{File, InputStream}
//import net.i2p.Router
import net.i2p.launchers.DeployProfile
/**
*
* For java developers:
* A scala object is like an instance of a class.
* If you define a method inside an object, it's equals to
* java's static methods.
*
* Also, in scala, the body of a class/object is executed as it's
* constructor.
*
* Also noteworthy;
* val is immutable
* var is mutable
*
*
* @author Meeh
* @version 0.0.1
* @since 0.9.35
*/
object RouterLauncherApp extends App {
def toBytes(xs: Int*) = xs.map(_.toByte).toArray
def getInt(bytes: Array[Byte]): Int = (bytes(3) << 24) & 0xff000000 | (bytes(2) << 16) & 0x00ff0000 | (bytes(1) << 8) & 0x0000ff00 | (bytes(0) << 0) & 0x000000ff
/**
* Encodes bytes in "binary form" as mentioned in Native Messaging in the WebExtension API.
* @param length
* @return
*/
def getBytes(length: Int): Array[Byte] = {
val bytes = new Array[Byte](4)
bytes(0) = (length & 0xFF).toByte
bytes(1) = ((length >> 8) & 0xFF).toByte
bytes(2) = ((length >> 16) & 0xFF).toByte
bytes(3) = ((length >> 24) & 0xFF).toByte
bytes
}
def readMessage(in: InputStream): String = {
val arr = new Array[Byte](4)
in.read(arr)
val bytes = new Array[Byte](getInt(arr))
in.read(bytes)
new String(bytes, "UTF-8")
}
def sendMessage(message: String): Unit = {
System.out.write(getBytes(message.length))
System.out.write(message.getBytes("UTF-8"))
}
// Executed at launch
val basePath = Option(System.getProperty("i2p.dir.base")).getOrElse(System.getenv("I2PBASE"))
val configPath = Option(System.getProperty("i2p.dir.config")).getOrElse(System.getenv("I2PCONFIG"))
println(s"basePath => ${basePath}\nconfigPath => ${configPath}")
/*
object ErrorUtils {
def errorMessageInJson(message: String, solution: String) : JObject = JObject(
List(
("error",
JObject(
("message", JString(message)),
("solution", JString(solution))
)
)
)
)
def printError(message: String, solution: String): Unit = {
println(compact(render( errorMessageInJson(message,solution) )))
}
def printErrorAndExit(message: String, solution: String, exitCode: Int = 1): Unit = {
printError(message, solution)
System.exit(exitCode)
}
}
// Path related error checking
if (basePath == null || basePath.isEmpty) ErrorUtils.printErrorAndExit("I2P Base path is missing", "set property i2p.dir.base or environment variable I2PBASE")
if (configPath == null || configPath.isEmpty) ErrorUtils.printErrorAndExit("I2P Config path is missing", "set property i2p.dir.config or environment variable I2PCONFIG")
if (!new File(basePath).exists()) ErrorUtils.printErrorAndExit("I2P Base path don't exist", "Reinstall the Browser Bundle")
if (!new File(configPath).exists()) ErrorUtils.printErrorAndExit("I2P Config path don't exist", "Delete your config directory for the Browser Bundle")
*/
val deployer = new DeployProfile(configPath,basePath)
deployer.verifyExistenceOfConfig()
// Required/mocked properties
System.setProperty("wrapper.version", "portable-1")
System.setProperty("i2p.dir.portableMode", "true")
System.setProperty("loggerFilenameOverride", "router-@.log")
//ErrorUtils.printError(s"Starting up with arguments ${(args mkString ", ")}",":)")
//Router.main(args)
}

View File

@ -1,58 +0,0 @@
import sbt._
import Keys._
scalaVersion in Global := "2.11.11"
resolvers ++= Seq(
DefaultMavenRepository,
Resolver.mavenLocal,
Resolver.sonatypeRepo("releases"),
Resolver.typesafeRepo("releases"),
Resolver.sbtPluginRepo("releases")
)
lazy val commonSettings = Seq(
organization := "net.i2p",
scalaVersion := "2.11.11", // We have to use Scala 11 as long as we're going to support JRE 1.7
version := "0.1.0-SNAPSHOT",
maintainer := "Meeh <mikalv@mikalv.net>",
packageSummary := "The Invisible Internet Project",
packageDescription := "Blabla"
)
lazy val common = (project in file("common"))
.settings(
commonSettings,
name := "LauncherCommon"
)
lazy val browserbundle = (project in file("browserbundle"))
.settings(
commonSettings,
name := "RouterLaunchApp",
assemblyJarName in assembly := s"${name.value}-${version.value}.jar",
mainClass in assembly := Some("net.i2p.RouterLauncherApp"),
libraryDependencies ++= Seq(
"org.json4s" %% "json4s-native" % "3.5.3"
)
).dependsOn(common)
lazy val macosx = (project in file("macosx"))
.settings(
commonSettings,
name := "routerLauncher",
mainClass in assembly := Some("net.i2p.launchers.SimpleOSXLauncher")
).dependsOn(common)
lazy val root = (project in file("."))
.aggregate(common, browserbundle, macosx)
javacOptions ++= Seq("-source", "1.7", "-target", "1.7")
scalacOptions in Compile := Seq("-deprecated","-target:jvm-1.7")
fork := true
run / javaOptions += "-Xmx512M"
run / connectInput := true

View File

@ -1 +0,0 @@
libraryDependencies += "org.xeustechnologies" % "jcl-core" % "2.8"

View File

@ -1,45 +0,0 @@
package net.i2p.launchers
import java.io.{File, InputStream}
/**
* This represent a file or a directory. A filesystem resource.
* It's used to handle a correct deployment of base files, even
* installer resources got kind of a flat list. (i.e geoip files
* is placed under a dir geoip)
*
* With this class we can now have a list containing both directories
* and files in a type safe list.
*
* @param path
* @param content
* @param files
* @since 0.9.35
*/
class FDObject(path: String, content: Option[InputStream], files: Option[List[File]] = None, subDirectories: Boolean = false) {
def isFile = files.isEmpty
def getPath = path
def filesIsDirectories = subDirectories
def getContent: Either[InputStream, List[File]] = {
if (files.isEmpty) return Left(content.get)
Right(files.get.map { f => new File(DeployProfile.pathJoin(path, f.getName)) })
}
}
class FDObjFile(file: String) extends FDObject(new File(file).getPath, Some(getClass.getResourceAsStream("/".concat(file))) )
class FDObjDir(name: String, files: List[String],subDirectories:Boolean=false) extends FDObject(name, None, Some(files.map { s => new File(s) }),subDirectories)
/**
*
* This is a case class, it's a Scala thing. Think of it as a type.
* If you're familiar with C, it's like a "typedef" even iirc Scala
* has a own function for that as well.
*
*
*/

View File

@ -1,48 +0,0 @@
package net.i2p.launchers
import java.io.{File, FileInputStream, FileOutputStream, InputStream}
import java.nio.file.Path
import java.util.zip.ZipInputStream
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
/**
*
* CompleteDeployment - In use to deploy base path for the Mac OS X Bundle release.
*
* @author Meeh
* @since 0.9.35
*/
class CompleteDeployment(val zipFile: File, val i2pBaseDir: File) {
if (!i2pBaseDir.exists()) {
i2pBaseDir.mkdirs()
} else {
// TODO: Check what version etc..
}
def unzip(zipFile: InputStream, destination: Path): Unit = {
val zis = new ZipInputStream(zipFile)
Stream.continually(zis.getNextEntry).takeWhile(_ != null).foreach { file =>
if (!file.isDirectory) {
val outPath = destination.resolve(file.getName)
val outPathParent = outPath.getParent
if (!outPathParent.toFile.exists()) {
outPathParent.toFile.mkdirs()
}
val outFile = outPath.toFile
val out = new FileOutputStream(outFile)
val buffer = new Array[Byte](4096)
Stream.continually(zis.read(buffer)).takeWhile(_ != -1).foreach(out.write(buffer, 0, _))
}
}
}
def makeDeployment : Future[Unit] = Future {
unzip(new FileInputStream(zipFile), i2pBaseDir.toPath)
}
}

View File

@ -1,147 +0,0 @@
package net.i2p.launchers
import java.io.{File, IOException, InputStream}
import java.nio.file.FileAlreadyExistsException
/**
*
* This is a singleton ish style. It's an object - it was never a class.
* However it has a class-"brother/sister"
*
* objects like this should be considered like containers for java's
* public static methods.
*
* Many classes/objects benefits from pathJoin, so therefore it's defined
* as an "public static method".
*
* @since 0.9.35
*/
object DeployProfile {
/**
* This function will find the executing jar. "myself"
* @return
*/
def executingJarFile = getClass().getProtectionDomain().getCodeSource().getLocation()
/**
* This joins two paths in a cross platform way. Meaning it takes care of either we use
* \\ or / as directory separator. It returns the resulting path in a string.
*
* @since 0.9.35
* @param parent The parent path
* @param child The child path to append
* @return String
*/
def pathJoin(parent:String,child:String): String = new File(new File(parent), child).getPath
}
/**
*
* The purpose of this class is to copy files from the i2p "default config" directory
* and to a "current config" directory relative to the browser bundle - but this class is
* also used by the OSX launcher since it shares common properties like that the bundle has
* to be read only.
*
* @author Meeh
* @version 0.0.1
* @since 0.9.35
*/
class DeployProfile(val confDir: String, val baseDir: String) {
import java.nio.file.{Files, Paths}
/**
* This function copies resources from the fatjar to the config directory of i2p.
*
* @since 0.9.35
* @param fStr
* @return Unit
*/
def copyFileResToDisk(fStr: String) = try { Files.copy(
getClass.getResource("/".concat(fStr)).getContent.asInstanceOf[InputStream],
Paths.get(DeployProfile.pathJoin(confDir, fStr)).normalize()
)} catch {
case _:FileAlreadyExistsException => {} // Ignored
case ex:IOException => println(s"Error! Exception ${ex}")
}
/**
* This function copies resources from the fatjar to the config directory of i2p.
*
* @since 0.9.35
* @param path
* @param is
* @return Unit
*/
def copyBaseFileResToDisk(path: String, is: InputStream) = try { Files.copy(
is,
Paths.get(DeployProfile.pathJoin(baseDir, path)).normalize()
)} catch {
case _:FileAlreadyExistsException => {} // Ignored
case ex:IOException => println(s"Error! Exception ${ex}")
}
/**
* Filter function for finding missing required files.
*
* @since 0.9.35
* @param l1
* @param l2
* @return
*/
def missingFiles(l1: List[String], l2: List[String]) = l1.filter { x => !l2.contains(x) }
val warFiles = List("routerconsole.war")
val staticFiles = List(
"blocklist.txt",
"clients.config",
"continents.txt",
"countries.txt",
"hosts.txt",
"geoip.txt",
"i2ptunnel.config",
"logger.config",
"router.config",
"webapps.config"
)
/**
*
* This function will check the existence of static files,
* and if any of them are lacking, it will be copied from the
* fat jar's resources.
*
* @since 0.9.35
* @return Unit (Null)
*/
def verifyExistenceOfConfig() = {
val fDir = new File(confDir)
if (fDir.exists()) {
// We still check if files are in place
val currentDirContentList = fDir.list.toList
val missing = missingFiles(staticFiles, currentDirContentList)
if (!missing.isEmpty) {
missing.map(copyFileResToDisk)
}
} else {
// New deployment!
deployDefaultConfig()
}
}
/**
*
* This function does the default deployment of files,
* map is the same as a loop. we're looping over the file list.
*
* @since 0.9.35
* @return Unit
*/
def deployDefaultConfig(): Unit = staticFiles.map(copyFileResToDisk)
}

View File

@ -1,18 +0,0 @@
package net.i2p.launchers
import java.io.File
/**
* Defaults object for Mac OS X
*
*
* @author Meeh
* @since 0.9.35
*/
object OSXDefaults {
def getOSXConfigDirectory = new File(DeployProfile.pathJoin(System.getProperty("user.home"), "Library/Application Support/i2p"))
def getOSXBaseDirectory = new File(DeployProfile.pathJoin(System.getProperty("user.home"),"Library/I2P"))
}

View File

@ -1,248 +0,0 @@
package net.i2p.launchers
import java.io.{File, IOException}
import java.nio.file.FileAlreadyExistsException
import java.util.zip.ZipFile
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
import java.nio.file.Files.copy
import java.nio.file.Paths.get
import collection.JavaConverters._
/**
*
* NOTE: Work in progress: Originally written for OSX launcher - but will be used in BB launcher.
*
* PartialXDeployment
*
* This class can be a bit new for java developers. In Scala, when inherit other classes,
* you would need to define their arguments if the super class only has constructors taking arguments.
*
* This child class don't take arguments, but rather get them from the signleton OSXDefaults.
*
* This class should be able to copy recursive resources into correct position for a normal deployment.
*
*
* This class might look like black magic for some. But what it does is to deploy a structure like for example:
* tree ~/Library/I2P
* /Users/mikalv/Library/I2P
* ├── blocklist.txt
* ├── certificates
* │   ├── family
* │   │   ├── gostcoin.crt
* │   │   ├── i2p-dev.crt
* │   │   ├── i2pd-dev.crt
* │   │   └── volatile.crt
* │   ├── i2ptunnel
* │   ├── news
* │   │   ├── ampernand_at_gmail.com.crt
* │   │   ├── echelon_at_mail.i2p.crt
* │   │   ├── str4d_at_mail.i2p.crt
* │   │   └── zzz_at_mail.i2p.crt
* │   ├── plugin
* │   │   ├── cacapo_at_mail.i2p.crt
* │   │   ├── str4d_at_mail.i2p.crt
* │   │   └── zzz-plugin_at_mail.i2p.crt
* │   ├── reseed
* │   │   ├── atomike_at_mail.i2p.crt
* │   │   ├── backup_at_mail.i2p.crt
* │   │   ├── bugme_at_mail.i2p.crt
* │   │   ├── creativecowpat_at_mail.i2p.crt
* │   │   ├── echelon_at_mail.i2p.crt
* │   │   ├── hottuna_at_mail.i2p.crt
* │   │   ├── igor_at_novg.net.crt
* │   │   ├── lazygravy_at_mail.i2p.crt
* │   │   ├── meeh_at_mail.i2p.crt
* │   │   └── zmx_at_mail.i2p.crt
* │   ├── revocations
* │   ├── router
* │   │   ├── echelon_at_mail.i2p.crt
* │   │   ├── str4d_at_mail.i2p.crt
* │   │   └── zzz_at_mail.i2p.crt
* │   └── ssl
* │   ├── echelon.reseed2017.crt
* │   ├── i2p.mooo.com.crt
* │   ├── i2pseed.creativecowpat.net.crt
* │   ├── isrgrootx1.crt
* │   └── reseed.onion.im.crt
* ├── clients.config
* ├── geoip
* │   ├── continents.txt
* │   ├── countries.txt
* │   ├── geoip.txt
* │   └── geoipv6.dat.gz
* ├── hosts.txt
* └── i2ptunnel.config
*
* @author Meeh
* @since 0.9.35
*/
class PartialDeployment extends
DeployProfile(
OSXDefaults.getOSXConfigDirectory.getAbsolutePath,
OSXDefaults.getOSXBaseDirectory.getAbsolutePath
) {
/**
* This list is a micro DSL for how files should
* be deployed to the filesystem in the base
* directory.
*/
val staticFilesFromResources = List(
new FDObjFile("blocklist.txt"),
new FDObjFile("clients.config"),
new FDObjFile("hosts.txt"),
new FDObjDir("geoip", files = List(
"continents.txt",
"countries.txt",
"geoip.txt",
"geoipv6.dat.gz")),
new FDObjFile("i2ptunnel.config"),
new FDObjDir("certificates", List(
"family",
"i2ptunnel",
"news",
"plugin",
"reseed",
"revocations",
"router",
"ssl"
),subDirectories=true),
new FDObjDir("themes",List(
"console",
"imagegen",
"snark",
"susidns",
"susimail"
),true)
)
/**
* This function copies an directory of files from the jar
* to the base directory defined in the launcher.
* @param dir
* @return
*/
def copyDirFromRes(dir: File): Unit = {
// A small hack
try {
val zipFile = new ZipFile(DeployProfile.executingJarFile.getFile)
zipFile.entries().asScala.toList.filter(_.toString.startsWith(dir.getPath)).filter(!_.isDirectory).map { entry =>
new File(DeployProfile.pathJoin(baseDir,entry.getName)).getParentFile.mkdirs()
if (entry.isDirectory) {
createFileOrDirectory(new File(DeployProfile.pathJoin(baseDir,entry.getName)), true)
} else {
copyBaseFileResToDisk(entry.getName, getClass.getResourceAsStream("/".concat(entry.getName)))
}
}
} catch {
case _:FileAlreadyExistsException => {} // Ignored
case ex:IOException => println(s"Error! Exception ${ex}")
}
}
/**
* This function will depending on directory or not copy either the file
* or create the directory and copy directory content if any.
*
* @param file
* @param isDir
* @return
*/
def createFileOrDirectory(file: File, isDir: Boolean = false): Unit = {
if (file != null) {
//println(s"createFileOrDirectory(${file},${isDir})")
try {
// Make sure subject exists if directory
if (!new File(DeployProfile.pathJoin(baseDir,file.getPath)).exists()) {
if (isDir) new File(DeployProfile.pathJoin(baseDir,file.getPath)).mkdirs()
}
if (isDir) {
// Handle dir
copyDirFromRes(file)
} else {
// Handle file
copyBaseFileResToDisk(file.getPath, getClass.getResourceAsStream("/".concat(file.getName)))
}
} catch {
case _:FileAlreadyExistsException => {} // Ignored
case ex:IOException => println(s"Error! Exception ${ex}")
}
}
}
if (!new File(baseDir).exists()) {
new File(baseDir).mkdirs()
}
implicit def toPath (filename: String) = get(filename)
val selfFile = new File(DeployProfile.executingJarFile.getFile)
val selfDir = selfFile.getParentFile
val resDir = new File(selfDir.getParent, "Resources")
val i2pBaseBundleDir = new File(resDir, "i2pbase")
val i2pBundleJarDir = new File(i2pBaseBundleDir, "lib")
val i2pBaseDir = OSXDefaults.getOSXBaseDirectory
val i2pDeployJarDir = new File(i2pBaseDir, "lib")
if (!i2pDeployJarDir.exists()) {
i2pDeployJarDir.mkdirs()
i2pBundleJarDir.list().toList.map {
jar => {
copy (
DeployProfile.pathJoin(i2pBundleJarDir.getAbsolutePath, jar),
DeployProfile.pathJoin(i2pDeployJarDir.getAbsolutePath, jar),
REPLACE_EXISTING)
println(s"Copied ${jar} to right place")
}
}
}
/**
* Please note that in Scala, the constructor body is same as class body.
* What's defined outside of methods is considered constructor code and
* executed as it.
*
*
*
* a map function work as a loop with some built in security
* for "null" objects.
* What happens here is "for each staticFilesFromResources" do =>
*
* Then, based upon if it's a file or a directory, different actions take place.
*
* the match case is controlling the flow based upon which type of object it is.
*/
staticFilesFromResources.map {
fd => fd.getContent match {
// Case subject is a file/resource
case Left(is) => {
// Write file
val f = DeployProfile.pathJoin(baseDir, fd.getPath)
println(s"f: ${f.toString}")
if (!new File(f).exists()) {
//println(s"copyBaseFileResToDisk(${fd.getPath})")
try {
copyBaseFileResToDisk(fd.getPath, is)
} catch {
case ex:IOException => println(s"Error! Exception ${ex}")
case _:FileAlreadyExistsException => {} // Ignored
}
}
}
// Case subject is a directory
case Right(dir) => {
// Ensure directory
//println(s"Directory(${fd.getPath})")
if (!new File(DeployProfile.pathJoin(baseDir,fd.getPath)).exists()) {
new File(DeployProfile.pathJoin(baseDir,fd.getPath)).mkdirs()
}
dir.map { f => createFileOrDirectory(f,fd.filesIsDirectories) }
}
}
}
}

View File

@ -1,19 +0,0 @@
package net.i2p.launchers
import java.io.File
import scala.concurrent.Future
import scala.sys.process.Process
/**
* A abstract class is kind of like an java interface.
*
* @author Meeh
* @since 0.9.35
*/
abstract class RouterLauncher {
def runRouter(basePath: File, args: Array[String]): Future[Process]
def runRouter(args: Array[String]): Future[Process]
}

View File

@ -1,36 +0,0 @@
{
"files.associations": {
"*.jsm": "javascript",
"*.grd": "xml",
"*.plist": "xml",
"*.jxa": "javascript",
"*.gni": "javascript",
"*.gn": "javascript",
"type_traits": "cpp",
"__config": "cpp",
"__nullptr": "cpp",
"cstddef": "cpp",
"exception": "cpp",
"initializer_list": "cpp",
"new": "cpp",
"stdexcept": "cpp",
"typeinfo": "cpp",
"algorithm": "cpp",
"ios": "cpp",
"iosfwd": "cpp",
"__split_buffer": "cpp",
"list": "cpp",
"string": "cpp",
"vector": "cpp",
"iterator": "cpp",
"optional": "cpp",
"memory": "cpp",
"map": "cpp",
"__mutex_base": "cpp",
"mutex": "cpp",
"functional": "cpp",
"__functional_03": "cpp",
"__string": "cpp",
"system_error": "cpp"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,3 +0,0 @@
github 'sparkle-project/Sparkle' == 1.21.0

View File

@ -1,17 +0,0 @@
# Change Log
## 0.9.38
* Initial alpha/beta ish.
* Preferences dialog (unfinished).
* Firefox detection.
## 0.9.39
* Fixed startup option so the launcher can start at OSX login/bootup.
* Added I2P Browser to the list of "firefox" browsers to detect.
* Changed hardcoded path lookup to native "registry" lookup for firefox application.
* Made the advanced preferences table editable by the user.
* Cleanup of old and/or unused code.
* Bugfixes.

View File

@ -1,44 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="oLh-xo-y8T">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Window Controller-->
<scene sceneID="g0w-9k-Acs">
<objects>
<windowController id="oLh-xo-y8T" sceneMemberID="viewController">
<window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="5Sr-7Y-gfA">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="294" y="362" width="480" height="270"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1177"/>
<connections>
<outlet property="delegate" destination="oLh-xo-y8T" id="Fxx-ke-F0n"/>
</connections>
</window>
<connections>
<segue destination="nXM-BR-Lxj" kind="relationship" relationship="window.shadowedContentViewController" id="3cB-Gf-aj2"/>
</connections>
</windowController>
<customObject id="K5y-Qa-lNJ" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="50" y="118"/>
</scene>
<!--Console View Controller-->
<scene sceneID="MSW-MZ-pqs">
<objects>
<viewController id="nXM-BR-Lxj" customClass="ConsoleViewController" customModule="I2PLauncher" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="Omy-Aq-Pw7">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask"/>
</view>
</viewController>
<customObject id="LZW-ak-seY" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="757" y="93"/>
</scene>
</scenes>
</document>

View File

@ -1,88 +0,0 @@
//
// EmbeddedConsoleView.swift
// I2PLauncher
//
// Created by Mikal Villa on 08/12/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import AppKit
import WebKit
/*
protocol EConsoleViewWrapper {}
class WebViewSource {
class func webView() -> EConsoleViewWrapper {
if #available(OSX 10.12, *) {
//
return EmbeddedConsoleView(coder: NSCoder())!
} else {
// Sorry
return EmbeddedConsoleViewDummy()
}
}
}
extension EConsoleViewWrapper {
static func instantiate(frame frameRect: NSRect) -> EConsoleViewWrapper {
return WebViewSource.webView()
}
}
*/
class ConsoleWindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
/* let v: NSView = WebViewSource.webView() as! NSView
v.wantsLayer = true
self.window?.contentView?.addSubview(v)*/
}
}
class ConsoleViewController: NSViewController {
var webView: WKWebView!
let consoleWebUrl = URL(string: "http://127.0.0.1:7657")
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
//webView.uiDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
webView.load(URLRequest(url: consoleWebUrl!))
}
}
/*
@available(OSX 10.12, *)
class EmbeddedConsoleView: WKWebView, EConsoleViewWrapper {
let consoleWebUrl = URL(string: "http://127.0.0.1:7657")
func setupWebViewForConsole(_ f: NSRect = NSRect(x: 0, y: 0, width: 800, height: 400)) {
self.allowsBackForwardNavigationGestures = true
self.configuration.preferences.javaScriptEnabled = true
self.configuration.preferences.plugInsEnabled = false
self.load(URLRequest(url: consoleWebUrl!))
}
override func viewWillDraw() {
super.viewWillDraw()
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.setupWebViewForConsole()
}
}
class EmbeddedConsoleViewDummy: NSView, EConsoleViewWrapper {}
*/

View File

@ -1,108 +0,0 @@
//
// FirefoxManager.swift
// I2PLauncher
//
// Created by Mikal Villa on 08/12/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
class FirefoxManager {
var firefoxAppPath = ""
private var isFirefoxFound = false
private var isFirefoxProfileExtracted = false
fileprivate func directoryExistsAtPath(_ path: String) -> Bool {
var isDirectory = ObjCBool(true)
let exists = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory)
return exists && isDirectory.boolValue
}
func IsFirefoxFound() -> Bool {
return self.isFirefoxFound
}
func IsProfileExtracted() -> Bool {
return self.isFirefoxProfileExtracted
}
// Since we execute in the "unix/POSIX way", we need the full path of the binary.
func bundleExecutableSuffixPath() -> String {
return "/Contents/MacOS/firefox"
}
func executeFirefox() -> Bool {
let fullExecPath = "\(self.firefoxAppPath)\(self.bundleExecutableSuffixPath())"
let firefoxProcess = Subprocess(executablePath: fullExecPath, arguments: [ "-profile", Preferences.shared()["I2Pref_firefoxProfilePath"] as! String, "http://127.0.0.1:7657/home" ])
DispatchQueue.global(qos: .background).async {
let _ = firefoxProcess.execute()
}
return true
}
/**
*
* First, try find I2P Browser, if it fails, then try Firefox or Firefox Developer.
*
* Instead of using hardcoded paths, or file search we use OS X's internal "registry" API
* and detects I2P Browser or Firefox by bundle name. (or id, but name is more readable)
*
*/
func tryAutoDetect() -> Bool {
var browserPath = NSWorkspace.shared.fullPath(forApplication: "I2P Browser")
if (browserPath == nil)
{
browserPath = NSWorkspace.shared.fullPath(forApplication: "Firefox")
if (browserPath == nil)
{
browserPath = NSWorkspace.shared.fullPath(forApplication: "Firefox Developer")
}
}
self.isFirefoxProfileExtracted = directoryExistsAtPath(Preferences.shared()["I2Pref_firefoxProfilePath"] as! String)
// If browserPath is still nil, then nothing above was found and we can return early.
if (browserPath == nil)
{
return false
}
let result = directoryExistsAtPath(browserPath!)
self.isFirefoxFound = result
if (result) {
self.firefoxAppPath = browserPath!
return true
}
return false
}
private static var sharedFirefoxManager: FirefoxManager = {
let firefoxMgr = FirefoxManager()
return firefoxMgr
}()
class func shared() -> FirefoxManager {
return sharedFirefoxManager
}
}
extension FirefoxManager {
func unzipProfile() -> Bool {
let resourceUrl = Bundle.main.url(forResource: "profile", withExtension: "tgz")
let profileTgz = resourceUrl!.path
let unzipProc = Subprocess(executablePath: "/usr/bin/tar", arguments: ["-xf",profileTgz,"-C",NSString(format: "%@/Library/Application Support/i2p", NSHomeDirectory()) as String])
DispatchQueue.global(qos: .background).async {
let proc = unzipProc.execute(captureOutput: true)
print("Firefox Profile Extraction Errors: \(proc?.errors ?? "false")")
print("Firefox Profile Extraction Output: \(proc?.output ?? "false")")
}
return false
}
}

View File

@ -1,29 +0,0 @@
//
// ArrayExtension.swift
// I2PLauncher
//
// Created by Mikal Villa on 17/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
extension Array where Element: NSAttributedString {
func joined2(separator: NSAttributedString) -> NSAttributedString {
var isFirst = true
return self.reduce(NSMutableAttributedString()) {
(r, e) in
if isFirst {
isFirst = false
} else {
r.append(separator)
}
r.append(e)
return r
}
}
}

View File

@ -1,87 +0,0 @@
//
// DateTimeUtils.swift
// I2PLauncher
//
// Created by Mikal Villa on 18/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
extension Date {
init(date: NSDate) {
self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate)
}
}
extension NSDate {
convenience init(date: Date) {
self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate)
}
}
class DateTimeUtils {
static func timeAgoSinceDate(date:NSDate, numericDates:Bool) -> String {
let calendar = NSCalendar.current
let unitFlags: Set<Calendar.Component> = [.minute, .hour, .day, .weekOfYear, .month, .year, .second]
let now = NSDate()
let earliest = now.earlierDate(date as Date)
let latest = (earliest == now as Date) ? date : now
let components = calendar.dateComponents(unitFlags, from: earliest as Date, to: latest as Date)
if (components.year! >= 2) {
return "\(components.year!) years ago"
} else if (components.year! >= 1){
if (numericDates){
return "1 year ago"
} else {
return "Last year"
}
} else if (components.month! >= 2) {
return "\(components.month!) months ago"
} else if (components.month! >= 1){
if (numericDates){
return "1 month ago"
} else {
return "Last month"
}
} else if (components.weekOfYear! >= 2) {
return "\(components.weekOfYear!) weeks ago"
} else if (components.weekOfYear! >= 1){
if (numericDates){
return "1 week ago"
} else {
return "Last week"
}
} else if (components.day! >= 2) {
return "\(components.day!) days ago"
} else if (components.day! >= 1){
if (numericDates){
return "1 day ago"
} else {
return "Yesterday"
}
} else if (components.hour! >= 2) {
return "\(components.hour!) hours ago"
} else if (components.hour! >= 1){
if (numericDates){
return "1 hour ago"
} else {
return "An hour ago"
}
} else if (components.minute! >= 2) {
return "\(components.minute!) minutes ago"
} else if (components.minute! >= 1){
if (numericDates){
return "1 minute ago"
} else {
return "A minute ago"
}
} else if (components.second! >= 3) {
return "\(components.second!) seconds ago"
} else {
return "Just now"
}
}
}

View File

@ -1,15 +0,0 @@
//
// DispatchQueue+delay.swift
// I2PLauncher
//
// Created by Mikal Villa on 24/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Foundation
extension DispatchQueue {
static func delay(_ delay: DispatchTimeInterval, closure: @escaping () -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: closure)
}
}

View File

@ -1,86 +0,0 @@
//
// EventManager.swift
// I2PLauncher
//
// Created by Mikal Villa on 22/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
// TODO: Log all events?
class EventManager {
var listeners = Dictionary<String, NSMutableArray>();
// Create a new event listener, not expecting information from the trigger
// @param eventName: Matching trigger eventNames will cause this listener to fire
// @param action: The function/lambda you want executed when the event triggers
func listenTo(eventName:String, action: @escaping (()->())) {
let newListener = EventListenerAction(callback: action)
addListener(eventName: eventName, newEventListener: newListener)
}
// Create a new event listener, expecting information from the trigger
// @param eventName: Matching trigger eventNames will cause this listener to fire
// @param action: The function/lambda you want executed when the event triggers
func listenTo(eventName:String, action: @escaping ((Any?)->())) {
let newListener = EventListenerAction(callback: action)
addListener(eventName: eventName, newEventListener: newListener)
}
internal func addListener(eventName:String, newEventListener:EventListenerAction) {
if let listenerArray = self.listeners[eventName] {
listenerArray.add(newEventListener)
} else {
self.listeners[eventName] = [newEventListener] as NSMutableArray
}
}
// Removes all listeners by default, or specific listeners through paramters
// @param eventName: If an event name is passed, only listeners for that event will be removed
func removeListeners(eventNameToRemoveOrNil:String?) {
if let eventNameToRemove = eventNameToRemoveOrNil {
if let actionArray = self.listeners[eventNameToRemove] {
actionArray.removeAllObjects()
}
} else {
self.listeners.removeAll(keepingCapacity: false)
}
}
// Triggers an event
// @param eventName: Matching listener eventNames will fire when this is called
// @param information: pass values to your listeners
func trigger(eventName:String, information:Any? = nil) {
print("Event: ", eventName, " will trigger ", self.listeners[eventName]?.count ?? 0, " listeners")
if let actionObjects = self.listeners[eventName] {
for actionObject in actionObjects {
if let actionToPerform = actionObject as? EventListenerAction {
if let methodToCall = actionToPerform.actionExpectsInfo {
methodToCall(information)
}
else if let methodToCall = actionToPerform.action {
methodToCall()
}
}
}
}
}
}
// Class to hold actions to live in NSMutableArray
class EventListenerAction {
let action:(() -> ())?
let actionExpectsInfo:((Any?) -> ())?
init(callback: @escaping (() -> ()) ) {
self.action = callback
self.actionExpectsInfo = nil
}
init(callback: @escaping ((Any?) -> ()) ) {
self.actionExpectsInfo = callback
self.action = nil
}
}

View File

@ -1,111 +0,0 @@
//
// FolderContentMonitor.swift
// I2PLauncher
//
// Created by Mikal Villa on 28/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
/*
infix operator ~>
private let queue = DispatchQueue(label: "background")
func ~> <R> (
backgroundClosure: @escaping () -> R,
mainClosure: @escaping (_ result: R) -> ())
{
queue.async {
let result = backgroundClosure()
DispatchQueue.main.async(execute: {
mainClosure(result)
})
}
}
*/
public struct Event: CustomStringConvertible {
public let eventId: FSEventStreamEventId
public let eventPath: String
public let eventFlags: FSEventStreamEventFlags
public var description: String {
return "\(eventId) - \(eventFlags) - \(eventPath)"
}
}
public class FolderContentMonitor {
let callback: (Event) -> Void
public init(pathsToWatch: [String], sinceWhen: FSEventStreamEventId = FSEventStreamEventId(kFSEventStreamEventIdSinceNow), callback: @escaping (Event) -> Void) {
self.lastEventId = sinceWhen
self.pathsToWatch = pathsToWatch
self.callback = callback
}
deinit {
stop()
}
// MARK: - Private Properties
private let eventCallback: FSEventStreamCallback = {
(stream: ConstFSEventStreamRef,
contextInfo: UnsafeMutableRawPointer?,
numEvents: Int,
eventPaths: UnsafeMutableRawPointer,
eventFlags: UnsafePointer<FSEventStreamEventFlags>,
eventIds: UnsafePointer<FSEventStreamEventId>) in
let fileSystemWatcher: FolderContentMonitor = unsafeBitCast(contextInfo, to: FolderContentMonitor.self)
guard let paths = unsafeBitCast(eventPaths, to: NSArray.self) as? [String] else { return }
for index in 0..<numEvents {
fileSystemWatcher.processEvent(eventId: eventIds[index], eventPath: paths[index], eventFlags: eventFlags[index])
}
fileSystemWatcher.lastEventId = eventIds[numEvents - 1]
}
private let pathsToWatch: [String]
private var started = false
private var streamRef: FSEventStreamRef!
private func processEvent(eventId: FSEventStreamEventId, eventPath: String, eventFlags: FSEventStreamEventFlags) {
let event = Event(eventId: eventId, eventPath: eventPath, eventFlags: eventFlags)
callback(event)
}
public private(set) var lastEventId: FSEventStreamEventId
public func start() {
guard started == false else { return }
var context = FSEventStreamContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
context.info = Unmanaged.passUnretained(self).toOpaque()
let flags = UInt32(kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagFileEvents)
streamRef = FSEventStreamCreate(kCFAllocatorDefault, eventCallback, &context, pathsToWatch as CFArray, lastEventId, 0, flags)
FSEventStreamScheduleWithRunLoop(streamRef, CFRunLoopGetMain(), CFRunLoopMode.defaultMode.rawValue)
FSEventStreamStart(streamRef)
started = true
}
public func stop() {
guard started == true else { return }
FSEventStreamStop(streamRef)
FSEventStreamInvalidate(streamRef)
FSEventStreamRelease(streamRef)
streamRef = nil
started = false
}
}

View File

@ -1,317 +0,0 @@
//
// Preferences.swift
// I2PLauncher
//
// Created by Mikal Villa on 01/12/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
class PreferenceRow {
var name: String?
var defaultValue: Any?
var selectedValue: Any?
init(_ name: String, _ value: Any?, _ defaultVal: Any? = "") {
self.name = name
self.selectedValue = value
self.defaultValue = defaultVal
}
func asRawValue() -> Any {
return self.selectedValue ?? self.defaultValue!
}
}
class Preferences : NSObject {
enum ShowAsMode {
case bothIcon
case menubarIcon
case dockIcon
}
private var prefObject: Dictionary<String,Any> = Dictionary<String,Any>()
private var prefDict = Dictionary<String,PreferenceRow>()
private var prefDefaultDict: Dictionary<String,Any> = Dictionary<String,Any>()
var notifyOnStatusChange: Bool {
get { return UserDefaults.standard.bool(forKey: "notifyOnStatusChange") }
set { UserDefaults.standard.set(newValue, forKey: "notifyOnStatusChange") }
}
var count: Int = 0
// Interface with a string setting in background
var showAsIconMode: ShowAsMode {
get {
var mode = self["I2Pref_showAsIconMode"]
if (mode == nil) {
mode = "bothIcon"
}
switch (mode as! String) {
case "bothIcon":
return ShowAsMode.bothIcon
case "dockIcon":
return ShowAsMode.dockIcon
case "menubarIcon":
return ShowAsMode.menubarIcon
default:
return ShowAsMode.bothIcon
}
}
set(newVal) {
//
var newMode: String = "bothIcon"
switch newVal {
case .bothIcon:
newMode = "bothIcon"
case .menubarIcon:
newMode = "menubarIcon"
case .dockIcon:
newMode = "dockIcon"
}
self["I2Pref_showAsIconMode"] = newMode
self.syncPref()
}
}
// Lookup by name
subscript(prefName:String) -> Any? {
get {
let ret = prefObject[prefName]
if (ret != nil) {
return ret
}
return prefDefaultDict[prefName]
}
set(newValue) {
prefObject[prefName] = newValue
prefDict[prefName] = PreferenceRow(prefName, newValue, prefDefaultDict[prefName])
UserDefaults.standard.set(newValue, forKey: prefName)
self.syncPref()
}
}
func syncPref() {
UserDefaults.standard.setPersistentDomain(self.prefObject, forName: Identifiers.applicationDomainId)
UserDefaults.standard.synchronize()
}
// Lookup by index
subscript(index:Int) -> PreferenceRow? {
get {
return prefDict[Array(prefDict.keys)[index]]
}
set(newValue) {
let pKey = Array(prefDefaultDict.keys)[index]
prefDict[pKey] = newValue!
prefObject[pKey] = newValue!.asRawValue()
}
}
private static var sharedPreferences: Preferences = {
let preferences = Preferences()
// Setup defaults
var home = NSHomeDirectory()
// Add default values
var defaults = Dictionary<String,Any>()
defaults["I2Pref_enableLogging"] = true
defaults["I2Pref_enableVerboseLogging"] = true
defaults["I2Pref_autoStartRouterAtBoot"] = false
defaults["I2Pref_startLauncherAtLogin"] = false
defaults["I2Pref_startRouterAtStartup"] = true
defaults["I2Pref_stopRouterAtShutdown"] = true
defaults["I2Pref_letRouterLiveEvenLauncherDied"] = false
defaults["I2Pref_allowAdvancedPreferences"] = false
defaults["I2Pref_alsoStartFirefoxOnLaunch"] = true
defaults["I2Pref_useServiceManagementAsStartupTool"] = false
defaults["I2Pref_firefoxProfilePath"] = NSString(format: "%@/Library/Application Support/i2p/profile", home)
defaults["I2Pref_consolePortCheckNum"] = 7657
defaults["I2Pref_i2pBaseDirectory"] = NSString(format: "%@/Library/I2P", home)
defaults["I2Pref_i2pLogDirectory"] = NSString(format: "%@/Library/Logs/I2P", home)
defaults["I2Pref_showAsIconMode"] = "bothIcon"
defaults["I2Pref_javaCommandPath"] = "/usr/libexec/java_home -v 1.7+ --exec java "
defaults["I2Pref_javaCommandOptions"] = "-Xmx512M -Xms128m"
defaults["I2Pref_featureToggleExperimental"] = false
preferences.prefDefaultDict = defaults
/*if (preferences.prefDict.isEmpty) {
print("Stored new user defaults")
preferences.addDictToPrefTable(defaults)
}*/
for name in Array(preferences.prefDefaultDict.keys) {
let potentialValue = UserDefaults.standard.object(forKey: name)
//preferences.prefDict[name] = PreferenceRow(name, potentialValue, preferences.prefDefaultDict[name])
preferences[name] = potentialValue
}
preferences.count = preferences.prefDict.keys.count
UserDefaults.standard.register(defaults: defaults)
UserDefaults.standard.setPersistentDomain(preferences.prefObject, forName: Identifiers.applicationDomainId)
UserDefaults.standard.synchronize()
print("User Preferences loaded - Got \(preferences.count) items.")
return preferences
}()
// MARK: -
func addDictToPrefTable(_ dict: Dictionary<String,Any>, _ emptyFirst: Bool = true) {
if (emptyFirst) {
self.prefDict.removeAll()
}
for (pKey, pVal) in dict {
if (pKey.starts(with: "I2P")) {
print("Preference -> \(pKey)")
self[pKey] = pVal
} else {
print("Skipping preference -> \(pKey)")
}
}
}
// Initialization
private override init() {
super.init()
let fromDisk = UserDefaults.standard.persistentDomain(forName: Identifiers.applicationDomainId) ?? Dictionary<String,Any>()
for (pKey, pVal) in fromDisk {
if (pKey.starts(with: "I2P")) {
print("Preference -> \(pKey)")
self[pKey] = pVal
} else {
print("Skipping preference -> \(pKey)")
}
}
print("Preferences size from disk is: \(prefObject.count).")
self.syncPref()
}
// TODO: Make menubar icon optional
func getMenubarIconStateIsShowing() -> Bool {
return true
}
// MARK: - Accessors
class func shared() -> Preferences {
return sharedPreferences
}
func redrawPrefTableItems() {
self.addDictToPrefTable(self.prefObject, false)
}
// MARK: - Accessors for Application Preferences
var startRouterOnLauncherStart: Bool {
get {
let dfl = self.prefDefaultDict["I2Pref_startRouterAtStartup"] as! Bool
return (self["I2Pref_startRouterAtStartup"] as? Bool ?? dfl)
}
set(newValue) {
self["I2Pref_startRouterAtStartup"] = newValue
self.syncPref()
}
}
var stopRouterOnLauncherShutdown: Bool {
get {
let dfl = self.prefDefaultDict["I2Pref_stopRouterAtShutdown"] as! Bool
return (self["I2Pref_stopRouterAtShutdown"] as? Bool ?? dfl)
}
set(newValue) {
self["I2Pref_stopRouterAtShutdown"] = newValue
self.syncPref()
}
}
var allowAdvancedPreferenceEdit: Bool {
get {
let dfl = self.prefDefaultDict["I2Pref_allowAdvancedPreferences"] as! Bool
return (self["I2Pref_allowAdvancedPreferences"] as? Bool ?? dfl)
}
set(newValue) {
self["I2Pref_allowAdvancedPreferences"] = newValue
self.syncPref()
}
}
var alsoStartFirefoxOnLaunch: Bool {
get {
let dfl = self.prefDefaultDict["I2Pref_alsoStartFirefoxOnLaunch"] as! Bool
return (self["I2Pref_alsoStartFirefoxOnLaunch"] as? Bool ?? dfl)
}
set(newValue) {
self["I2Pref_alsoStartFirefoxOnLaunch"] = newValue
self.syncPref()
}
}
var featureToggleExperimental: Bool {
get {
let dfl = self.prefDefaultDict["I2Pref_featureToggleExperimental"] as! Bool
return (self["I2Pref_featureToggleExperimental"] as? Bool ?? dfl)
}
set(newValue) {
self["I2Pref_featureToggleExperimental"] = newValue
self.syncPref()
}
}
var i2pBaseDirectory: String {
get {
let dfl = self.prefDefaultDict["I2Pref_i2pBaseDirectory"] as! String
return (self["I2Pref_i2pBaseDirectory"] as? String ?? dfl)
}
set(newValue) {
// TODO: Check if string is a valid directory path, and that it exists.
self["I2Pref_i2pBaseDirectory"] = newValue
self.syncPref()
}
}
var i2pLogDirectory: String {
get {
let dfl = self.prefDefaultDict["I2Pref_i2pLogDirectory"] as! String
return (self["I2Pref_i2pLogDirectory"] as? String ?? dfl)
}
set(newValue) {
// TODO: Check if string is a valid java command path, check if it executes with -version.
self["I2Pref_i2pLogDirectory"] = newValue
self.syncPref()
}
}
var javaCommandPath: String {
get {
let dfl = self.prefDefaultDict["I2Pref_javaCommandPath"] as! String
return (self["I2Pref_javaCommandPath"] as? String ?? dfl)
}
set(newValue) {
// TODO: Check if string is a valid java command path, check if it executes with -version.
self["I2Pref_javaCommandPath"] = newValue
self.syncPref()
}
}
var javaCommandOptions: String {
get {
let dfl = self.prefDefaultDict["I2Pref_javaCommandOptions"] as! String
return (self["I2Pref_javaCommandOptions"] as? String ?? dfl)
}
set(newValue) {
// TODO: Check if string is a valid set of java options
self["I2Pref_javaCommandOptions"] = newValue
self.syncPref()
}
}
}

View File

@ -1,57 +0,0 @@
//
// ReflectionFunctions.swift
// I2PLauncher
//
// Created by Mikal Villa on 17/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
class ReflectionFunctions {
/// Given pointer to first element of a C array, invoke a function for each element
func enumerateCArray<T>(array: UnsafePointer<T>, count: UInt32, f: (UInt32, T) -> ()) {
var ptr = array
for i in 0..<count {
f(i, ptr.pointee)
ptr = ptr.successor()
}
}
/// Return name for a method
func methodName(m: Method) -> String? {
let sel = method_getName(m)
let nameCString = sel_getName(sel)
return String(cString: nameCString)
}
/// Print the names for each method in a class
func printMethodNamesForClass(cls: AnyClass) {
var methodCount: UInt32 = 0
let methodList = class_copyMethodList(cls, &methodCount)
if methodList != nil && methodCount > 0 {
enumerateCArray(array: methodList!, count: methodCount) { i, m in
let name = methodName(m: m) ?? "unknown"
print("#\(i): \(name)")
}
free(methodList)
}
}
/// Print the names for each method in a class with a specified name
func printMethodNamesForClassNamed(classname: String) {
// NSClassFromString() is declared to return AnyClass!, but should be AnyClass?
let maybeClass: AnyClass? = NSClassFromString(classname)
if let cls: AnyClass = maybeClass {
printMethodNamesForClass(cls: cls)
}
else {
print("\(classname): no such class")
}
}
}

View File

@ -1,119 +0,0 @@
//
// Startup.swift
// I2PLauncher
//
// Created by Mikal Villa on 22/12/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
class Startup : NSObject {
let loginItemsList : LSSharedFileList = LSSharedFileListCreate(nil, kLSSharedFileListSessionLoginItems.takeRetainedValue(), nil).takeRetainedValue();
func addLoginItem(_ path: CFURL) -> Bool {
if(getLoginItem(path) != nil) {
print("Login Item has already been added to the list.");
return true;
}
let path : CFURL = CFURLCreateWithString(nil, Bundle.main.bundleURL.absoluteString as CFString, nil);
print("Path adding to Login Item list is: ", path);
// add new Login Item at the end of Login Items list
if let loginItem = LSSharedFileListInsertItemURL(loginItemsList,
getLastLoginItemInList(),
nil, nil,
path,
nil, nil) {
print("Added login item is: ", loginItem);
return true;
}
return false;
}
func removeLoginItem(_ path: CFURL) -> Bool {
// remove Login Item from the Login Items list
if let oldLoginItem = getLoginItem(path) {
print("Old login item is: ", oldLoginItem);
if(LSSharedFileListItemRemove(loginItemsList, oldLoginItem) == noErr) {
return true;
}
return false;
}
print("Login Item for given path not found in the list.");
return true;
}
func getLoginItem(_ path : CFURL) -> LSSharedFileListItem! {
let path : CFURL = CFURLCreateWithString(nil, Bundle.main.bundleURL.absoluteString as CFString, nil);
// Copy all login items in the list
let loginItems : NSArray = LSSharedFileListCopySnapshot(loginItemsList, nil).takeRetainedValue();
var foundLoginItem : LSSharedFileListItem?;
var nextItemUrl : Unmanaged<CFURL>?;
// Iterate through login items to find one for given path
print("App URL: ", path);
for var i in (0..<loginItems.count) // CFArrayGetCount(loginItems)
{
let nextLoginItem : LSSharedFileListItem = loginItems.object(at: i) as! LSSharedFileListItem; // CFArrayGetValueAtIndex(loginItems, i).;
if(LSSharedFileListItemResolve(nextLoginItem, 0, &nextItemUrl, nil) == noErr) {
print("Next login item URL: ", nextItemUrl!.takeUnretainedValue());
// compare searched item URL passed in argument with next item URL
if(nextItemUrl!.takeRetainedValue() == path) {
foundLoginItem = nextLoginItem;
}
}
}
return foundLoginItem;
}
func getLastLoginItemInList() -> LSSharedFileListItem! {
// Copy all login items in the list
let loginItems : NSArray = LSSharedFileListCopySnapshot(loginItemsList, nil).takeRetainedValue() as NSArray;
if(loginItems.count > 0) {
let lastLoginItem = loginItems.lastObject as! LSSharedFileListItem;
print("Last login item is: ", lastLoginItem);
return lastLoginItem
}
return kLSSharedFileListItemBeforeFirst.takeRetainedValue();
}
func isLoginItemInList(_ path : CFURL) -> Bool {
if(getLoginItem(path) != nil) {
return true;
}
return false;
}
static func appPath() -> CFURL {
return NSURL.fileURL(withPath: Bundle.main.bundlePath) as CFURL;
}
}

View File

@ -1,106 +0,0 @@
//
// StringExtensions.swift
// I2PLauncher
//
// Created by Mikal Villa on 17/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
extension String {
func replace(target: String, withString: String) -> String
{
return self.replacingOccurrences(of: target, with: withString, options: NSString.CompareOptions.literal, range: nil)
}
/// Returns an array of string obtained splitting self at each newline ("\n").
/// If the last character is a newline, it will be ignored (no empty string
/// will be appended at the end of the array)
public func splitByNewline() -> [String] {
return self.split { $0 == "\n" }.map(String.init)
}
/// Returns an array of string obtained splitting self at each space, newline or TAB character
public func splitByWhitespace() -> [String] {
let whitespaces = Set<Character>([" ", "\n", "\t"])
return self.split { whitespaces.contains($0) }.map(String.init)
}
public func splitByColon() -> [String] {
return self.split { $0 == ":" }.map(String.init)
}
func leftPadding(toLength: Int, withPad character: Character) -> String {
let stringLength = self.count
if stringLength < toLength {
return String(repeatElement(character, count: toLength - stringLength)) + self
} else {
return String(self.suffix(toLength))
}
}
}
extension String {
subscript (i: Int) -> Character {
return self[index(startIndex, offsetBy: i)]
}
subscript (bounds: CountableRange<Int>) -> Substring {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(startIndex, offsetBy: bounds.upperBound)
return self[start ..< end]
}
subscript (bounds: CountableClosedRange<Int>) -> Substring {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(startIndex, offsetBy: bounds.upperBound)
return self[start ... end]
}
subscript (bounds: CountablePartialRangeFrom<Int>) -> Substring {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(endIndex, offsetBy: -1)
return self[start ... end]
}
subscript (bounds: PartialRangeThrough<Int>) -> Substring {
let end = index(startIndex, offsetBy: bounds.upperBound)
return self[startIndex ... end]
}
subscript (bounds: PartialRangeUpTo<Int>) -> Substring {
let end = index(startIndex, offsetBy: bounds.upperBound)
return self[startIndex ..< end]
}
}
/*
* This is functions for comparing version numbers.
* Example usage:
* "3.0.0" >= "3.0.0.1" // false
* "3.0.0" > "3.0.0.1" // false
* "3.0.0" <= "3.0.0.1" // true
* "3.0.0.1" >= "3.0.0.1" // true
*/
extension String {
static func ==(lhs: String, rhs: String) -> Bool {
return lhs.compare(rhs, options: .numeric) == .orderedSame
}
static func <(lhs: String, rhs: String) -> Bool {
return lhs.compare(rhs, options: .numeric) == .orderedAscending
}
static func <=(lhs: String, rhs: String) -> Bool {
return lhs.compare(rhs, options: .numeric) == .orderedAscending || lhs.compare(rhs, options: .numeric) == .orderedSame
}
static func >(lhs: String, rhs: String) -> Bool {
return lhs.compare(rhs, options: .numeric) == .orderedDescending
}
static func >=(lhs: String, rhs: String) -> Bool {
return lhs.compare(rhs, options: .numeric) == .orderedDescending || lhs.compare(rhs, options: .numeric) == .orderedSame
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -1,8 +0,0 @@
<?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>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:I2PLauncher.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,8 +0,0 @@
<?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>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -1,5 +0,0 @@
<?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/>
</plist>

View File

@ -1,25 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "128x128-Itoopie Transparent.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "128x128-Itoopie Transparent@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@ -1,24 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "16x16-Itoopie Transparent@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@ -1,25 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "256x256-Itoopie Transparent.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "256x256-Itoopie Transparent@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -1,22 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "32x32-Itoopie Transparent.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "32x32-Itoopie Transparent@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -1,25 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "512x512-Itoopie Transparent.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "512x512-Itoopie Transparent@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

View File

@ -1,66 +0,0 @@
{
"images" : [
{
"idiom" : "mac",
"size" : "16x16",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "16x16",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "32x32-Itoopie Transparent.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "32x32-Itoopie Transparent@2x.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "128x128-Itoopie Transparent.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "128x128-Itoopie Transparent@2x.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "256x256-Itoopie Transparent.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "256x256-Itoopie Transparent@2x.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "512x512-Itoopie Transparent.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "512x512-Itoopie Transparent@2x.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -1,6 +0,0 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -1,22 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ItoopieTransparent.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "32x32-Itoopie Transparent@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,89 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu id="TTS-U7-WkT" userLabel="statusBarContextMenu">
<items>
<menuItem title="Open Console" id="cOe-UL-1TW">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="openConsoleClicked:" target="-1" id="uR3-lb-ikG"/>
</connections>
</menuItem>
<menuItem title="Start I2P Router" id="RQ8-Q2-68A">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startRouterClicked:" target="-1" id="Vl3-cC-77e"/>
</connections>
</menuItem>
<menuItem title="Exit" id="hsL-CH-m3C">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="quickClicked:" target="-1" id="hiW-5d-nBX"/>
</connections>
</menuItem>
</items>
<point key="canvasLocation" x="17" y="167"/>
</menu>
<menuItem title="Application" allowsKeyEquivalentWhenHidden="YES" id="84R-pF-Wt1">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Application" id="Trv-j7-WYu">
<items>
<menuItem title="About Application" id="XDp-94-iig">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-1" id="gJe-90-jzd"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="xT4-H9-l6g"/>
<menuItem title="Preferences…" keyEquivalent="," id="2Kn-gO-qBP">
<connections>
<action selector="handleNativePrefMenuClicked:" target="-1" id="LFK-TI-TdY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="45f-s8-MiT"/>
<menuItem title="Services" id="x2v-WG-Nuy">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Services" systemMenu="services" id="DkL-DH-gJf"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="T1H-h7-Sat"/>
<menuItem title="Hide Application" keyEquivalent="h" id="uAA-NV-src">
<connections>
<action selector="hide:" target="-1" id="wFN-Nz-FpI"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="ext-76-4lm">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="KyT-0x-vod"/>
</connections>
</menuItem>
<menuItem title="Show All" id="HmI-6K-eUt">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="-1" id="mXg-9c-azx"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="xOX-eV-fcA"/>
<menuItem title="Quit Application" keyEquivalent="q" id="Fap-30-HH0">
<connections>
<action selector="terminate:" target="-1" id="IM7-XC-JlF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</objects>
</document>

View File

@ -1,9 +0,0 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "ObjectiveC/SBridge.h"
#import "AppleStuffExceptionHandler.h"
#import "ObjectiveC/AppDelegate.h"
#import "ObjectiveC/RouterTask.h"
#import "ObjectiveC/logger_c.h"
#import <Webkit/Webkit.h>

View File

@ -1,14 +0,0 @@
<?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>com.apple.security.application-groups</key>
<array>
<string>$(TeamIdentifierPrefix).i2p</string>
</array>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
</dict>
</plist>

View File

@ -1,8 +0,0 @@
<?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>_XCCurrentVersionName</key>
<string>I2PLauncher.xcdatamodel</string>
</dict>
</plist>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1" systemVersion="11A491" minimumToolsVersion="Automatic" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
<elements/>
</model>

View File

@ -1,15 +0,0 @@
//
// Identifiers.swift
// I2PLauncher
//
// Created by Mikal Villa on 24/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Foundation
class Identifiers {
static let applicationDomainId = "net.i2p.bootstrap-macosx"
static let mainApplicationBundleId = "net.i2p.bootstrap-macosx.I2PLauncher"
static let launcherApplicationBundleId = "net.i2p.bootstrap-macosx.StartupItemApp"
}

View File

@ -1,48 +0,0 @@
<?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>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>I2PRouterVersion</key>
<string>0.9.45</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>LSUIElement</key>
<true/>
<key>NSAppleScriptEnabled</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018-2019 The I2P Project. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>UserInterfaces</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSServices</key>
<array>
<dict/>
</array>
<key>SUFeedURL</key>
<string>https://download.i2p2.de/macosx/sparkle/updates/v1/appcast.xml</string>
<key>SUPublicEDKey</key>
<string>mYg7wXploT0EGhSROqE8QX61T/6Igv622oZOy5jMCoE=</string>
</dict>
</plist>

View File

@ -1,112 +0,0 @@
#ifndef __APPDELEGATE_H__
#define __APPDELEGATE_H__
#include <string.h>
#include <memory.h>
#ifdef __cplusplus
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <assert.h>
#endif
#include <Cocoa/Cocoa.h>
#include "SBridge.h"
#include "RouterTask.h"
// TODO: Configure the project to avoid such includes.
#include "../version.h"
@class SwiftApplicationDelegate;
@class I2PDeployer;
@protocol SwiftMainDelegateProto
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
@end
@class ExtractMetaInfo;
@interface ExtractMetaInfo : NSObject
@property (copy) NSString* i2pBase;
@property (copy) NSString* javaBinary;
@property (copy) NSString* zipFile;
@property (copy) NSString* jarFile;
@end
#ifdef __cplusplus
inline const char* RealHomeDirectory() {
struct passwd *pw = getpwuid(getuid());
assert(pw);
return pw->pw_dir;
}
inline std::string getDefaultBaseDir()
{
// Figure out base directory
auto homeDir = RealHomeDirectory();
const char* pathFromHome = "%s/Library/I2P";
char buffer[strlen(homeDir)+strlen(pathFromHome)];
sprintf(buffer, pathFromHome, homeDir);
std::string i2pBaseDir(buffer);
return i2pBaseDir;
}
inline std::string getDefaultLogDir()
{
// Figure out log directory
auto homeDir = RealHomeDirectory();
const char* pathFromHome = "%s/Library/Logs/I2P";
char buffer[strlen(homeDir)+strlen(pathFromHome)];
sprintf(buffer, pathFromHome, homeDir);
std::string i2pBaseDir(buffer);
return i2pBaseDir;
}
inline void sendUserNotification(NSString* title, NSString* informativeText, bool makeSound = false) {
NSUserNotification *userNotification = [[NSUserNotification alloc] init];
userNotification.title = title;
userNotification.informativeText = informativeText;
NSBundle *launcherBundle = [NSBundle mainBundle];
auto resPath = [launcherBundle resourcePath];
auto stdResPath = std::string([resPath UTF8String]);
stdResPath += "/AppImage.png";
auto nsString = [[NSString alloc] initWithUTF8String:(const char*)stdResPath.c_str()];
NSImage *appImage = [[NSImage alloc] initWithContentsOfFile:nsString];
userNotification.contentImage = appImage;
if (makeSound) userNotification.soundName = NSUserNotificationDefaultSoundName;
[[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification:userNotification];
};
#endif
@interface AppDelegate : NSObject <NSUserNotificationCenterDelegate, NSApplicationDelegate>
@property BOOL enableLogging;
@property BOOL enableVerboseLogging;
@property (assign) SwiftApplicationDelegate *swiftRuntime;
@property (assign) NSUserDefaults *userPreferences;
@property (assign) ExtractMetaInfo *metaInfo;
@property (assign) I2PDeployer *deployer;
@property (copy) NSImage *contentImage NS_AVAILABLE(10_9, NA);
- (void) extractI2PBaseDir:(void(^)(BOOL success, NSError *error))completion;
- (void) awakeFromNib;
- (void) applicationDidFinishLaunching:(NSNotification *)aNotification;
- (void) applicationWillTerminate:(NSNotification *)aNotification;
- (AppDelegate *) initWithArgc:(int)argc argv:(const char **)argv;
- (BOOL) userNotificationCenter:(NSUserNotificationCenter *)center
shouldPresentNotification:(NSUserNotification *)notification;
@end
#endif

View File

@ -1,21 +0,0 @@
//
// Deployer.h
// I2PLauncher
//
// Created by Mikal Villa on 19/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <Foundation/NSError.h>
#import "AppDelegate.h"
@class ExtractMetaInfo;
@interface I2PDeployer : NSObject
@property (assign) ExtractMetaInfo *metaInfo;
- (I2PDeployer *) initWithMetaInfo:(ExtractMetaInfo*)mi;
- (void) extractI2PBaseDir:(void(^)(BOOL success, NSError *error))completion;
@end

View File

@ -1,122 +0,0 @@
//
// Deployer.m
// I2PLauncher
//
// Created by Mikal Villa on 19/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
#include <functional>
#include <memory>
#include <iostream>
#include <algorithm>
#include <string>
#include <list>
#include <sys/stat.h>
#include <stdlib.h>
#include <future>
#include <vector>
#import <Foundation/Foundation.h>
#import <Foundation/NSFileManager.h>
#include <CoreFoundation/CFPreferences.h>
#import <objc/Object.h>
#import <Cocoa/Cocoa.h>
#import <AppKit/AppKit.h>
#import <AppKit/NSApplication.h>
#import "I2PLauncher-Swift.h"
#include "AppDelegate.h"
// TODO: Configure the project to avoid such includes.
#include "../include/fn.h"
#include "../include/subprocess.hpp"
#include "../include/strutil.hpp"
#import "SBridge.h"
#include "logger_c.h"
#include <string>
#include "Logger.h"
#include "LoggerWorker.hpp"
#import "Deployer.h"
#include <string.h>
using namespace subprocess;
@implementation I2PDeployer
- (I2PDeployer *) initWithMetaInfo:(ExtractMetaInfo*)mi
{
self.metaInfo = mi;
return self;
}
- (void) extractI2PBaseDir:(void(^)(BOOL success, NSError *error))completion
{
#ifdef __cplusplus
NSBundle *launcherBundle = [NSBundle mainBundle];
auto homeDir = RealHomeDirectory();
NSLog(@"Home directory is %s", homeDir);
std::string basePath(homeDir);
basePath.append("/Library/I2P");
self.metaInfo.zipFile = [launcherBundle pathForResource:@"base" ofType:@"zip"];
NSParameterAssert(basePath.c_str());
NSError *error = NULL;
BOOL success = YES;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
try {
// Create directory
mkdir(basePath.c_str(), S_IRUSR | S_IWUSR | S_IXUSR);
auto cli = defaultFlagsForExtractorJob;
setenv("I2PBASE", basePath.c_str(), true);
//setenv("DYLD_LIBRARY_PATH",".:/usr/lib:/lib:/usr/local/lib", true);
chdir(basePath.c_str());
// Everything behind --exec java - would be passed as arguments to the java executable.
std::string execStr = "/usr/bin/unzip -uo "; //std::string([rs.getJavaHome UTF8String]);
execStr += [self.metaInfo.zipFile UTF8String];
//for_each(cli, [&execStr](std::string str){ execStr += std::string(" ") + str; });
NSLog(@"Trying cmd: %@", [NSString stringWithUTF8String:execStr.c_str()]);
sendUserNotification(APP_IDSTR, @"Please hold on while we extract I2P. You'll get a new message once done!");
int extractStatus = Popen(execStr.c_str(), environment{{
{"I2PBASE", basePath.c_str()}
}}).wait();
NSLog(@"Extraction exit code %@",[NSString stringWithUTF8String:(std::to_string(extractStatus)).c_str()]);
if (extractStatus == 0) {
NSLog(@"Extraction process done");
} else {
NSLog(@"Something went wrong");
}
// All done. Assume success and error are already set.
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion(YES, error);
}
});
} catch (OSError &err) {
auto errMsg = [NSString stringWithUTF8String:err.what()];
NSLog(@"Exception: %@", errMsg);
sendUserNotification(APP_IDSTR, [NSString stringWithFormat:@"Error: %@", errMsg]);
}
});
#endif
}
@end

View File

@ -1,263 +0,0 @@
//
// Logger.h
// I2PLauncher
//
// Created by Mikal Villa on 27/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
// Imported/Refactored from earlier C++ project of Meeh
//
#ifndef Logger_h
#define Logger_h
#ifdef __cplusplus
#include <string>
#include <sstream>
#include <iostream>
#include <cstdarg>
#include <chrono>
#include <functional>
#include <ctime>
/**
* For details please see this
* REFERENCE: http://www.cppreference.com/wiki/io/c/printf_format
* \verbatim
*
There are different %-codes for different variable types, as well as options to
limit the length of the variables and whatnot.
Code Format
%[flags][width][.precision][length]specifier
SPECIFIERS
----------
%c character
%d signed integers
%i signed integers
%e scientific notation, with a lowercase “e”
%E scientific notation, with a uppercase “E”
%f floating point
%g use %e or %f, whichever is shorter
%G use %E or %f, whichever is shorter
%o octal
%s a string of characters
%u unsigned integer
%x unsigned hexadecimal, with lowercase letters
%X unsigned hexadecimal, with uppercase letters
%p a pointer
%n the argument shall be a pointer to an integer into which is placed the number of characters written so far
*/
/*
Usage:
SharedLogWorker logger(argv[0], path_to_log_file);
MeehLog::initializeLogging(&logger);
MLOG(INFO) << "Test SLOG INFO";
MLOG(DEBUG) << "Test SLOG DEBUG";
*/
class SharedLogWorker;
#if !(defined(__PRETTY_FUNCTION__))
#define __PRETTY_FUNCTION__ __FUNCTION__
#endif
const int ML_ANNOYING = 0;
const int ML_DEBUG = 1;
const int ML_INFO = 2;
const int ML_WARN = 3;
const int ML_ERROR = 4;
const int ML_FATAL = 5;
static const std::string kFatalLogExpression = "";
#define MLOG_ANNOYING MeehLog::internal::LogMessage(__FILE__,__LINE__,__PRETTY_FUNCTION__,"ML_ANNOYING")
#define MLOG_DEBUG MeehLog::internal::LogMessage(__FILE__,__LINE__,__PRETTY_FUNCTION__,"ML_DEBUG")
#define MLOG_INFO MeehLog::internal::LogMessage(__FILE__,__LINE__,__PRETTY_FUNCTION__,"ML_INFO")
#define MLOG_WARN MeehLog::internal::LogMessage(__FILE__,__LINE__,__PRETTY_FUNCTION__,"ML_WARN")
#define MLOG_ERROR MeehLog::internal::LogMessage(__FILE__,__LINE__,__PRETTY_FUNCTION__,"ML_ERROR")
#define MLOG_FATAL MeehLog::internal::LogContractMessage(__FILE__,__LINE__,__PRETTY_FUNCTION__,k_fatal_log_expression)
// MLOG(level) is the API for the stream log
#define MLOG(level) MLOG_##level.messageStream()
// conditional stream log
#define LOG_IF(level, boolean_expression) \
if(true == boolean_expression) \
MLOG_##level.messageStream()
#define MASSERT(boolean_expression) \
if (false == (boolean_expression)) \
MeehLog::internal::LogContractMessage(__FILE__, __LINE__, __PRETTY_FUNCTION__, #boolean_expression).messageStream()
#define MLOGF_ANNOYING MeehLog::internal::LogMessage(__FILE__, __LINE__, __PRETTY_FUNCTION__,"ML_ANNOYING")
#define MLOGF_INFO MeehLog::internal::LogMessage(__FILE__, __LINE__, __PRETTY_FUNCTION__,"ML_INFO")
#define MLOGF_DEBUG MeehLog::internal::LogMessage(__FILE__, __LINE__, __PRETTY_FUNCTION__,"ML_DEBUG")
#define MLOGF_WARN MeehLog::internal::LogMessage(__FILE__, __LINE__, __PRETTY_FUNCTION__,"ML_WARN")
#define MLOGF_ERROR MeehLog::internal::LogMessage(__FILE__, __LINE__, __PRETTY_FUNCTION__,"ML_ERROR")
#define MLOGF_FATAL MeehLog::internal::LogContractMessage(__FILE__, __LINE__, __PRETTY_FUNCTION__,k_fatal_log_expression)
// MLOGF(level,msg,...) is the API for the "printf" like log
#define MLOGF(level, printf_like_message, ...) \
MLOGF_##level.messageSave(printf_like_message, ##__VA_ARGS__)
// conditional log printf syntax
#define MLOGF_IF(level,boolean_expression, printf_like_message, ...) \
if(true == boolean_expression) \
MLOG_##level.messageSave(printf_like_message, ##__VA_ARGS__)
// Design By Contract, printf-like API syntax with variadic input parameters. Throws std::runtime_eror if contract breaks */
#define MASSERTF(boolean_expression, printf_like_message, ...) \
if (false == (boolean_expression)) \
MeehLog::internal::LogContractMessage(__FILE__, __LINE__, __PRETTY_FUNCTION__,#boolean_expression).messageSave(printf_like_message, ##__VA_ARGS__)
namespace MeehLog {
// PUBLIC API:
/** Install signal handler that catches FATAL C-runtime or OS signals
SIGABRT ABORT (ANSI), abnormal termination
SIGFPE Floating point exception (ANSI): http://en.wikipedia.org/wiki/SIGFPE
SIGILL ILlegal instruction (ANSI)
SIGSEGV Segmentation violation i.e. illegal memory reference
SIGTERM TERMINATION (ANSI) */
void installSignalHandler();
namespace internal {
/** \return signal_name. Ref: signum.h and \ref installSignalHandler */
std::string signalName(int signal_number);
/** Re-"throw" a fatal signal, previously caught. This will exit the application
* This is an internal only function. Do not use it elsewhere. It is triggered
* from g2log, g2LogWorker after flushing messages to file */
void exitWithDefaultSignalHandler(int signal_number);
std::time_t systemtime_now();
bool isLoggingInitialized();
struct LogEntry {
LogEntry(std::string msg, std::time_t timestamp) : mMsg(msg), mTimestamp(timestamp) {}
LogEntry(const LogEntry& other): mMsg(other.mMsg), mTimestamp(other.mTimestamp) {}
LogEntry& operator=(const LogEntry& other) {
mMsg = other.mMsg;
mTimestamp = other.mTimestamp;
return *this;
}
std::string mMsg;
std::time_t mTimestamp;
};
/** Trigger for flushing the message queue and exiting the application
A thread that causes a FatalMessage will sleep forever until the
application has exited (after message flush) */
struct FatalMessage {
enum FatalType {kReasonFatal, kReasonOS_FATAL_SIGNAL};
FatalMessage(LogEntry message, FatalType type, int signal_id);
~FatalMessage() {};
FatalMessage& operator=(const FatalMessage& fatal_message);
LogEntry mMessage;
FatalType mType;
int mSignalId;
};
// Will trigger a FatalMessage sending
struct FatalTrigger {
FatalTrigger(const FatalMessage& message);
~FatalTrigger();
FatalMessage mMessage;
};
// Log message for 'printf-like' or stream logging, it's a temporary message constructions
class LogMessage {
public:
LogMessage(const std::string& file, const int line, const std::string& function, const std::string& level);
virtual ~LogMessage(); // at destruction will flush the message
std::ostringstream& messageStream() {return mStream;}
// To generate warn on illegal printf format
#ifndef __GNUC__
#define __attribute__(x)
#endif
// C++ get 'this' as first arg
void messageSave(const char* printf_like_message, ...)
__attribute__((format(printf, 2, 3) ));
protected:
const std::string mFile;
const int mLine;
const std::string mFunction;
const std::string mLevel;
std::ostringstream mStream;
std::string mLogEntry;
std::time_t mTimestamp;
};
// 'Design-by-Contract' temporary messsage construction
class LogContractMessage : public LogMessage {
public:
LogContractMessage(const std::string& file, const int line,
const std::string& function, const std::string& boolean_expression);
virtual ~LogContractMessage(); // at destruction will flush the message
protected:
const std::string mExpression;
};
// wrap for std::chrono::system_clock::now()
std::time_t systemtime_now();
} // namespace internal
/** Should be called at very first startup of the software with \ref SharedLogWorker pointer.
* Ownership of the \ref SharedLogWorker is the responsibilkity of the caller */
void initializeLogging(SharedLogWorker* logger);
/** Shutdown the logging by making the pointer to the background logger to nullptr
* The \ref pointer to the SharedLogWorker is owned by the instantniater \ref initializeLogging
* and is not deleted.
*/
void shutDownLogging();
/** Same as the Shutdown above but called by the destructor of the LogWorker, thus ensuring that no further
* LOG(...) calls can happen to a non-existing LogWorker.
* @param active MUST BE the LogWorker initialized for logging. If it is not then this call is just ignored
* and the logging continues to be active.
* @return true if the correct worker was given,. and shutDownLogging was called
*/
bool shutDownLoggingForActiveOnly(SharedLogWorker* active);
typedef std::chrono::steady_clock::time_point steady_time_point;
typedef std::chrono::time_point<std::chrono::system_clock> system_time_point;
typedef std::chrono::milliseconds milliseconds;
typedef std::chrono::microseconds microseconds;
/** return time representing POD struct (ref ctime + wchar) that is normally
* retrieved with std::localtime. MeehLog::localtime is threadsafe which std::localtime is not.
* MeehLog::localtime is probably used together with @ref MeehLog::systemtime_now */
tm localtime(const std::time_t& time);
/** format string must conform to std::put_time's demands.
* WARNING: At time of writing there is only so-so compiler support for
* std::put_time. A possible fix if your c++11 library is not updated is to
* modify this to use std::strftime instead */
std::string localtime_formatted(const std::time_t& time_snapshot, const std::string& time_format) ;
} // namespace MeehLog
#endif // __cplusplus
#endif /* Logger_h */

View File

@ -1,370 +0,0 @@
//
// Logger.cpp
// I2PLauncher
//
// Created by Mikal Villa on 27/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
// Imported/Refactored from earlier C++ project of Meeh
//
#include <iostream>
#include <sstream>
#include <string>
#include <stdexcept> // exceptions
#include <cstdio> // vsnprintf
#include <cassert>
#include <mutex>
#include <signal.h>
#include <thread>
#include <unistd.h>
#include <execinfo.h>
#include <cxxabi.h>
#include <cstdlib>
#include <sys/ucontext.h>
#include "Logger.h"
#include "LoggerWorker.hpp"
namespace {
std::once_flag gIsInitialized;
std::once_flag gSetFirstUninitializedFlag;
std::once_flag gSaveFirstUnintializedFlag;
SharedLogWorker* gLoggerWorkerInstance = nullptr;
MeehLog::internal::LogEntry gFirstUnintializedMsg = {"", 0};
std::mutex gLoggerMasterMutex;
const std::string kTruncatedWarningText = "[...truncated...]";
std::string splitFileName(const std::string& str) {
size_t found;
found = str.find_last_of("\\");
return str.substr(found + 1);
}
void saveToLogger(const MeehLog::internal::LogEntry& logEntry) {
// Uninitialized messages are ignored but does not MASSERT/crash the logger
if (!MeehLog::internal::isLoggingInitialized()) {
std::string err("LOGGER NOT INITIALIZED: " + logEntry.mMsg);
std::call_once(gSetFirstUninitializedFlag,
[&] { gFirstUnintializedMsg.mMsg += err;
gFirstUnintializedMsg.mTimestamp = MeehLog::internal::systemtime_now();
});
// dump to std::err all the non-initialized logs
std::cerr << err << std::endl;
return;
}
// Save the first uninitialized message, if any
std::call_once(gSaveFirstUnintializedFlag, [] {
if (!gFirstUnintializedMsg.mMsg.empty()) {
gLoggerWorkerInstance->save(gFirstUnintializedMsg);
}
});
gLoggerWorkerInstance->save(logEntry);
}
void crashHandler(int signal_number, siginfo_t* info, void* unused_context) {
const size_t max_dump_size = 50;
void* dump[max_dump_size];
size_t size = backtrace(dump, max_dump_size);
char** messages = backtrace_symbols(dump, (int)size); // overwrite sigaction with caller's address
std::ostringstream oss;
oss << "Received fatal signal: " << MeehLog::internal::signalName(signal_number);
oss << "(" << signal_number << ")" << std::endl;
oss << "PID: " << getpid() << std::endl;
// dump stack: skip first frame, since that is here
for (size_t idx = 1; idx < size && messages != nullptr; ++idx) {
char* mangled_name = 0, *offset_begin = 0, *offset_end = 0;
// find parantheses and +address offset surrounding mangled name
for (char* p = messages[idx]; *p; ++p) {
if (*p == '(') {
mangled_name = p;
} else if (*p == '+') {
offset_begin = p;
} else if (*p == ')') {
offset_end = p;
break;
}
}
// if the line could be processed, attempt to demangle the symbol
if (mangled_name && offset_begin && offset_end &&
mangled_name < offset_begin) {
*mangled_name++ = '\0';
*offset_begin++ = '\0';
*offset_end++ = '\0';
int status;
char* real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);
// if demangling is successful, output the demangled function name
if (status == 0) {
oss << "stack dump [" << idx << "] " << messages[idx] << " : " << real_name << "+";
oss << offset_begin << offset_end << std::endl;
}
// otherwise, output the mangled function name
else {
oss << "stack dump [" << idx << "] " << messages[idx] << mangled_name << "+";
oss << offset_begin << offset_end << std::endl;
}
free(real_name); // mallocated by abi::__cxa_demangle(...)
} else {
// no demangling done -- just dump the whole line
oss << "stack dump [" << idx << "] " << messages[idx] << std::endl;
}
} // END: for(size_t idx = 1; idx < size && messages != nullptr; ++idx)
free(messages);
{
// Local scope, trigger send
using namespace MeehLog::internal;
std::ostringstream fatal_stream;
fatal_stream << "\n\n***** FATAL TRIGGER RECEIVED ******* " << std::endl;
fatal_stream << oss.str() << std::endl;
fatal_stream << "\n***** RETHROWING SIGNAL " << signalName(signal_number) << "(" << signal_number << ")" << std::endl;
LogEntry entry = {fatal_stream.str(), systemtime_now()};
FatalMessage fatal_message(entry, FatalMessage::kReasonOS_FATAL_SIGNAL, signal_number);
FatalTrigger trigger(fatal_message); std::ostringstream oss;
std::cerr << fatal_message.mMessage.mMsg << std::endl << std::flush;
} // message sent to SharedLogWorker
// wait to die -- will be inside the FatalTrigger
}
} // End anonymous namespace
namespace MeehLog {
// signalhandler and internal clock is only needed to install once
// for unit testing purposes the initializeLogging might be called
// several times... for all other practical use, it shouldn't!
void initializeLogging(SharedLogWorker* bgworker) {
std::call_once(gIsInitialized, []() {
//installSignalHandler();
});
std::lock_guard<std::mutex> lock(gLoggerMasterMutex);
MASSERT(!internal::isLoggingInitialized());
MASSERT(bgworker != nullptr);
gLoggerWorkerInstance = bgworker;
}
void shutDownLogging() {
std::lock_guard<std::mutex> lock(gLoggerMasterMutex);
gLoggerWorkerInstance = nullptr;
}
bool shutDownLoggingForActiveOnly(SharedLogWorker* active) {
if (internal::isLoggingInitialized() && nullptr != active &&
(dynamic_cast<void*>(active) != dynamic_cast<void*>(gLoggerWorkerInstance))) {
MLOG(WARN) << "\nShutting down logging, but the ID of the Logger is not the one that is active."
<< "\nHaving multiple instances of the MeehLog::LogWorker is likely a BUG"
<< "\nEither way, this call to shutDownLogging was ignored";
return false;
}
shutDownLogging();
return true;
}
void installSignalHandler() {
struct sigaction action;
memset(&action, 0, sizeof(action));
sigemptyset(&action.sa_mask);
action.sa_sigaction = &crashHandler; // callback to crashHandler for fatal signals
// sigaction to use sa_sigaction file. ref: http://www.linuxprogrammingblog.com/code-examples/sigaction
action.sa_flags = SA_SIGINFO;
// do it verbose style - install all signal actions
if (sigaction(SIGABRT, &action, NULL) < 0)
perror("sigaction - SIGABRT");
if (sigaction(SIGFPE, &action, NULL) < 0)
perror("sigaction - SIGFPE");
if (sigaction(SIGILL, &action, NULL) < 0)
perror("sigaction - SIGILL");
if (sigaction(SIGSEGV, &action, NULL) < 0)
perror("sigaction - SIGSEGV");
if (sigaction(SIGTERM, &action, NULL) < 0)
perror("sigaction - SIGTERM");
}
namespace internal {
std::time_t systemtime_now()
{
system_time_point system_now = std::chrono::system_clock::now();
return std::chrono::system_clock::to_time_t(system_now);
}
bool isLoggingInitialized() {
return gLoggerWorkerInstance != nullptr;
}
LogContractMessage::LogContractMessage(const std::string& file, const int line,
const std::string& function, const std::string& boolean_expression)
: LogMessage(file, line, function, "FATAL")
, mExpression(boolean_expression)
{}
LogContractMessage::~LogContractMessage() {
std::ostringstream oss;
if (0 == mExpression.compare(kFatalLogExpression)) {
oss << "\n[ *******\tEXIT trigger caused by MLOG(FATAL): \n";
} else {
oss << "\n[ *******\tEXIT trigger caused by broken Contract: MASSERT(" << mExpression << ")\n";
}
mLogEntry = oss.str();
}
LogMessage::LogMessage(const std::string& file, const int line, const std::string& function, const std::string& level)
: mFile(file)
, mLine(line)
, mFunction(function)
, mLevel(level)
, mTimestamp(systemtime_now())
{}
LogMessage::~LogMessage() {
using namespace internal;
std::ostringstream oss;
const bool fatal = (0 == mLevel.compare("FATAL"));
oss << mLevel << " [" << splitFileName(mFile);
if (fatal)
oss << " at function: " << mFunction ;
oss << " Line: " << mLine << "]";
const std::string str(mStream.str());
if (!str.empty()) {
oss << '"' << str << '"';
}
mLogEntry += oss.str();
if (fatal) { // os_fatal is handled by crashhandlers
{
// local scope - to trigger FatalMessage sending
FatalMessage::FatalType fatal_type(FatalMessage::kReasonFatal);
FatalMessage fatal_message({mLogEntry, mTimestamp}, fatal_type, SIGABRT);
FatalTrigger trigger(fatal_message);
std::cerr << mLogEntry << "\t******* ]" << std::endl << std::flush;
} // will send to worker
}
saveToLogger({mLogEntry, mTimestamp}); // message saved
}
// represents the actual fatal message
FatalMessage::FatalMessage(LogEntry message, FatalType type, int signal_id)
: mMessage(message)
, mType(type)
, mSignalId(signal_id) {}
FatalMessage& FatalMessage::operator=(const FatalMessage& other) {
mMessage = other.mMessage;
mType = other.mType;
mSignalId = other.mSignalId;
return *this;
}
std::string signalName(int signal_number) {
switch (signal_number) {
case SIGABRT: return "SIGABRT"; break;
case SIGFPE: return "SIGFPE"; break;
case SIGSEGV: return "SIGSEGV"; break;
case SIGILL: return "SIGILL"; break;
case SIGTERM: return "SIGTERM"; break;
default:
std::ostringstream oss;
oss << "UNKNOWN SIGNAL(" << signal_number << ")";
return oss.str();
}
}
void exitWithDefaultSignalHandler(int signal_number) {
std::cerr << "Exiting - FATAL SIGNAL: " << signal_number << " " << std::flush;
struct sigaction action;
memset(&action, 0, sizeof(action)); //
sigemptyset(&action.sa_mask);
action.sa_handler = SIG_DFL; // take default action for the signal
sigaction(signal_number, &action, NULL);
kill(getpid(), signal_number);
abort(); // should never reach this
}
/** Fatal call saved to logger. This will trigger SIGABRT or other fatal signal
* to exit the program. After saving the fatal message the calling thread
* will sleep forever (i.e. until the background thread catches up, saves the fatal
* message and kills the software with the fatal signal.
*/
void fatalCallToLogger(FatalMessage message) {
if (!isLoggingInitialized()) {
std::ostringstream error;
error << "FATAL CALL but logger is NOT initialized\n"
<< "SIGNAL: " << MeehLog::internal::signalName(message.mSignalId)
<< "\nMessage: \n" << message.mMessage.mMsg << std::flush;
std::cerr << error.str();
//internal::exitWithDefaultSignalHandler(message.mSignalId);
}
gLoggerWorkerInstance->fatal(message);
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
std::function<void(FatalMessage) > gFatalToMLogWorkerFunctionPtr = fatalCallToLogger;
// used to RAII trigger fatal message sending to g2LogWorker
FatalTrigger::FatalTrigger(const FatalMessage& message)
: mMessage(message) {}
// at destruction, flushes fatal message to g2LogWorker
FatalTrigger::~FatalTrigger() {
// either we will stay here eternally, or it's in unit-test mode
gFatalToMLogWorkerFunctionPtr(mMessage);
}
// This mimics the original "std::put_time(const std::tm* tmb, const charT* fmt)"
// This is needed since latest version (at time of writing) of gcc4.7 does not implement this library function yet.
// return value is SIMPLIFIED to only return a std::string
std::string put_time(const struct tm* tmb, const char* c_time_format)
{
const size_t size = 1024;
char buffer[size]; // IMPORTANT: check now and then for when gcc will implement std::put_time finns.
// ... also ... This is way more buffer space then we need
auto success = std::strftime(buffer, size, c_time_format, tmb);
if (0 == success)
return c_time_format; // For this hack it is OK but in case of more permanent we really should throw here, or even assert
return buffer;
}
} // ns internal
tm localtime(const std::time_t& time)
{
struct tm tm_snapshot;
localtime_r(&time, &tm_snapshot); // POSIX
return tm_snapshot;
}
/// returns a std::string with content of time_t as localtime formatted by input format string
/// * format string must conform to std::put_time
/// This is similar to std::put_time(std::localtime(std::time_t*), time_format.c_str());
std::string localtime_formatted(const std::time_t& time_snapshot, const std::string& time_format)
{
std::tm t = localtime(time_snapshot); // could be const, but cannot due to VS2012 is non conformant for C++11's std::put_time (see above)
std::stringstream buffer;
buffer << MeehLog::internal::put_time(&t, time_format.c_str()); // format example: //"%Y/%m/%d %H:%M:%S");
return buffer.str();
}
} // ns MeehLog

View File

@ -1,320 +0,0 @@
//
// LoggerWorker.cpp
// I2PLauncher
//
// Created by Mikal Villa on 27/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
// Imported/Refactored from earlier C++ project of Meeh
//
#include "LoggerWorker.hpp"
#include "Logger.h"
#include <fstream>
#include <sstream>
#include <cassert>
#include <algorithm>
#include <string>
#include <chrono>
#include <functional>
#include <future>
using namespace MeehLog;
using namespace MeehLog::internal;
Active::Active(): mDone(false){}
Active::~Active() {
Callback quit_token = std::bind(&Active::doDone, this);
send(quit_token); // tell thread to exit
mThd.join();
}
// Add asynchronously a work-message to queue
void Active::send(Callback msg_){
mMq.push(msg_);
}
// Will wait for msgs if queue is empty
// A great explanation of how this is done (using Qt's library):
// http://doc.qt.nokia.com/stable/qwaitcondition.html
void Active::run() {
while (!mDone) {
// wait till job is available, then retrieve it and
// executes the retrieved job in this thread (background)
Callback func;
mMq.wait_and_pop(func);
func();
}
}
// Factory: safe construction of object before thread start
std::unique_ptr<Active> Active::createActive(){
std::unique_ptr<Active> aPtr(new Active());
aPtr->mThd = std::thread(&Active::run, aPtr.get());
return aPtr;
}
namespace {
static const std::string date_formatted = "%Y/%m/%d";
static const std::string time_formatted = "%H:%M:%S";
static const std::string file_name_time_formatted = "%Y%m%d-%H%M%S";
// check for filename validity - filename should not be part of PATH
bool isValidFilename(const std::string prefix_filename) {
std::string illegal_characters("/,|<>:#$%{}()[]\'\"^!?+* ");
size_t pos = prefix_filename.find_first_of(illegal_characters, 0);
if (pos != std::string::npos) {
std::cerr << "Illegal character [" << prefix_filename.at(pos) << "] in logname prefix: " << "[" << prefix_filename << "]" << std::endl;
return false;
} else if (prefix_filename.empty()) {
std::cerr << "Empty filename prefix is not allowed" << std::endl;
return false;
}
return true;
}
// Clean up the path if put in by mistake in the prefix
std::string prefixSanityFix(std::string prefix) {
prefix.erase(std::remove_if(prefix.begin(), prefix.end(), ::isspace), prefix.end());
prefix.erase(std::remove( prefix.begin(), prefix.end(), '\\'), prefix.end()); // '\\'
prefix.erase(std::remove( prefix.begin(), prefix.end(), '.'), prefix.end()); // '.'
prefix.erase(std::remove(prefix.begin(), prefix.end(), ':'), prefix.end()); // ':'
if (!isValidFilename(prefix)) {
return "";
}
return prefix;
}
std::string pathSanityFix(std::string path, std::string file_name) {
// Unify the delimeters,. maybe sketchy solution but it seems to work
// on at least win7 + ubuntu. All bets are off for older windows
std::replace(path.begin(), path.end(), '\\', '/');
// clean up in case of multiples
auto contains_end = [&](std::string & in) -> bool {
size_t size = in.size();
if (!size) return false;
char end = in[size - 1];
return (end == '/' || end == ' ');
};
while (contains_end(path)) { path.erase(path.size() - 1); }
if (!path.empty()) {
path.insert(path.end(), '/');
}
path.insert(path.size(), file_name);
return path;
}
std::string createLogFileName(const std::string& verified_prefix) {
std::stringstream ossName;
ossName.fill('0');
ossName << verified_prefix << MeehLog::localtime_formatted(systemtime_now(), file_name_time_formatted);
ossName << ".log";
return ossName.str();
}
bool openLogFile(const std::string& complete_file_with_path, std::ofstream& outstream) {
std::ios_base::openmode mode = std::ios_base::out; // for clarity: it's really overkill since it's an ofstream
mode |= std::ios_base::trunc;
outstream.open(complete_file_with_path, mode);
if (!outstream.is_open()) {
std::ostringstream ss_error;
ss_error << "FILE ERROR: could not open log file:[" << complete_file_with_path << "]";
ss_error << "\nstd::ios_base state = " << outstream.rdstate();
std::cerr << ss_error.str().c_str() << std::endl << std::flush;
outstream.close();
return false;
}
std::ostringstream ss_entry;
// Day Month Date Time Year: is written as "%a %b %d %H:%M:%S %Y" and formatted output as : Wed Aug 10 08:19:45 2014
ss_entry << "MeehLog created log file at: " << MeehLog::localtime_formatted(systemtime_now(), "%a %b %d %H:%M:%S %Y") << "\n";
ss_entry << "LOG format: [YYYY/MM/DD hh:mm:ss uuu* LEVEL FILE:LINE] message (uuu*: microsecond counter since initialization of log worker)\n\n";
outstream << ss_entry.str() << std::flush;
outstream.fill('0');
return true;
}
std::unique_ptr<std::ofstream> createLogFile(const std::string& file_with_full_path) {
std::unique_ptr<std::ofstream> out(new std::ofstream);
std::ofstream& stream(*(out.get()));
bool success_with_open_file = openLogFile(file_with_full_path, stream);
if (false == success_with_open_file) {
out.release(); // nullptr contained ptr<file> signals error in creating the log file
}
return out;
}
} // end anonymous namespace
/** The Real McCoy Background worker, while g2LogWorker gives the
* asynchronous API to put job in the background the g2LogWorkerImpl
* does the actual background thread work */
struct SharedLogWorkerImpl {
SharedLogWorkerImpl(const std::string& log_prefix, const std::string& log_directory);
~SharedLogWorkerImpl();
void backgroundFileWrite(MeehLog::internal::LogEntry message);
void backgroundExitFatal(MeehLog::internal::FatalMessage fatal_message);
std::string backgroundChangeLogFile(const std::string& directory);
std::string backgroundFileName();
std::string log_file_with_path_;
std::string log_prefix_backup_; // needed in case of future log file changes of directory
std::unique_ptr<MeehLog::Active> mBg;
std::unique_ptr<std::ofstream> mOutptr;
steady_time_point steady_start_time_;
private:
SharedLogWorkerImpl& operator=(const SharedLogWorkerImpl&);
SharedLogWorkerImpl(const SharedLogWorkerImpl& other);
std::ofstream& filestream() {return *(mOutptr.get());}
};
//
// Private API implementation : SharedLogWorkerImpl
SharedLogWorkerImpl::SharedLogWorkerImpl(const std::string& log_prefix, const std::string& log_directory)
: log_file_with_path_(log_directory)
, log_prefix_backup_(log_prefix)
, mBg(MeehLog::Active::createActive())
, mOutptr(new std::ofstream)
, steady_start_time_(std::chrono::steady_clock::now()) { // TODO: ha en timer function steadyTimer som har koll på start
log_prefix_backup_ = prefixSanityFix(log_prefix);
if (!isValidFilename(log_prefix_backup_)) {
// illegal prefix, refuse to start
std::cerr << "MeehLog: forced abort due to illegal log prefix [" << log_prefix << "]" << std::endl << std::flush;
abort();
}
std::string file_name = createLogFileName(log_prefix_backup_);
log_file_with_path_ = pathSanityFix(log_file_with_path_, file_name);
mOutptr = createLogFile(log_file_with_path_);
if (!mOutptr) {
std::cerr << "Cannot write logfile to location, attempting current directory" << std::endl;
log_file_with_path_ = "./" + file_name;
mOutptr = createLogFile(log_file_with_path_);
}
assert((mOutptr) && "cannot open log file at startup");
}
SharedLogWorkerImpl::~SharedLogWorkerImpl() {
std::ostringstream ss_exit;
mBg.reset(); // flush the log queue
ss_exit << "\nMeehLog file shutdown at: " << MeehLog::localtime_formatted(systemtime_now(), time_formatted);
filestream() << ss_exit.str() << std::flush;
}
void SharedLogWorkerImpl::backgroundFileWrite(LogEntry message) {
using namespace std;
std::ofstream& out(filestream());
auto log_time = message.mTimestamp;
auto steady_time = std::chrono::steady_clock::now();
out << "\n" << MeehLog::localtime_formatted(log_time, date_formatted);
out << " " << MeehLog::localtime_formatted(log_time, time_formatted);
out << " " << chrono::duration_cast<std::chrono::microseconds>(steady_time - steady_start_time_).count();
out << "\t" << message.mMsg << std::flush;
}
void SharedLogWorkerImpl::backgroundExitFatal(FatalMessage fatal_message) {
backgroundFileWrite(fatal_message.mMessage);
LogEntry flushEntry{"Log flushed successfully to disk \nExiting", MeehLog::internal::systemtime_now()};
backgroundFileWrite(flushEntry);
std::cerr << "MeehLog exiting after receiving fatal event" << std::endl;
std::cerr << "Log file at: [" << log_file_with_path_ << "]\n" << std::endl << std::flush;
filestream().close();
MeehLog::shutDownLogging(); // only an initialized logger can recieve a fatal message. So shutting down logging now is fine.
//exitWithDefaultSignalHandler(fatal_message.mSignalId);
perror("MeehLog exited after receiving FATAL trigger. Flush message status: "); // should never reach this point
}
std::string SharedLogWorkerImpl::backgroundChangeLogFile(const std::string& directory) {
std::string file_name = createLogFileName(log_prefix_backup_);
std::string prospect_log = directory + file_name;
std::unique_ptr<std::ofstream> log_stream = createLogFile(prospect_log);
if (nullptr == log_stream) {
LogEntry error("Unable to change log file. Illegal filename or busy? Unsuccessful log name was:" + prospect_log, MeehLog::internal::systemtime_now());
backgroundFileWrite(error);
return ""; // no success
}
std::ostringstream ss_change;
ss_change << "\nChanging log file from : " << log_file_with_path_;
ss_change << "\nto new location: " << prospect_log << "\n";
backgroundFileWrite({ss_change.str().c_str(), MeehLog::internal::systemtime_now()});
ss_change.str("");
// setting the new log as active
std::string old_log = log_file_with_path_;
log_file_with_path_ = prospect_log;
mOutptr = std::move(log_stream);
ss_change << "\nNew log file. The previous log file was at: ";
ss_change << old_log;
backgroundFileWrite({ss_change.str(), MeehLog::internal::systemtime_now()});
return log_file_with_path_;
}
std::string SharedLogWorkerImpl::backgroundFileName() {
return log_file_with_path_;
}
// Public API implementation
//
SharedLogWorker::SharedLogWorker(const std::string& log_prefix, const std::string& log_directory)
: pimpl(new SharedLogWorkerImpl(log_prefix, log_directory))
, logFileWithPath(pimpl->log_file_with_path_) {
assert((pimpl != nullptr) && "shouild never happen");
}
SharedLogWorker::~SharedLogWorker() {
pimpl.reset();
MeehLog::shutDownLoggingForActiveOnly(this);
std::cerr << "\nExiting, log location: " << logFileWithPath << std::endl << std::flush;
}
void SharedLogWorker::save(const MeehLog::internal::LogEntry& msg) {
pimpl->mBg->send(std::bind(&SharedLogWorkerImpl::backgroundFileWrite, pimpl.get(), msg));
}
void SharedLogWorker::fatal(MeehLog::internal::FatalMessage fatal_message) {
pimpl->mBg->send(std::bind(&SharedLogWorkerImpl::backgroundExitFatal, pimpl.get(), fatal_message));
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpessimizing-move"
std::future<std::string> SharedLogWorker::changeLogFile(const std::string& log_directory) {
MeehLog::Active* bgWorker = pimpl->mBg.get();
auto mBgcall = [this, log_directory]() {return pimpl->backgroundChangeLogFile(log_directory);};
auto future_result = MeehLog::spawn_task(mBgcall, bgWorker);
return std::move(future_result);
}
std::future<void> SharedLogWorker::genericAsyncCall(std::function<void()> f) {
auto bgWorker = pimpl->mBg.get();
auto future_result = MeehLog::spawn_task(f, bgWorker);
return std::move(future_result);
}
std::future<std::string> SharedLogWorker::logFileName() {
MeehLog::Active* bgWorker = pimpl->mBg.get();
auto mBgcall = [&]() {return pimpl->backgroundFileName();};
auto future_result = MeehLog::spawn_task(mBgcall , bgWorker);
return std::move(future_result);
}
#pragma GCC diagnostic pop

View File

@ -1,144 +0,0 @@
//
// LoggerWorker.hpp
// I2PLauncher
//
// Created by Mikal Villa on 27/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
// Imported/Refactored from earlier C++ project of Meeh
//
#ifndef LoggerWorker_hpp
#define LoggerWorker_hpp
#ifdef __cplusplus
#include <memory>
#include <thread>
#include <mutex>
#include <future>
#include <string>
#include <condition_variable>
#include <functional>
// TODO: Configure the project to avoid such includes.
#include "../include/sharedqueue.h"
#include "Logger.h"
struct SharedLogWorkerImpl;
namespace MeehLog {
typedef std::function<void()> Callback;
class Active {
private:
Active(const Active&);
Active& operator=(const Active&);
Active(); // Construction ONLY through factory createActive();
void doDone(){mDone = true;}
void run();
shared_queue<Callback> mMq;
std::thread mThd;
bool mDone; // finished flag to be set through msg queue by ~Active
public:
virtual ~Active();
void send(Callback msg_);
static std::unique_ptr<Active> createActive(); // Factory: safe construction & thread start
};
// A straightforward technique to move around packaged_tasks.
// Instances of std::packaged_task are MoveConstructible and MoveAssignable, but
// not CopyConstructible or CopyAssignable. To put them in a std container they need
// to be wrapped and their internals "moved" when tried to be copied.
template<typename Moveable>
struct MoveOnCopy {
mutable Moveable _move_only;
explicit MoveOnCopy(Moveable&& m) : _move_only(std::move(m)) {}
MoveOnCopy(MoveOnCopy const& t) : _move_only(std::move(t._move_only)) {}
MoveOnCopy(MoveOnCopy&& t) : _move_only(std::move(t._move_only)) {}
MoveOnCopy& operator=(MoveOnCopy const& other) {
_move_only = std::move(other._move_only);
return *this;
}
MoveOnCopy& operator=(MoveOnCopy&& other) {
_move_only = std::move(other._move_only);
return *this;
}
void operator()() { _move_only(); }
Moveable& get() { return _move_only; }
Moveable release() { return std::move(_move_only); }
};
// Generic helper function to avoid repeating the steps for managing
// asynchronous task job (by active object) that returns a future results
// could of course be made even more generic if done more in the way of
// std::async, ref: http://en.cppreference.com/w/cpp/thread/async
//
// Example usage:
// std::unique_ptr<Active> bgWorker{Active::createActive()};
// ...
// auto msg_call=[=](){return ("Hello from the Background");};
// auto future_msg = g2::spawn_task(msg_lambda, bgWorker.get());
template <typename Func>
std::future<typename std::result_of<Func()>::type> spawn_task(Func func, Active* worker) {
typedef typename std::result_of<Func()>::type result_type;
typedef std::packaged_task<result_type()> task_type;
task_type task(std::move(func));
std::future<result_type> result = task.get_future();
worker->send(MoveOnCopy<task_type>(std::move(task)));
return std::move(result);
}
}
class SharedLogWorker {
public:
/**
* \param log_prefix is the 'name' of the binary, this give the log name 'LOG-'name'-...
* \param log_directory gives the directory to put the log files */
SharedLogWorker(const std::string& log_prefix, const std::string& log_directory);
virtual ~SharedLogWorker();
/// pushes in background thread (asynchronously) input messages to log file
void save(const MeehLog::internal::LogEntry& entry);
/// Will push a fatal message on the queue, this is the last message to be processed
/// this way it's ensured that all existing entries were flushed before 'fatal'
/// Will abort the application!
void fatal(MeehLog::internal::FatalMessage fatal_message);
/// Attempt to change the current log file to another name/location.
/// returns filename with full path if successful, else empty string
std::future<std::string> changeLogFile(const std::string& log_directory);
/// Does an independent action in FIFO order, compared to the normal LOG statements
/// Example: auto threadID = [] { std::cout << "thread id: " << std::this_thread::get_id() << std::endl; };
/// auto call = logger.genericAsyncCall(threadID);
/// // this will print out the thread id of the background worker
std::future<void> genericAsyncCall(std::function<void()> f);
/// Probably only needed for unit-testing or specific log management post logging
/// request to get log name is processed in FIFO order just like any other background job.
std::future<std::string> logFileName();
private:
std::unique_ptr<SharedLogWorkerImpl> pimpl;
const std::string logFileWithPath;
SharedLogWorker(const SharedLogWorker&);
SharedLogWorker& operator=(const SharedLogWorker&);
};
#endif // __cplusplus
#endif /* LoggerWorker_hpp */

View File

@ -1,219 +0,0 @@
#pragma once
#include <dispatch/dispatch.h>
#include <memory.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Cocoa/Cocoa.h>
#import <AppKit/AppKit.h>
#ifdef __cplusplus
#include <vector>
#include <string>
const std::vector<NSString*> defaultStartupFlags {
@"-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"
};
const std::vector<std::string> defaultFlagsForExtractorJob {
"-Xmx512M",
"-Xms128m",
"-Djava.awt.headless=true"
};
#endif
@class RTaskOptions;
@interface RTaskOptions : NSObject
@property (strong) NSString* binPath;
@property (strong) NSArray<NSString *>* arguments;
@property (strong) NSString* i2pBaseDir;
@end
@class I2PRouterTask;
@interface I2PRouterTask : NSObject
@property (strong) NSTask* routerTask;
@property (strong) NSPipe *processPipe;
@property (atomic) BOOL isRouterRunning;
@property (atomic) BOOL userRequestedRestart;
- (instancetype) initWithOptions : (RTaskOptions*) options;
- (int) execute;
- (void) requestShutdown;
- (void) requestRestart;
- (BOOL) isRunning;
- (int) getPID;
- (void)routerStdoutData:(NSNotification *)notification;
@end
@interface IIProcessInfo : NSObject {
@private
int numberOfProcesses;
NSMutableArray *processList;
}
- (id) init;
- (int)numberOfProcesses;
- (void)obtainFreshProcessList;
- (BOOL)findProcessWithStringInNameOrArguments:(NSString *)procNameToSearch;
@end
#ifdef __cplusplus
// Inspired by the "ps" util.
inline std::string getArgvOfPid(int pid) {
int mib[3], argmax, nargs, c = 0;
size_t size;
char *procargs, *sp, *np, *cp;
int show_args = 1;
mib[0] = CTL_KERN;
mib[1] = KERN_ARGMAX;
size = sizeof(argmax);
if (sysctl(mib, 2, &argmax, &size, NULL, 0) == -1) {
return std::string("sorry");
}
/* Allocate space for the arguments. */
procargs = (char *)malloc(argmax);
if (procargs == NULL) {
return std::string("sorry");
}
/*
* Make a sysctl() call to get the raw argument space of the process.
* The layout is documented in start.s, which is part of the Csu
* project. In summary, it looks like:
*
* /---------------\ 0x00000000
* : :
* : :
* |---------------|
* | argc |
* |---------------|
* | arg[0] |
* |---------------|
* : :
* : :
* |---------------|
* | arg[argc - 1] |
* |---------------|
* | 0 |
* |---------------|
* | env[0] |
* |---------------|
* : :
* : :
* |---------------|
* | env[n] |
* |---------------|
* | 0 |
* |---------------| <-- Beginning of data returned by sysctl() is here.
* | argc |
* |---------------|
* | exec_path |
* |:::::::::::::::|
* | |
* | String area. |
* | |
* |---------------| <-- Top of stack.
* : :
* : :
* \---------------/ 0xffffffff
*/
mib[0] = CTL_KERN;
mib[1] = KERN_PROCARGS2;
mib[2] = pid;
size = (size_t)argmax;
if (sysctl(mib, 3, procargs, &size, NULL, 0) == -1) {
free(procargs);
return std::string("sorry");
}
memcpy(&nargs, procargs, sizeof(nargs));
cp = procargs + sizeof(nargs);
/* Skip the saved exec_path. */
for (; cp < &procargs[size]; cp++) {
if (*cp == '\0') {
/* End of exec_path reached. */
break;
}
}
if (cp == &procargs[size]) {
free(procargs);
return std::string("sorry");
}
/* Skip trailing '\0' characters. */
for (; cp < &procargs[size]; cp++) {
if (*cp != '\0') {
/* Beginning of first argument reached. */
break;
}
}
if (cp == &procargs[size]) {
free(procargs);
return std::string("sorry");
}
/* Save where the argv[0] string starts. */
sp = cp;
/*
* Iterate through the '\0'-terminated strings and convert '\0' to ' '
* until a string is found that has a '=' character in it (or there are
* no more strings in procargs). There is no way to deterministically
* know where the command arguments end and the environment strings
* start, which is why the '=' character is searched for as a heuristic.
*/
for (np = NULL; c < nargs && cp < &procargs[size]; cp++) {
if (*cp == '\0') {
c++;
if (np != NULL) {
/* Convert previous '\0'. */
*np = ' ';
} else {
/* *argv0len = cp - sp; */
}
/* Note location of current '\0'. */
np = cp;
if (!show_args) {
/*
* Don't convert '\0' characters to ' '.
* However, we needed to know that the
* command name was terminated, which we
* now know.
*/
break;
}
}
}
/*
* sp points to the beginning of the arguments/environment string, and
* np should point to the '\0' terminator for the string.
*/
if (np == NULL || np == sp) {
/* Empty or unterminated string. */
free(procargs);
return std::string("sorry");
}
return std::string(sp);
}
#endif

View File

@ -1,242 +0,0 @@
#include "RouterTask.h"
#include <dispatch/dispatch.h>
#include <future>
#include <stdlib.h>
#ifdef __cplusplus
// TODO: Configure the project to avoid such includes.
#include "../include/subprocess.hpp"
#include "../include/PidWatcher.h"
#import "I2PLauncher-Swift.h"
#include "AppDelegate.h"
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <sys/sysctl.h>
#endif
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
@implementation RTaskOptions
@end
@implementation I2PRouterTask
- (void)routerStdoutData:(NSNotification *)notification
{
NSLog(@"%@", [[NSString alloc] initWithData:[notification.object availableData] encoding:NSUTF8StringEncoding]);
[notification.object waitForDataInBackgroundAndNotify];
}
- (instancetype) initWithOptions : (RTaskOptions*) options
{
self.userRequestedRestart = NO;
self.isRouterRunning = NO;
self.routerTask = [NSTask new];
self.processPipe = [NSPipe new];
[self.routerTask setLaunchPath:options.binPath];
[self.routerTask setArguments:options.arguments];
NSDictionary *envDict = @{
@"I2PBASE": options.i2pBaseDir
};
[self.routerTask setEnvironment: envDict];
NSLog(@"Using environment variables: %@", envDict);
[self.routerTask setStandardOutput:self.processPipe];
[self.routerTask setStandardError:self.processPipe];
[self.routerTask setTerminationHandler:^(NSTask* task) {
// Cleanup
NSLog(@"termHandler triggered!");
[[[RouterProcessStatus alloc] init] triggerEventWithEn:@"router_stop" details:@"normal shutdown"];
[[SBridge sharedInstance] setCurrentRouterInstance:nil];
sendUserNotification(APP_IDSTR, @"I2P Router has stopped");
}];
return self;
}
- (void) requestShutdown
{
[self.routerTask interrupt];
}
- (void) requestRestart
{ self.userRequestedRestart = YES;
kill([self.routerTask processIdentifier], SIGHUP);
}
- (BOOL) isRunning
{
return self.routerTask.running;
}
- (int) execute
{
@try {
[self.routerTask launch];
self.isRouterRunning = YES;
return 1;
}
@catch (NSException *e)
{
NSLog(@"Expection occurred %@", [e reason]);
self.isRouterRunning = NO;
[[[RouterProcessStatus alloc] init] triggerEventWithEn:@"router_exception" details:[e reason]];
[[SBridge sharedInstance] setCurrentRouterInstance:nil];
sendUserNotification(@"An error occured, can't start the I2P Router", [e reason]);
return 0;
}
}
- (int) getPID
{
return [self.routerTask processIdentifier];
}
@end
typedef struct kinfo_proc kinfo_proc;
@implementation IIProcessInfo
- (id) init
{
self = [super init];
if (self != nil)
{
numberOfProcesses = -1; // means "not initialized"
processList = NULL;
}
return self;
}
- (int)numberOfProcesses
{
return numberOfProcesses;
}
- (void)setNumberOfProcesses:(int)num
{
numberOfProcesses = num;
}
- (int)getBSDProcessList:(kinfo_proc **)procList
withNumberOfProcesses:(size_t *)procCount
{
#ifdef __cplusplus
int err;
kinfo_proc * result;
bool done;
static const int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
size_t length;
// a valid pointer procList holder should be passed
assert( procList != NULL );
// But it should not be pre-allocated
assert( *procList == NULL );
// a valid pointer to procCount should be passed
assert( procCount != NULL );
*procCount = 0;
result = NULL;
done = false;
do
{
assert( result == NULL );
// Call sysctl with a NULL buffer to get proper length
length = 0;
err = sysctl((int *)name,(sizeof(name)/sizeof(*name))-1,NULL,&length,NULL,0);
if( err == -1 )
err = errno;
// Now, proper length is optained
if( err == 0 )
{
result = (kinfo_proc *)malloc(length);
if( result == NULL )
err = ENOMEM; // not allocated
}
if( err == 0 )
{
err = sysctl( (int *)name, (sizeof(name)/sizeof(*name))-1, result, &length, NULL, 0);
if( err == -1 )
err = errno;
if( err == 0 )
done = true;
else if( err == ENOMEM )
{
assert( result != NULL );
free( result );
result = NULL;
err = 0;
}
}
}while ( err == 0 && !done );
// Clean up and establish post condition
if( err != 0 && result != NULL )
{
free(result);
result = NULL;
}
*procList = result; // will return the result as procList
if( err == 0 )
*procCount = length / sizeof( kinfo_proc );
assert( (err == 0) == (*procList != NULL ) );
return err;
}
- (void)obtainFreshProcessList
{
int i;
kinfo_proc *allProcs = 0;
size_t numProcs;
NSString *procName;
int err = [self getBSDProcessList:&allProcs withNumberOfProcesses:&numProcs];
if( err )
{
numberOfProcesses = -1;
processList = NULL;
return;
}
// Construct an array for ( process name, pid, arguments concat'ed )
processList = [NSMutableArray arrayWithCapacity:numProcs];
for( i = 0; i < numProcs; i++ )
{
int pid = (int)allProcs[i].kp_proc.p_pid;
procName = [NSString stringWithFormat:@"%s, pid %d, args: %s", allProcs[i].kp_proc.p_comm, pid, getArgvOfPid(pid).c_str()];
[processList addObject:procName];
}
[self setNumberOfProcesses:(int)numProcs];
free( allProcs );
#endif
}
- (BOOL)findProcessWithStringInNameOrArguments:(NSString *)procNameToSearch
{
BOOL seenProcessThatMatch = NO;
for (NSString* processInfoStr in processList) {
if ([processInfoStr containsString:procNameToSearch]) {
seenProcessThatMatch = YES;
break;
}
}
return seenProcessThatMatch;
}
@end

View File

@ -1,64 +0,0 @@
//
// SBridge.h
// I2PLauncher
//
// Created by Mikal Villa on 18/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import "RouterTask.h"
#ifdef __cplusplus
#include <memory>
#include <future>
#include <glob.h>
#include <string>
#include <vector>
// TODO: Configure the project to avoid such includes.
#include "../include/fn.h"
namespace osx {
inline void openUrl(NSString* url)
{
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString: url]];
}
}
inline std::vector<std::string> globVector(const std::string& pattern){
glob_t glob_result;
glob(pattern.c_str(),GLOB_TILDE,NULL,&glob_result);
std::vector<std::string> files;
for(unsigned int i=0;i<glob_result.gl_pathc;++i){
files.push_back(std::string(glob_result.gl_pathv[i]));
}
globfree(&glob_result);
return files;
}
inline std::string buildClassPathForObjC(std::string basePath)
{
NSBundle *launcherBundle = [NSBundle mainBundle];
auto jarList = globVector(basePath+std::string("/lib/*.jar"));
std::string classpathStrHead = "-classpath";
std::string classpathStr = "";
classpathStr += [[launcherBundle pathForResource:@"launcher" ofType:@"jar"] UTF8String];
std::string prefix(basePath);
prefix += "/lib/";
for_each(jarList, [&classpathStr](std::string str){ classpathStr += std::string(":") + str; });
return classpathStr;
}
#endif
@interface SBridge : NSObject
@property (nonatomic, assign) I2PRouterTask* currentRouterInstance;
- (void) openUrl:(NSString*)url;
+ (void) logProxy:(int)level formattedMsg:(NSString*)formattedMsg;
+ (void) sendUserNotification:(NSString*)title formattedMsg:(NSString*)formattedMsg;
+ (instancetype)sharedInstance; // this makes it a singleton
@end

View File

@ -1,63 +0,0 @@
//
// SBridge.m
// I2PLauncher
//
// Created by Mikal Villa on 18/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
#import "SBridge.h"
#ifdef __cplusplus
#include <functional>
#include <memory>
#include <glob.h>
#include <string>
#include <list>
#include <stdlib.h>
#include <future>
#include <vector>
#import <AppKit/AppKit.h>
#import "I2PLauncher-Swift.h"
#include "LoggerWorker.hpp"
#include "Logger.h"
#include "logger_c.h"
#include "AppDelegate.h"
// TODO: Configure the project to avoid such includes.
#include "../include/fn.h"
@implementation SBridge
// this makes it a singleton
+ (instancetype)sharedInstance {
static SBridge *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[SBridge alloc] init];
});
return sharedInstance;
}
+ (void) sendUserNotification:(NSString*)title formattedMsg:(NSString*)formattedMsg
{
sendUserNotification(title, formattedMsg);
}
- (void) openUrl:(NSString*)url
{
osx::openUrl(url);
}
+ (void) logProxy:(int)level formattedMsg:(NSString*)formattedMsg
{
MLog(level, formattedMsg);
}
@end
#endif

View File

@ -1,88 +0,0 @@
//
// logger_c.h
// I2PLauncher
//
// Created by Mikal Villa on 30/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
#ifndef logger_c_h
#define logger_c_h
#include "Logger.h"
#include "LoggerWorker.hpp"
/*
void genericLogger(int loglevel, va_list params) {
#ifdef __cplusplus
const char * paramArray[10];
int numParams = 0;
NSString* arg = nil;
while ((arg = va_arg(params, NSString *))) {
paramArray[numParams++] = [arg cStringUsingEncoding:[NSString defaultCStringEncoding]];
}
switch (loglevel) {
case 0:
MLOGF(ANNOYING) << params;
break;
case 1:
MLOGF(DEBUG) << params;
break;
case 2:
MLOGF(INFO) << params;
break;
case 3:
MLOGF(WARN) << params;
break;
default:
assert(false);
}
#endif
}
*/
inline void MLog(int loglevel, NSString* format, ...)
{
#ifdef __cplusplus
va_list vargs;
va_start(vargs, format);
NSString* formattedMessage = [[NSString alloc] initWithFormat:format arguments:vargs];
va_end(vargs);
NSString* message = formattedMessage;
switch (loglevel) {
case 0:
MLOG(ANNOYING) << [message UTF8String];
break;
case 1:
MLOG(DEBUG) << [message UTF8String];
break;
case 2:
MLOG(INFO) << [message UTF8String];
break;
case 3:
MLOG(WARN) << [message UTF8String];
break;
case 4:
MLOG(ERROR) << [message UTF8String];
break;
default:
#if DEBUG
assert(false);
#else
return;
#endif
}
#endif
}
#define MMLog(format_string,...) ((MLog(1, [NSString stringWithFormat:format_string,##__VA_ARGS__])))
#endif /* logger_c_h */

View File

@ -1,388 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="E2c-ib-a9d">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Window Controller-->
<scene sceneID="2xO-NO-0Hd">
<objects>
<windowController storyboardIdentifier="Preferences" id="E2c-ib-a9d" customClass="PreferencesWindowController" customModule="I2PLauncher" customModuleProvider="target" sceneMemberID="viewController">
<window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="7sM-0n-LvU">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="294" y="362" width="480" height="270"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1177"/>
<connections>
<outlet property="delegate" destination="E2c-ib-a9d" id="0bv-to-0T8"/>
</connections>
</window>
<connections>
<segue destination="Gn4-1Z-ZmR" kind="relationship" relationship="window.shadowedContentViewController" id="awB-ye-0WY"/>
</connections>
</windowController>
<customObject id="BZB-oK-A8Y" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-2004" y="208"/>
</scene>
<!--Tab View Controller-->
<scene sceneID="cDU-zb-AEX">
<objects>
<tabViewController selectedTabViewItemIndex="2" tabStyle="toolbar" id="Gn4-1Z-ZmR" sceneMemberID="viewController">
<tabViewItems>
<tabViewItem identifier="" image="NSInfo" id="IDv-FU-QPH"/>
<tabViewItem identifier="" image="NSUser" id="zLE-4e-HMO"/>
<tabViewItem image="NSAdvanced" id="1E7-3R-0EF"/>
</tabViewItems>
<viewControllerTransitionOptions key="transitionOptions" allowUserInteraction="YES"/>
<tabView key="tabView" type="noTabsNoBorder" id="MwI-11-flE">
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
<autoresizingMask key="autoresizingMask"/>
<font key="font" metaFont="message"/>
<connections>
<outlet property="delegate" destination="Gn4-1Z-ZmR" id="Sqn-qQ-We5"/>
</connections>
</tabView>
<connections>
<outlet property="tabView" destination="MwI-11-flE" id="dDk-kJ-uqy"/>
<segue destination="Ikw-G1-sbk" kind="relationship" relationship="tabItems" id="4gr-ZN-WkU"/>
<segue destination="ZrJ-Ig-h1D" kind="relationship" relationship="tabItems" id="BRO-ke-swp"/>
<segue destination="mVJ-sm-WjL" kind="relationship" relationship="tabItems" id="ksS-qq-FNs"/>
</connections>
</tabViewController>
<customObject id="H0i-51-VWT" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-1263" y="201"/>
</scene>
<!--Launcher-->
<scene sceneID="aj7-2h-9Zl">
<objects>
<viewController title="Launcher" id="Ikw-G1-sbk" customClass="PreferencesViewController" customModule="I2PLauncher" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="oyR-x0-o8B">
<rect key="frame" x="0.0" y="0.0" width="361" height="242"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="aAH-4e-tuf">
<rect key="frame" x="18" y="198" width="325" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Start the I2P Launcher at User login (Mac startup)" bezelStyle="regularSquare" imagePosition="left" inset="2" id="EOY-1C-aqa">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="checkboxStartLauncherOnOSXStartupClicked:" target="Ikw-G1-sbk" id="rpo-MV-F0V"/>
</connections>
</button>
<box fixedFrame="YES" boxType="custom" cornerRadius="4" title="Box" translatesAutoresizingMaskIntoConstraints="NO" id="KlH-NM-u60">
<rect key="frame" x="20" y="66" width="321" height="86"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<view key="contentView" ambiguous="YES" id="UL7-aj-Mxj">
<rect key="frame" x="1" y="1" width="319" height="84"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="mgK-Va-AeP">
<rect key="frame" x="5" y="52" width="83" height="25"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Show as:" id="19Z-jV-gnQ">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Edq-cq-ZrT">
<rect key="frame" x="6" y="27" width="83" height="18"/>
<buttonCell key="cell" type="radio" title="Dock Icon" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="ZTf-wy-Wod">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="radioDockIconOnlySelected:" target="Ikw-G1-sbk" id="30f-en-hWz"/>
</connections>
</button>
<button verticalHuggingPriority="750" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Y74-ti-gJ4">
<rect key="frame" x="238" y="27" width="51" height="18"/>
<buttonCell key="cell" type="radio" title="Both" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="TbR-qb-p7G">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="radioBothIconSelected:" target="Ikw-G1-sbk" id="M8u-jZ-0ub"/>
</connections>
</button>
<button verticalHuggingPriority="750" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="hWQ-Fn-Au7">
<rect key="frame" x="107" y="27" width="105" height="18"/>
<buttonCell key="cell" type="radio" title="Menubar Icon" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="FSa-BS-LVr">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="radioMenubarOnlySelected:" target="Ikw-G1-sbk" id="X24-j5-9sA"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="Edq-cq-ZrT" firstAttribute="width" secondItem="Y74-ti-gJ4" secondAttribute="width" id="I3w-l3-2ZW"/>
<constraint firstItem="Edq-cq-ZrT" firstAttribute="width" secondItem="hWQ-Fn-Au7" secondAttribute="width" id="d1N-dE-tGj"/>
<constraint firstItem="Edq-cq-ZrT" firstAttribute="width" secondItem="Y74-ti-gJ4" secondAttribute="height" multiplier="5:1" id="dv4-dW-3FO"/>
</constraints>
</view>
</box>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="XPH-xk-vMg">
<rect key="frame" x="18" y="173" width="313" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Start Firefox with a I2P enabled profile at launch" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="i7v-mQ-d1Y">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="checkboxStartFirefoxAlsoAtLaunchClicked:" target="Ikw-G1-sbk" id="8L9-vJ-GyU"/>
</connections>
</button>
</subviews>
</view>
<connections>
<outlet property="checkboxStartFirefoxAlso" destination="XPH-xk-vMg" id="gzR-kC-XRa"/>
<outlet property="checkboxStartWithOSX" destination="aAH-4e-tuf" id="Lui-vl-1cg"/>
<outlet property="radioBothIcon" destination="Y74-ti-gJ4" id="tFQ-lh-RT8"/>
<outlet property="radioDockIcon" destination="Edq-cq-ZrT" id="BQ8-gt-vhx"/>
<outlet property="radioMenubarIcon" destination="hWQ-Fn-Au7" id="8p5-9H-RcJ"/>
</connections>
</viewController>
<customObject id="6YO-uB-NC9" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-448.5" y="-209"/>
</scene>
<!--Router-->
<scene sceneID="D7p-Ep-rhA">
<objects>
<viewController title="Router" id="ZrJ-Ig-h1D" customClass="PreferencesViewController" customModule="I2PLauncher" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="ueb-yj-LIx">
<rect key="frame" x="0.0" y="0.0" width="319" height="132"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bWG-1q-47t">
<rect key="frame" x="18" y="96" width="292" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Start the router when the launcher is started" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="8aU-yf-Lzp">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="checkboxStartRouterWithLauncherClicked:" target="ZrJ-Ig-h1D" id="uEH-nn-EuL"/>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4Ii-hB-9X6">
<rect key="frame" x="18" y="59" width="283" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Stop the router when the launcher is exited" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="TgB-Sz-u2E">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="checkboxStopRouterWithLauncherClicked:" target="ZrJ-Ig-h1D" id="IdO-17-off"/>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1k4-dg-OqV">
<rect key="frame" x="14" y="13" width="159" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Reset configuration" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="y6O-Pw-sua">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="buttonResetRouterConfigClicked:" target="ZrJ-Ig-h1D" id="hX2-JI-CrK"/>
</connections>
</button>
</subviews>
</view>
<connections>
<outlet property="buttonResetRouterConfig" destination="1k4-dg-OqV" id="eI9-Qx-foO"/>
<outlet property="checkboxStartWithLauncher" destination="bWG-1q-47t" id="s1f-zJ-9CW"/>
<outlet property="checkboxStopWithLauncher" destination="4Ii-hB-9X6" id="SUY-Nn-EkH"/>
</connections>
</viewController>
<customObject id="HfJ-oO-OiU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-455" y="-487"/>
</scene>
<!--Advanced-->
<scene sceneID="CCY-VE-I04">
<objects>
<viewController title="Advanced" id="mVJ-sm-WjL" customClass="PreferencesViewController" customModule="I2PLauncher" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="za6-Zc-Mi0">
<rect key="frame" x="0.0" y="0.0" width="601" height="391"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="SSS-Fz-fYY">
<rect key="frame" x="18" y="345" width="257" height="28"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Yes, I want to edit advanced settings" bezelStyle="regularSquare" imagePosition="left" inset="2" id="UzU-4G-MLw">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="checkboxEnableAdvancedPreferencesClicked:" target="mVJ-sm-WjL" id="6TH-cr-HJa"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="jMA-gl-Wbw">
<rect key="frame" x="264" y="330" width="319" height="51"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" id="ygn-Tg-kwS">
<font key="font" metaFont="system"/>
<string key="title">Note: Advanced settings are editable via the defaults command line tool. Features that are not ready are disabled in preferences.</string>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<scrollView identifier="ScollViewTableView" fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HtO-MW-DI8">
<rect key="frame" x="20" y="11" width="561" height="311"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<clipView key="contentView" ambiguous="YES" id="D8u-eX-bBu">
<rect key="frame" x="1" y="0.0" width="559" height="310"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView identifier="AdvancedView" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" alternatingRowBackgroundColors="YES" columnSelection="YES" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" headerView="M6Y-Yi-YWr" viewBased="YES" id="lzO-OC-oiQ" customClass="AdvancedTableView" customModule="I2PLauncher" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="645" height="285"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn identifier="KeyColumnID" editable="NO" width="116" minWidth="40" maxWidth="1000" id="3Hj-6J-5ww">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Key">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="d5O-BO-cq8">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="8Z2-cm-fKP">
<rect key="frame" x="1" y="1" width="116" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="s7j-Qx-PVz">
<rect key="frame" x="0.0" y="0.0" width="116" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="tMT-gR-wu2">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="s7j-Qx-PVz" id="Mt0-Gz-VEn"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="DefaultColumnID" editable="NO" width="120" minWidth="40" maxWidth="1000" id="xna-T0-L5h">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Default Value">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="MUm-Xs-FfY">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="b5W-zl-l9T">
<rect key="frame" x="120" y="1" width="120" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="K1c-BK-Dzb">
<rect key="frame" x="0.0" y="0.0" width="120" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="zRJ-Jw-vJZ">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="K1c-BK-Dzb" id="824-qp-OmE"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn identifier="ValueColumnID" width="400" minWidth="10" maxWidth="3.4028234663852886e+38" id="hjb-YH-EoM">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Selected Value">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="adF-18-fTe">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="zRg-yl-f2l">
<rect key="frame" x="243" y="1" width="400" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2ro-Gm-4DU">
<rect key="frame" x="0.0" y="0.0" width="400" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" title="Table View Cell" usesSingleLineMode="YES" id="Sj7-Se-KMC">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<action selector="onEnterInTextField:" target="mVJ-sm-WjL" id="4y0-HJ-teb"/>
</connections>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="2ro-Gm-4DU" id="bXs-LL-3SR"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
</tableView>
</subviews>
</clipView>
<scroller key="horizontalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="Cnd-ra-6V7">
<rect key="frame" x="1" y="294" width="559" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="Nnf-g8-3wh">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<tableHeaderView key="headerView" id="M6Y-Yi-YWr">
<rect key="frame" x="0.0" y="0.0" width="645" height="25"/>
<autoresizingMask key="autoresizingMask"/>
</tableHeaderView>
</scrollView>
</subviews>
</view>
<connections>
<outlet property="advPrefTableView" destination="lzO-OC-oiQ" id="VfI-ro-Bq5"/>
</connections>
</viewController>
<customObject id="ASm-AK-p0y" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-397.5" y="254.5"/>
</scene>
</scenes>
<resources>
<image name="NSAdvanced" width="128" height="128"/>
<image name="NSInfo" width="128" height="128"/>
<image name="NSUser" width="128" height="128"/>
</resources>
</document>

View File

@ -1,12 +0,0 @@
<?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>services</key>
<array>
<string>I2PRouterService</string>
<string>HttpTunnelService</string>
<string>IrcTunnelService</string>
</array>
</dict>
</plist>

View File

@ -1,188 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--PopoverController-->
<scene sceneID="4eF-i8-6L0">
<objects>
<viewController identifier="PopoverView" storyboardIdentifier="PopoverView" id="Ii7-Rb-Ls8" userLabel="PopoverController" customClass="PopoverViewController" customModule="I2PLauncher" customModuleProvider="target" sceneMemberID="viewController">
<tabView key="view" drawsBackground="NO" allowsTruncatedLabels="NO" initialItem="Fle-u6-lgB" id="dmf-Go-6qY">
<rect key="frame" x="0.0" y="0.0" width="450" height="333"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<font key="font" metaFont="system"/>
<tabViewItems>
<tabViewItem label="Router Status &amp; Control" identifier="" id="Fle-u6-lgB">
<view key="view" identifier="routerStatusTabView" canDrawConcurrently="YES" id="xXg-Bt-RWR" customClass="RouterStatusView" customModule="I2PLauncher" customModuleProvider="target">
<rect key="frame" x="10" y="33" width="430" height="287"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<box fixedFrame="YES" boxType="secondary" title="Router information and status" translatesAutoresizingMaskIntoConstraints="NO" id="e8C-qI-SCp">
<rect key="frame" x="3" y="49" width="285" height="231"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<view key="contentView" id="zBB-wE-VXr">
<rect key="frame" x="3" y="3" width="279" height="213"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField identifier="RouterStatusLabel" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0Eo-re-5WK" userLabel="RouterStatusLabel">
<rect key="frame" x="6" y="189" width="245" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Router status: Unknown" id="m7V-Se-tnf">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField identifier="RouterVersionLabel" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="sGy-NV-gmH" userLabel="RouterVersionLabel">
<rect key="frame" x="6" y="170" width="245" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Router version: Unknown" id="Mda-Os-8O9">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField identifier="RouterStartedByLabel" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tgG-IT-Ojg" userLabel="RouterStartedByLabel">
<rect key="frame" x="6" y="151" width="245" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Router started by launcher? No" id="WBg-nH-kwu">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField identifier="RouterUptimeLabel" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="f2W-Kc-cxB" userLabel="RouterUptimeLabel">
<rect key="frame" x="6" y="132" width="245" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Router uptime: Unknown" id="uQ0-cW-tYL">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField identifier="RouterPIDLabel" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bmP-Ui-gyo" userLabel="RouterPIDLabel">
<rect key="frame" x="6" y="113" width="245" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Router PID: Unknown" id="uFb-he-Wf3">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
</view>
</box>
<box fixedFrame="YES" title="Quick Control" translatesAutoresizingMaskIntoConstraints="NO" id="IIP-Qi-7dp">
<rect key="frame" x="287" y="49" width="150" height="231"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<view key="contentView" identifier="QuickControlView" id="D8V-d8-0wT">
<rect key="frame" x="3" y="3" width="144" height="213"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button identifier="startstopRouterButton" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1eu-Qw-TD9" userLabel="start-stop-button">
<rect key="frame" x="10" y="175" width="126" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Start/Stop Router" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="OYN-sS-eQH">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button identifier="openConsoleButton" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="XZi-oz-5gy" userLabel="open-console-button">
<rect key="frame" x="10" y="149" width="126" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Open Console" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Yh8-lj-JFi">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="93P-Qg-IND">
<rect key="frame" x="10" y="122" width="126" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Preferences" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="vab-yq-aBK">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="onPreferencesClick:" target="Ii7-Rb-Ls8" id="xzS-61-nx7"/>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="SQB-9F-cGZ">
<rect key="frame" x="10" y="95" width="126" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Launch Firefox" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="MDt-Na-H9e">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
</subviews>
</view>
</box>
</subviews>
<accessibility identifier="routerStatusTabView"/>
<connections>
<outlet property="launchFirefoxButton" destination="SQB-9F-cGZ" id="Rp9-UH-O8q"/>
<outlet property="openConsoleButton" destination="XZi-oz-5gy" id="6xH-8v-63j"/>
<outlet property="quickControlView" destination="D8V-d8-0wT" id="4to-rN-2eL"/>
<outlet property="routerPIDLabel" destination="bmP-Ui-gyo" id="x5L-c1-yja"/>
<outlet property="routerStartStopButton" destination="1eu-Qw-TD9" id="FLt-MK-y5u"/>
<outlet property="routerStartedByLabel" destination="tgG-IT-Ojg" id="dA0-3w-cuF"/>
<outlet property="routerStatusLabel" destination="0Eo-re-5WK" id="7dm-Et-eSn"/>
<outlet property="routerUptimeLabel" destination="f2W-Kc-cxB" id="4Ya-qU-eb3"/>
<outlet property="routerVersionLabel" destination="sGy-NV-gmH" id="tM5-4M-DKy"/>
</connections>
</view>
</tabViewItem>
<tabViewItem label="Router Log Viewer" identifier="" id="IFq-CR-cjD" customClass="LogViewerViewController" customModule="I2PLauncher" customModuleProvider="target">
<view key="view" id="7U9-d7-IVr">
<rect key="frame" x="10" y="33" width="430" height="287"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView identifier="LoggerTextScrollView" fixedFrame="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jca-Kn-cO2" userLabel="LoggerTextScrollView">
<rect key="frame" x="11" y="17" width="409" height="228"/>
<autoresizingMask key="autoresizingMask"/>
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" id="E5l-WA-qOn">
<rect key="frame" x="1" y="1" width="407" height="226"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView identifier="LoggerTextView" ambiguous="YES" importsGraphics="NO" verticallyResizable="YES" usesFontPanel="YES" findStyle="panel" continuousSpellChecking="YES" allowsUndo="YES" usesRuler="YES" allowsNonContiguousLayout="YES" quoteSubstitution="YES" dashSubstitution="YES" spellingCorrection="YES" smartInsertDelete="YES" id="bgQ-8i-Xgb" userLabel="LoggerTextView">
<rect key="frame" x="0.0" y="0.0" width="407" height="226"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="407" height="226"/>
<size key="maxSize" width="463" height="10000000"/>
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
</textView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="MRF-Wt-zdZ">
<rect key="frame" x="-100" y="-100" width="87" height="18"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="Xq6-ur-WuT">
<rect key="frame" x="392" y="1" width="16" height="226"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
</view>
<connections>
<outlet property="scrollView" destination="jca-Kn-cO2" id="qAi-hi-PsC"/>
<outlet property="textFieldView" destination="bgQ-8i-Xgb" id="SbC-0r-xPR"/>
</connections>
</tabViewItem>
</tabViewItems>
</tabView>
<connections>
<outlet property="routerStatusViewOutlet" destination="xXg-Bt-RWR" id="L98-nF-i0n"/>
</connections>
</viewController>
<customObject id="d8g-wS-Zts" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-823" y="165.5"/>
</scene>
</scenes>
</document>

View File

@ -1,247 +0,0 @@
//
// SwiftMainDelegate.swift
// I2PLauncher
//
// Created by Mikal Villa on 17/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
import Cocoa
import MBPopup
extension Notification.Name {
static let killLauncher = Notification.Name("killStartupLauncher")
static let upgradeRouter = Notification.Name("upgradeRouter")
static let startRouter = Notification.Name("startRouter")
static let stopRouter = Notification.Name("stopRouter")
}
class Logger {
static func MLog<T>(level:Int32, _ object: T?,file:String = #file, function:String = #function, line:Int = #line) {
SBridge.logProxy(level, formattedMsg: "\(makeTag(function: function, file: file, line: line)) : \(String(describing: object))")
}
private static func makeTag(function: String, file: String, line: Int) -> String{
let url = NSURL(fileURLWithPath: file)
let className:String! = url.lastPathComponent == nil ? file: url.lastPathComponent!
return "\(String(describing: className)) \(function)[\(line)]"
}
}
@objc class SwiftApplicationDelegate : NSObject, NSApplicationDelegate, NSMenuDelegate {
let statusBarController = StatusBarController()
let sharedRouterMgmr = RouterManager.shared()
//let popupController: MBPopupController
//let serviceTableViewController = ServiceTableViewController()
//let editorTableViewController: EditorTableViewController
// Constructor, think of it like an early entrypoint.
override init() {
//self.popupController = MBPopupController(contentView: serviceTableViewController.contentView)
//self.editorTableViewController = serviceTableViewController.editorTableViewController
super.init()
if (!DetectJava.shared().isJavaFound()) {
DetectJava.shared().findIt()
if (!DetectJava.shared().isJavaFound()) {
Logger.MLog(level:3, "Could not find java....")
terminate("No java..")
}
}
let javaBinPath = DetectJava.shared().javaBinary
Logger.MLog(level:1, "".appendingFormat("Found java home = %@", javaBinPath!))
let (portIsNotTaken, _) = NetworkUtil.checkTcpPortForListen(port: 7657)
if (!portIsNotTaken) {
RouterProcessStatus.isRouterRunning = true
RouterProcessStatus.isRouterChildProcess = false
Logger.MLog(level:2, "I2P Router seems to be running")
} else {
RouterProcessStatus.isRouterRunning = false
Logger.MLog(level:2, "I2P Router seems to NOT be running")
}
} // End of init()
// A function which detects the current installed I2P router version
// NOTE: The return value tells if the function fails to detect I2P or not, and not if I2P is installed or not.
@objc func findInstalledI2PVersion() -> Bool {
let jarPath = Preferences.shared().i2pBaseDirectory + "/lib/i2p.jar"
let subCmd = Preferences.shared().javaCommandPath + "-cp " + jarPath + " net.i2p.CoreVersion"
let cmdArgs:[String] = ["-c", subCmd]
let sub:Subprocess = Subprocess.init(executablePath: "/bin/sh", arguments: cmdArgs)
let results:ExecutionResult = sub.execute(captureOutput: true)!
if (results.didCaptureOutput) {
if (results.status == 0) {
let i2pVersion = results.outputLines.first?.replace(target: "I2P Core version: ", withString: "")
Logger.MLog(level: 1, "".appendingFormat("I2P version detected: %@",i2pVersion ?? "Unknown"))
RouterProcessStatus.routerVersion = i2pVersion
RouterManager.shared().eventManager.trigger(eventName: "router_version", information: i2pVersion)
return true
} else {
Logger.MLog(level: 2, "Non zero exit code from subprocess while trying to detect version number!")
for line in results.errorsLines {
Logger.MLog(level: 2, line)
}
return false
}
} else {
Logger.MLog(level: 1, "Warning: Version Detection did NOT captured output")
}
return false
}
// Helper functions for the optional dock icon
func triggerDockIconShowHide(showIcon state: Bool) -> Bool {
var result: Bool
if state {
result = NSApp.setActivationPolicy(NSApplication.ActivationPolicy.regular)
} else {
result = NSApp.setActivationPolicy(NSApplication.ActivationPolicy.accessory)
}
return result
}
// Helper functions for the optional dock icon
func getDockIconStateIsShowing() -> Bool {
if NSApp.activationPolicy() == NSApplication.ActivationPolicy.regular {
return true
} else {
return false
}
}
@objc func updateServices() {
/*
serviceTableViewController.updateServices { [weak self] in
let title = self?.serviceTableViewController.generalStatus == .crashed ? "-.-" : "I2PLauncher"
self?.popupController.statusItem.title = title
if Preferences.shared().notifyOnStatusChange {
self?.serviceTableViewController.services.forEach { $0.notifyIfNecessary() }
}
}
*/
}
/**
*
* This is the swift "entrypoint". In C it would been "main(argc,argv)"
*
*/
@objc func applicationDidFinishLaunching() {
switch Preferences.shared().showAsIconMode {
case .bothIcon, .dockIcon:
if (!getDockIconStateIsShowing()) {
let _ = triggerDockIconShowHide(showIcon: true)
}
default:
if (getDockIconStateIsShowing()) {
let _ = triggerDockIconShowHide(showIcon: false)
}
}
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter { $0.bundleIdentifier == Identifiers.launcherApplicationBundleId }.isEmpty
// SMLoginItemSetEnabled(launcherAppId as CFString, true)
if isRunning {
DistributedNotificationCenter.default().post(name: .killLauncher, object: Bundle.main.bundleIdentifier!)
}
if (Preferences.shared().alsoStartFirefoxOnLaunch) {
DispatchQueue.delay(.seconds(120)) {
print("two minutes has passed, executing firefox manager")
let _ = FirefoxManager.shared().executeFirefox()
}
}
if #available(OSX 10.14, *) {
Appearance.addObserver(self)
} else {
//popupController.backgroundView.backgroundColor = .white
}
NSUserNotificationCenter.default.delegate = self
/*
popupController.statusItem.button?.image = NSImage(named:"StatusBarButtonImage")
popupController.statusItem.button?.toolTip = "I2P Launch Manager"
popupController.statusItem.button?.font = NSFont(name: "SF Mono Regular", size: 10) ?? NSFont.systemFont(ofSize: 12)
popupController.statusItem.length = 30
popupController.contentView.wantsLayer = true
popupController.contentView.layer?.masksToBounds = true
serviceTableViewController.setup()
popupController.willOpenPopup = { [weak self] _ in
if self?.editorTableViewController.hidden == true {
self?.serviceTableViewController.willOpenPopup()
} else {
self?.editorTableViewController.willOpenPopup()
}
}
popupController.didOpenPopup = { [weak self] in
if self?.editorTableViewController.hidden == false {
self?.editorTableViewController.didOpenPopup()
}
}
*/
}
@objc func listenForEvent(eventName: String, callbackActionFn: @escaping ((Any?)->()) ) {
RouterManager.shared().eventManager.listenTo(eventName: eventName, action: callbackActionFn )
}
@objc func triggerEvent(en: String, details: String? = nil) {
RouterManager.shared().eventManager.trigger(eventName: en, information: details)
}
@objc static func openLink(url: String) {
NSLog("Trying to open \(url)")
NSWorkspace.shared.open(NSURL(string: url)! as URL)
}
/**
*
* This function will execute when the launcher shuts down for some reason.
* Could be either OS or user triggered.
*
*/
@objc func applicationWillTerminate() {
// Shutdown stuff
if (Preferences.shared().stopRouterOnLauncherShutdown) {
RouterManager.shared().routerRunner.TeardownLaunchd()
sleep(2)
let status: AgentStatus? = RouterRunner.launchAgent?.status()
if status != .unloaded {
Logger.MLog(level:2, "Router service not yet stopped")
RouterManager.shared().routerRunner.TeardownLaunchd()
sleep(5)
}
}
}
@objc func terminate(_ why: Any?) {
Logger.MLog(level:2, "".appendingFormat("Stopping cause of ", why! as! CVarArg))
}
}
extension SwiftApplicationDelegate: NSUserNotificationCenterDelegate {
func userNotificationCenter(_ center: NSUserNotificationCenter, didActivate notification: NSUserNotification) {
//popupController.openPopup()
}
}
extension SwiftApplicationDelegate: AppearanceObserver {
func changeAppearance(to newAppearance: NSAppearance) {
//popupController.backgroundView.backgroundColor = newAppearance.isDarkMode ? .windowBackgroundColor : .white
}
}

View File

@ -1,182 +0,0 @@
//
// DetectJava.swift
// JavaI2PWrapper
//
// Created by Mikal Villa on 24/03/2018.
// Copyright © 2018 I2P. All rights reserved.
//
import Foundation
@objc class DetectJava : NSObject {
static var hasJRE : Bool = false
static var hasJDK : Bool = false
static var userWantJRE : Bool = false
static var userAcceptOracleEULA : Bool = false
private static var sharedDetectJava: DetectJava = {
let javaDetector = DetectJava()
// Configuration
// ...
return javaDetector
}()
// Initialization
private override init() {
super.init()
}
// MARK: - Accessors
class func shared() -> DetectJava {
return sharedDetectJava
}
@objc var javaBinary: String? {
didSet{
print("DetectJava.javaBinary was set to "+self.javaBinary!)
}
}
// Java checks
@objc var javaHome: String = ""{
//Called before the change
willSet(newValue){
print("DetectJava.javaHome will change from "+self.javaHome+" to "+newValue)
}
//Called after the change
didSet{
DetectJava.hasJRE = true
// javaHome will have a trailing \n which we remove to not break the cli
self.javaBinary = (self.javaHome+"/bin/java").replace(target: "\n", withString: "")
print("DetectJava.javaHome did change to "+self.javaHome)
//RouterManager.shared().eventManager.trigger(eventName: "java_found", information: self.javaHome)
}
};
private var testedEnv : Bool = false
private var testedJH : Bool = false
private var testedDfl : Bool = false
@objc func isJavaFound() -> Bool {
if !(self.javaHome.isEmpty) {
return true
}
return false
}
/**
*
* The order of the code blocks will decide the order, which will define the preffered.
*
**/
@objc func findIt() {
if (DetectJava.hasJRE) {
return
}
print("Start with checking environment variable")
self.checkJavaEnvironmentVariable()
if !(self.javaHome.isEmpty) {
DetectJava.hasJRE = true
return
}
print("Checking with the java_home util")
self.runJavaHomeCmd()
if !(self.javaHome.isEmpty) {
DetectJava.hasJRE = true
return
}
print("Checking default JRE install path")
self.checkDefaultJREPath()
if !(self.javaHome.isEmpty) {
DetectJava.hasJRE = true
return
}
}
@objc func getJavaViaLibexecBin() -> Array<String> {
return ["/usr/libexec/java_home", "-v", "1.7+", "--exec", "java"]
}
@objc func runJavaHomeCmd() {
let task = Process()
task.launchPath = "/usr/libexec/java_home"
task.arguments = ["-v", "1.7+"]
let pipe = Pipe()
task.standardOutput = pipe
let outHandle = pipe.fileHandleForReading
outHandle.waitForDataInBackgroundAndNotify()
var obs1 : NSObjectProtocol!
obs1 = NotificationCenter.default.addObserver(
forName: NSNotification.Name.NSFileHandleDataAvailable,
object: outHandle, queue: nil) {
notification -> Void in
let data = outHandle.availableData
if data.count > 0 {
let str = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
if (str != nil) {
let stringVal = str! as String
print("got output: "+stringVal)
self.javaHome = stringVal
}
// TODO: Found something, check it out
outHandle.waitForDataInBackgroundAndNotify()
} else {
print("EOF on stdout from process")
NotificationCenter.default.removeObserver(obs1)
// No JRE so far
}
}
var obs2 : NSObjectProtocol!
obs2 = NotificationCenter.default.addObserver(
forName: Process.didTerminateNotification,
object: task, queue: nil) {
notification -> Void in
print("terminated")
NotificationCenter.default.removeObserver(obs2)
}
task.launch()
task.waitUntilExit()
self.testedJH = true
}
@objc func checkDefaultJREPath() {
var isDir : ObjCBool = false;
let defaultJREPath = "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home"
if (FileManager.default.fileExists(atPath: defaultJREPath, isDirectory:&isDir) && isDir.boolValue) {
// Found it!!
self.javaHome = defaultJREPath
}
self.testedDfl = true
// No JRE here
}
@objc func getEnvironmentVar(_ name: String) -> String? {
guard let rawValue = getenv(name) else { return nil }
return String(utf8String: rawValue)
}
@objc func checkJavaEnvironmentVariable() {
let dic = ProcessInfo.processInfo.environment
if let javaHomeEnv = dic["JAVA_HOME"] {
// Maybe we got an JRE
print("Found JAVA_HOME with value:")
print(javaHomeEnv)
self.javaHome = javaHomeEnv
}
self.testedEnv = true
print("JAVA HOME is not set")
}
}

View File

@ -1,50 +0,0 @@
//
// NetworkUtil.swift
// I2PLauncher
//
// Created by Mikal Villa on 07/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Foundation
class NetworkUtil {
static func checkTcpPortForListen(host: String = "127.0.0.1", port: in_port_t = 7657) -> (Bool, descr: String){
let socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0)
if socketFileDescriptor == -1 {
return (false, "SocketCreationFailed, \(descriptionOfLastError())")
}
var addr = sockaddr_in()
let sizeOfSockkAddr = MemoryLayout<sockaddr_in>.size
addr.sin_len = __uint8_t(sizeOfSockkAddr)
addr.sin_family = sa_family_t(AF_INET)
addr.sin_port = Int(OSHostByteOrder()) == OSLittleEndian ? _OSSwapInt16(port) : port
addr.sin_addr = in_addr(s_addr: inet_addr(host))
addr.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0)
var bind_addr = sockaddr()
memcpy(&bind_addr, &addr, Int(sizeOfSockkAddr))
if Darwin.bind(socketFileDescriptor, &bind_addr, socklen_t(sizeOfSockkAddr)) == -1 {
let details = descriptionOfLastError()
release(socket: socketFileDescriptor)
return (false, "\(port), BindFailed, \(details)")
}
if listen(socketFileDescriptor, SOMAXCONN ) == -1 {
let details = descriptionOfLastError()
release(socket: socketFileDescriptor)
return (false, "\(port), ListenFailed, \(details)")
}
release(socket: socketFileDescriptor)
return (true, "\(port) is free for use")
}
static func release(socket: Int32) {
Darwin.shutdown(socket, SHUT_RDWR)
close(socket)
}
static func descriptionOfLastError() -> String {
return String(cString: UnsafePointer(strerror(errno)))
}
}

View File

@ -1,184 +0,0 @@
//
// RouterManager.swift
// I2PLauncher
//
// Created by Mikal Villa on 22/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
enum ErrorsInRouterMgmr: Swift.Error {
case NoJavaFound
case InvalidVersion
case NotFound
}
class RouterManager : NSObject {
// MARK: - Properties
static let packedVersion : String = NSDEF_I2P_VERSION
let eventManager = EventManager()
let routerRunner = RouterRunner()
var logViewStorage: NSTextStorage?
var lastRouterPid : String? = nil
private var canRouterStart : Bool = false
private static func handleRouterException(information:Any?) {
Logger.MLog(level:1,"event! - handle router exception")
Logger.MLog(level:1,information as? String)
}
private static func handleRouterStart(information:Any?) {
Logger.MLog(level:1,"event! - handle router start")
RouterProcessStatus.routerStartedAt = Date()
RouterProcessStatus.isRouterChildProcess = true
RouterProcessStatus.isRouterRunning = true
}
private static func handleRouterAlreadyStarted(information:Any?) {
Logger.MLog(level:1,"event! - handle router already started");
}
private static func handleRouterStop(information:Any?) {
Logger.MLog(level:1,"event! - handle router stop")
// TODO: Double check, check if pid stored exists
RouterProcessStatus.routerStartedAt = nil
RouterProcessStatus.isRouterChildProcess = false
RouterProcessStatus.isRouterRunning = false
}
private static func handleRouterPid(information:Any?) {
Logger.MLog(level:1,"".appendingFormat("event! - handle router pid: ", information as! String))
if (information != nil) {
let intPid = Int(information as! String)
print("Router pid is \(String(describing: intPid))..")
}
}
private static func handleRouterVersion(information:Any?) {
do {
Logger.MLog(level:1, "".appendingFormat("event! - handle router version: ", information as! String))
guard let currentVersion : String = information as? String else {
throw ErrorsInRouterMgmr.InvalidVersion
}
if (currentVersion == "Unknown") {
throw ErrorsInRouterMgmr.InvalidVersion
}
if (packedVersion.compare(currentVersion, options: .numeric) == .orderedDescending) {
Logger.MLog(level:1,"event! - router version: Packed version is newer, gonna re-deploy")
RouterManager.shared().eventManager.trigger(eventName: "router_must_upgrade", information: "got new version")
} else {
Logger.MLog(level:1,"event! - router version: No update needed")
RouterManager.shared().eventManager.trigger(eventName: "router_can_setup", information: "all ok")
}
} catch ErrorsInRouterMgmr.InvalidVersion {
// This is most likely due to an earlier extract got killed halfway or something
// Trigger an update
RouterManager.shared().eventManager.trigger(eventName: "router_must_upgrade", information: "invalid version found")
} catch {
// WTF
NSLog("Fatal error in RouterManager");
print(error)
}
}
private static var sharedRouterManager: RouterManager = {
let routerManager = RouterManager(detectJavaInstance: DetectJava.shared())
// Configuration
// ...
routerManager.updateState()
routerManager.eventManager.listenTo(eventName: "router_start", action: handleRouterStart)
routerManager.eventManager.listenTo(eventName: "router_stop", action: handleRouterStop)
routerManager.eventManager.listenTo(eventName: "router_pid", action: handleRouterPid)
routerManager.eventManager.listenTo(eventName: "router_version", action: handleRouterVersion)
routerManager.eventManager.listenTo(eventName: "router_exception", action: handleRouterException)
routerManager.eventManager.listenTo(eventName: "router_already_running", action: handleRouterAlreadyStarted)
routerManager.eventManager.listenTo(eventName: "router_can_start", action: routerManager.setLauncherReadyToStartRouter)
routerManager.eventManager.listenTo(eventName: "router_can_setup", action: routerManager.routerRunner.SetupAgent)
//routerManager.eventManager.listenTo(eventName: "launch_agent_running", action: routerManager.routerRunner.onLoadedAgent)
routerManager.eventManager.listenTo(eventName: "launch_agent_running", action: {
let agent = RouterRunner.launchAgent!
let agentStatus = agent.status()
switch agentStatus {
case .running(let pid):
DispatchQueue.main.async {
routerManager.eventManager.trigger(eventName: "router_start", information: String(pid))
routerManager.routerRunner.routerStatus.setRouterStatus(true)
routerManager.routerRunner.routerStatus.setRouterRanByUs(true)
routerManager.eventManager.trigger(eventName: "router_pid", information: String(pid))
}
break
default:
NSLog("wtf, agent status = \(agentStatus)")
break
}
})
return routerManager
}()
// MARK: -
let detectJava: DetectJava
private var routerInstance: I2PRouterTask?{
//Called after the change
didSet{
print("RouterManager.routerInstance did change to ", self.routerInstance ?? "null")
if (self.routerInstance != nil) {
RouterProcessStatus.isRouterRunning = (self.routerInstance?.isRouterRunning)!
}
}
};
// Initialization
private init(detectJavaInstance: DetectJava) {
self.detectJava = detectJavaInstance
}
// MARK: - Accessors
static func logInfo(format: String, messages: String...) {
//SBridge.sharedInstance().logMessageWithFormat(0, format, messages)func k(_ x: Int32, _ params: String...) {
/*withVaList(messages) {
genericLogger(x, $0)
}*/
}
class func shared() -> RouterManager {
return sharedRouterManager
}
func setLauncherReadyToStartRouter(_ information: Any?) {
self.canRouterStart = true
if (Preferences.shared().startRouterOnLauncherStart) {
// Trigger the actual start at launch time
print("Start router on launch preference is enabled - Starting")
self.routerRunner.StartAgent(information)
} else {
print("Start router on launch preference is disabled - Router ready when user are!")
SBridge.sendUserNotification("I2P Router Ready", formattedMsg: "The I2P Router is ready to be launched, however the autostart is disabled and require user input to start.")
}
}
func checkIfRouterCanStart() -> Bool {
return self.canRouterStart
}
func setRouterTask(router: I2PRouterTask) {
self.routerInstance = router
}
func getRouterTask() -> I2PRouterTask? {
return self.routerInstance
}
func updateState() {
self.routerInstance = SBridge.sharedInstance()?.currentRouterInstance
}
}

View File

@ -1,13 +0,0 @@
//
// RouterProcessStatus+ObjectiveC.swift
// I2PLauncher
//
// Created by Mikal Villa on 18/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
extension RouterProcessStatus {
}

View File

@ -1,58 +0,0 @@
//
// RouterProcessStatus.swift
// I2PLauncher
//
// Created by Mikal Villa on 18/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
import AppKit
@objc class RouterProcessStatus : NSObject {
/**
*
* Why the functions bellow? Because the Objective-C bridge is limited, it can't do Swift "static's" over it.
*
**/
@objc func setRouterStatus(_ isRunning: Bool = false) {
RouterProcessStatus.isRouterRunning = isRunning
}
@objc func setRouterRanByUs(_ ranByUs: Bool = false) {
RouterProcessStatus.isRouterChildProcess = ranByUs
}
@objc func getRouterIsRunning() -> Bool {
return RouterProcessStatus.isRouterRunning
}
@objc func getJavaHome() -> String {
return DetectJava.shared().javaHome
}
@objc func getJavaViaLibexec() -> Array<String> {
return DetectJava.shared().getJavaViaLibexecBin()
}
@objc func triggerEvent(en: String, details: String? = nil) {
RouterManager.shared().eventManager.trigger(eventName: en, information: details)
}
@objc func listenForEvent(eventName: String, callbackActionFn: @escaping ((Any?)->()) ) {
RouterManager.shared().eventManager.listenTo(eventName: eventName, action: callbackActionFn )
}
}
extension RouterProcessStatus {
static var isRouterRunning : Bool = (RouterManager.shared().getRouterTask() != nil)
static var isRouterChildProcess : Bool = (RouterManager.shared().getRouterTask() != nil)
static var routerVersion : String? = Optional.none
static var routerStartedAt : Date? = Optional.none
static var i2pDirectoryPath : String = Preferences.shared().i2pBaseDirectory
}

View File

@ -1,216 +0,0 @@
//
// RouterRunner.swift
// I2PLauncher
//
// Created by Mikal Villa on 18/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
class RouterRunner: NSObject {
var daemonPath: String?
var arguments: String?
static var launchAgent: LaunchAgent?
let routerStatus: RouterProcessStatus = RouterProcessStatus()
var currentRunningProcess: Subprocess?
var currentProcessResults: ExecutionResult?
let domainLabel = String(NSString(format: "%@.I2PRouter", APPDOMAIN))
let plistName = String(NSString(format: "%@.I2PRouter.plist", APPDOMAIN))
let appSupportPath = FileManager.default.urls(for: FileManager.SearchPathDirectory.applicationSupportDirectory, in: FileManager.SearchPathDomainMask.userDomainMask)
override init() {
super.init()
}
func SetupAgent() {
let agent = SetupAndReturnAgent()
RouterRunner.launchAgent = agent
}
typealias Async = (_ success: () -> Void, _ failure: (NSError) -> Void) -> Void
func retry(numberOfTimes: Int, _ sleepForS: UInt32, task: () -> Async, success: () -> Void, failure: (NSError) -> Void) {
task()(success, { error in
if numberOfTimes > 1 {
sleep(sleepForS)
retry(numberOfTimes: numberOfTimes - 1, sleepForS, task: task, success: success, failure: failure)
} else {
failure(error)
}
})
}
func SetupAndReturnAgent() -> LaunchAgent {
let applicationsSupportPath: URL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
let defaultStartupFlags:[String] = [
"-Djava.awt.headless=true",
"".appendingFormat("-Di2p.base.dir=%@", Preferences.shared().i2pBaseDirectory),
"".appendingFormat("-Dwrapper.logfile=%@/i2p/router.log", applicationsSupportPath.absoluteString),
"".appendingFormat("-Dwrapper.java.pidfile=%@/i2p/router.pid", applicationsSupportPath.absoluteString),
"-Dwrapper.logfile.loglevel=DEBUG", // TODO: Allow loglevel to be set from Preferences?
"-Dwrapper.console.loglevel=DEBUG",
"net.i2p.router.Router"
]
let javaCliArgs = Preferences.shared().javaCommandPath.splitByWhitespace()
self.daemonPath = javaCliArgs[0]
self.arguments = defaultStartupFlags.joined(separator: " ")
let basePath = Preferences.shared().i2pBaseDirectory
let jars = try! FileManager.default.contentsOfDirectory(atPath: basePath+"/lib")
var classpath:String = "."
for jar in jars {
if (jar.hasSuffix(".jar")) {
classpath += ":"+basePath+"/lib/"+jar
}
}
var cliArgs:[String] = [
self.daemonPath!,
]
cliArgs.append(contentsOf: javaCliArgs.dropFirst())
cliArgs.append(contentsOf: [
"-cp",
classpath,
])
// This allow java arguments to be passed from the settings
cliArgs.append(contentsOf: Preferences.shared().javaCommandOptions.splitByWhitespace())
cliArgs.append(contentsOf: defaultStartupFlags)
let agent = LaunchAgent(label: self.domainLabel,program: cliArgs)
agent.launchOnlyOnce = false
agent.keepAlive = false
agent.workingDirectory = basePath
agent.userName = NSUserName()
agent.standardErrorPath = NSString(format: "%@/router.stderr.log", Preferences.shared().i2pLogDirectory) as String
agent.standardOutPath = NSString(format: "%@/router.stdout.log", Preferences.shared().i2pLogDirectory) as String
agent.environmentVariables = [
"PATH": "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin",
"I2PBASE": basePath,
]
agent.disabled = false
agent.processType = ProcessType.adaptive
RouterRunner.launchAgent = agent
// NOTE: I suspect this is better to solve in the application
agent.runAtLoad = false //Preferences.shared().startRouterOnLauncherStart
agent.keepAlive = true
DispatchQueue(label: "background_starter").async {
do {
// TODO: Find a better way than sleep
try LaunchAgentManager.shared.write(agent, called: self.plistName)
sleep(1)
try LaunchAgentManager.shared.load(agent)
sleep(1)
let agentStatus = LaunchAgentManager.shared.status(agent)
switch agentStatus {
case .running:
break
case .loaded:
DispatchQueue.main.async {
RouterManager.shared().eventManager.trigger(eventName: "router_can_start", information: agent)
}
break
case .unloaded:
break
}
} catch {
DispatchQueue.main.async {
RouterManager.shared().eventManager.trigger(eventName: "router_setup_error", information: "\(error)")
}
}
}
return agent
}
func StartAgent(_ information:Any? = nil) {
if (RouterManager.shared().checkIfRouterCanStart()) {
let agent = RouterRunner.launchAgent ?? information as! LaunchAgent
DispatchQueue(label: "background_block").async {
LaunchAgentManager.shared.start(agent, { (proc) in
NSLog("Will call onLaunchdStarted")
})
}
} else {
SBridge.sendUserNotification("Whops! Please wait", formattedMsg: "I'm sorry but it's still something unresolved before we can start the I2P router. Please wait.")
}
}
func StopAgent(_ callback: @escaping () -> () = {}) {
let agentStatus = LaunchAgentManager.shared.status(RouterRunner.launchAgent!)
DispatchQueue(label: "background_block").async {
do {
switch agentStatus {
case .running:
// For now we need to use unload to stop it.
try LaunchAgentManager.shared.unload(RouterRunner.launchAgent!, { (proc) in
// Called when stop is actually executed
proc.waitUntilExit()
DispatchQueue.main.async {
RouterManager.shared().eventManager.trigger(eventName: "router_stop", information: "ok")
callback()
}
})
try LaunchAgentManager.shared.load(RouterRunner.launchAgent!)
break
case .unloaded:
// Seems it sometimes get unloaded on stop, we load it again.
try! LaunchAgentManager.shared.load(RouterRunner.launchAgent!)
return
default: break
}
} catch {
NSLog("Error \(error)")
}
}
}
func SetupLaunchd() {
do {
try LaunchAgentManager.shared.write(RouterRunner.launchAgent!, called: self.plistName)
try LaunchAgentManager.shared.load(RouterRunner.launchAgent!)
} catch {
RouterManager.shared().eventManager.trigger(eventName: "router_exception", information: error)
}
}
func TeardownLaunchd() {
/*let status = LaunchAgentManager.shared.status(RouterRunner.launchAgent!)
switch status {
case .running:*/
do {
// Unload no matter previous state!
try LaunchAgentManager.shared.unload(RouterRunner.launchAgent!)
let plistPath = NSHomeDirectory()+"/Library/LaunchAgents/"+self.plistName
sleep(1)
if FileManager.default.fileExists(atPath: plistPath) {
try FileManager.default.removeItem(atPath: plistPath)
}
} catch LaunchAgentManagerError.urlNotSet(label: self.domainLabel) {
Logger.MLog(level:3, "URL not set in launch agent")
} catch {
Logger.MLog(level:3, "".appendingFormat("Error in launch agent: %s", error as CVarArg))
RouterManager.shared().eventManager.trigger(eventName: "router_exception", information: error)
}
/* break
default: break
}
*/
}
}

View File

@ -1,42 +0,0 @@
//
// HttpTunnelService.swift
// I2PLauncher
//
// Created by Mikal Villa on 03/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Foundation
import Kanna
class HttpTunnelService : Service {
let dataURL: URL = URL(string: "http://127.0.0.1:7657/i2ptunnel/")!
override func updateStatus(callback: @escaping (BaseService) -> Void) {
URLSession.shared.dataTask(with: dataURL) { [weak self] data, _, error in
guard let strongSelf = self else { return }
defer { callback(strongSelf) }
guard let doc = try? HTML(html: data!, encoding: .utf8) else { return /*strongSelf._fail("Couldn't parse response")*/ }
_ = doc.css("table#clientTunnels > tr.tunnelProperties > td.tunnelStatus").first
let maxStatus: ServiceStatus = .started
strongSelf.status = maxStatus
switch maxStatus {
case .waiting:
strongSelf.message = "Waiting on router"
case .started:
strongSelf.message = "Started"
case .stopped:
strongSelf.message = "Stopped"
case .undetermined:
strongSelf.message = "Undetermined"
default:
strongSelf.message = "Undetermined" /*downComponents.map { $0["name"] as? String }.compactMap { $0 }.joined(separator: ", ")*/
}
}.resume()
}
}

View File

@ -1,23 +0,0 @@
//
// I2PRouterService.swift
// I2PLauncher
//
// Created by Mikal Villa on 03/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Foundation
class I2PRouterService : Service {
override func updateStatus(callback: @escaping (BaseService) -> Void) {
//guard let strongSelf = self else { return }
defer { callback(self) }
DispatchQueue.main.async {
self.status = ServiceStatus(rawValue: 0)!
self.message = "Dead"
}
}
}

View File

@ -1,20 +0,0 @@
//
// IrcTunnelService.swift
// I2PLauncher
//
// Created by Mikal Villa on 03/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Foundation
class IrcTunnelService : Service {
override func updateStatus(callback: @escaping (BaseService) -> Void) {
defer { callback(self) }
self.status = ServiceStatus(rawValue: 0)!
self.message = "Dead"
}
}

View File

@ -1,132 +0,0 @@
//
// Service.swift
// I2PLauncher
//
// Created by Mikal Villa on 12/04/2019.
// Copyright © 2019 The I2P Project. All rights reserved.
//
import Foundation
public enum ServiceStatus: Int, Comparable {
case undetermined
case waiting
case started
case notice
case killed
case crashed
case stopped
case restarting
public static func < (lhs: ServiceStatus, rhs: ServiceStatus) -> Bool {
return lhs.rawValue < rhs.rawValue
}
}
protocol ComparableStatus: Comparable {
var serviceStatus: ServiceStatus { get }
}
extension ComparableStatus {
public static func < (lhs: Self, rhs: Self) -> Bool {
return lhs.serviceStatus < rhs.serviceStatus
}
}
typealias Service = BaseService & RequiredServiceProperties
protocol RequiredServiceProperties {
var name: String { get }
//var url: URL { get }
}
extension RequiredServiceProperties {
// Default implementation of the property `name` is to return the class name
var name: String { return "\(type(of: self))" }
}
public class BaseService {
public var status: ServiceStatus = .undetermined {
didSet {
if oldValue == .undetermined || status == .undetermined || oldValue == status {
self.shouldNotify = false
} else if Preferences.shared().notifyOnStatusChange {
self.shouldNotify = true
}
}
}
var message: String = "Loading…"
var shouldNotify = false
public static func all() -> [BaseService] {
guard let servicesPlist = Bundle.main.path(forResource: "RouterServices", ofType: "plist"),
let services = NSDictionary(contentsOfFile: servicesPlist)?["services"] as? [String] else {
fatalError("The RouterServices.plist file does not exist. The build phase script might have failed.")
}
return services.map(BaseService.named).compactMap { $0 }
}
static func named(_ name: String) -> BaseService? {
return (NSClassFromString("I2PLauncher.\(name)") as? Service.Type)?.init()
}
public required init() {}
public func updateStatus(callback: @escaping (BaseService) -> Void) {}
func _fail(_ error: Error?) {
self.status = .undetermined
self.message = error.debugDescription// ?? "Unexpected error"
}
func _fail(_ message: String) {
self.status = .undetermined
self.message = message
}
func notifyIfNecessary() {
guard let realSelf = self as? Service else { fatalError("BaseService should not be used directly.") }
guard shouldNotify else { return }
self.shouldNotify = false
let notification = NSUserNotification()
let possessiveS = realSelf.name.hasSuffix("s") ? "'" : "'s"
notification.title = "\(realSelf.name)\(possessiveS) status has changed"
notification.informativeText = message
NSUserNotificationCenter.default.deliver(notification)
}
}
extension BaseService: Equatable {
public static func == (lhs: BaseService, rhs: BaseService) -> Bool {
guard
let lhs = lhs as? Service,
let rhs = rhs as? Service
else {
fatalError("BaseService should not be used directly.")
}
return lhs.name == rhs.name
}
}
extension BaseService: Comparable {
public static func < (lhs: BaseService, rhs: BaseService) -> Bool {
guard
let lhs = lhs as? Service,
let rhs = rhs as? Service
else {
fatalError("BaseService should not be used directly.")
}
let sameStatus = lhs.status == rhs.status
let differentStatus =
lhs.status != .started && lhs.status != .notice
&& rhs.status == .started || rhs.status == .notice
return ((lhs.name < rhs.name) && sameStatus) || differentStatus
}
}

View File

@ -1,69 +0,0 @@
//
// LaunchAgent+Status.swift
// I2PLauncher
//
// Created by Mikal Villa on 05/10/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
public enum AgentStatus: Equatable {
case running(pid: Int)
case loaded
case unloaded
public static func ==(lhs: AgentStatus, rhs: AgentStatus) -> Bool {
switch (lhs, rhs) {
case ( let .running(lhpid), let .running(rhpid) ):
return lhpid == rhpid
case (.loaded, .loaded):
return true
case (.unloaded, .unloaded):
return true
default:
return false
}
}
}
extension LaunchAgent {
/// Run `launchctl start` on the agent
///
/// Check the status of the job with `.status()`
public func start(_ callback: ((Process) -> Void)? = nil ) {
LaunchAgentManager.shared.start(self, callback)
}
/// Run `launchctl stop` on the agent
///
/// Check the status of the job with `.status()`
public func stop(_ callback: ((Process) -> Void)? = nil ) {
LaunchAgentManager.shared.stop(self, callback)
}
/// Run `launchctl load` on the agent
///
/// Check the status of the job with `.status()`
public func load() throws {
try LaunchAgentManager.shared.load(self)
}
/// Run `launchctl unload` on the agent
///
/// Check the status of the job with `.status()`
public func unload() throws {
try LaunchAgentManager.shared.unload(self)
}
/// Retreives the status of the LaunchAgent from `launchctl`
///
/// - Returns: the agent's status
public func status() -> AgentStatus {
return LaunchAgentManager.shared.status(self)
}
}

View File

@ -1,124 +0,0 @@
//
// LaunchAgent.swift
// I2PLauncher
//
// Created by Mikal Villa on 05/10/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
public enum ProcessType: String, Codable {
case standard = "Standard"
case background = "Background"
case adaptive = "Adaptive"
case interactive = "Interactive"
}
public class LaunchAgent: Codable {
public var url: URL? = nil
// Basic Properties
public var label: String
public var disabled: Bool? = nil
public var enableGlobbing: Bool? = nil
public var program: String? = nil {
didSet {
if program != nil {
programArguments = nil
}
}
}
public var programArguments: [String]? = nil {
didSet {
guard let args = programArguments else {
return
}
if args.count == 1 {
self.program = args.first
programArguments = nil
} else {
program = nil
}
}
}
public var processType: ProcessType? = nil
// Program
public var workingDirectory: String? = nil
public var standardOutPath: String? = nil
public var standardErrorPath: String? = nil
public var environmentVariables: [String: String]? = nil
// Run Conditions
public var runAtLoad: Bool? = nil
public var startInterval: Int? = nil
public var onDemand: Bool? = nil
public var keepAlive: Bool? = nil
public var watchPaths: [String]? = nil
// Security
public var umask: Int? = nil
// System Daemon Security
public var groupName: String? = nil
public var userName: String? = nil
public var rootDirectory: String? = nil
// Run Constriants
public var launchOnlyOnce: Bool? = nil
public var limitLoadToSessionType: [String]? = nil
public init(label: String, program: [String]) {
self.label = label
if program.count == 1 {
self.program = program.first
} else {
self.programArguments = program
}
}
public convenience init(label: String, program: String...) {
self.init(label: label, program: program)
}
public enum CodingKeys: String, CodingKey {
case label = "Label"
case disabled = "Disabled"
case program = "Program"
case programArguments = "ProgramArguments"
// Program
case workingDirectory = "WorkingDirectory"
case standardOutPath = "StandardOutPath"
case standardErrorPath = "StandardErrorPath"
case environmentVariables = "EnvironmentVariables"
// Run Conditions
case runAtLoad = "RunAtLoad"
case startInterval = "StartInterval"
case onDemand = "OnDemand"
case keepAlive = "KeepAlive"
case watchPaths = "WatchPaths"
// Security
case umask = "Umask"
case groupName = "GroupName"
case userName = "UserName"
case rootDirectory = "RootDirectory"
// Run Constriants
case launchOnlyOnce = "LaunchOnlyOnce"
case limitLoadToSessionType = "LimitLoadToSessionType"
// Process type
case processType = "ProcessType"
}
}

View File

@ -1,203 +0,0 @@
//
// LaunchAgentManager.swift
// I2PLauncher
//
// Created by Mikal Villa on 07/10/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
public enum LaunchAgentManagerError: Swift.Error {
case urlNotSet(label: String)
public var localizedDescription: String {
switch self {
case .urlNotSet(let label):
return "The URL is not set for agent \(label)"
}
}
}
public class LaunchAgentManager {
public static let shared = LaunchAgentManager()
static let launchctl = "/bin/launchctl"
var lastState: AgentStatus?
let encoder = PropertyListEncoder()
let decoder = PropertyListDecoder()
init() {
encoder.outputFormat = .xml
}
func launchAgentsURL() throws -> URL {
let library = try FileManager.default.url(for: .libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
return library.appendingPathComponent("LaunchAgents")
}
public func read(agent called: String) throws -> LaunchAgent {
let url = try launchAgentsURL().appendingPathComponent(called)
return try read(from: url)
}
public func read(from url: URL) throws -> LaunchAgent {
return try decoder.decode(LaunchAgent.self, from: Data(contentsOf: url))
}
public func write(_ agent: LaunchAgent, called: String) throws {
let url = try launchAgentsURL().appendingPathComponent(called)
try write(agent, to: url)
}
public func write(_ agent: LaunchAgent, to url: URL) throws {
try encoder.encode(agent).write(to: url)
agent.url = url
}
public func setURL(for agent: LaunchAgent) throws {
let contents = try FileManager.default.contentsOfDirectory(
at: try launchAgentsURL(),
includingPropertiesForKeys: nil,
options: [.skipsPackageDescendants, .skipsHiddenFiles, .skipsSubdirectoryDescendants]
)
contents.forEach { url in
let testAgent = try? self.read(from: url)
if agent.label == testAgent?.label {
agent.url = url
return
}
}
}
}
extension LaunchAgentManager {
/// Run `launchctl start` on the agent
///
/// Check the status of the job with `.status(_: LaunchAgent)`
public func start(_ agent: LaunchAgent, _ termHandler: ((Process) -> Void)? = nil ) {
let arguments = ["start", agent.label]
let proc = Process.launchedProcess(launchPath: LaunchAgentManager.launchctl, arguments: arguments)
if ((termHandler) != nil) {
proc.terminationHandler = termHandler
}
}
/// Run `launchctl stop` on the agent
///
/// Check the status of the job with `.status(_: LaunchAgent)`
public func stop(_ agent: LaunchAgent, _ termHandler: ((Process) -> Void)? = nil ) {
let arguments = ["stop", agent.label]
let proc = Process.launchedProcess(launchPath: LaunchAgentManager.launchctl, arguments: arguments)
if ((termHandler) != nil) {
proc.terminationHandler = termHandler
}
}
/// Run `launchctl load` on the agent
///
/// Check the status of the job with `.status(_: LaunchAgent)`
public func load(_ agent: LaunchAgent, _ termHandler: ((Process) -> Void)? = nil ) throws {
guard let agentURL = agent.url else {
throw LaunchAgentManagerError.urlNotSet(label: agent.label)
}
let arguments = ["load", agentURL.path]
let proc = Process.launchedProcess(launchPath: LaunchAgentManager.launchctl, arguments: arguments)
if ((termHandler) != nil) {
proc.terminationHandler = termHandler
}
}
/// Run `launchctl unload` on the agent
///
/// Check the status of the job with `.status(_: LaunchAgent)`
public func unload(_ agent: LaunchAgent, _ termHandler: ((Process) -> Void)? = nil ) throws {
guard let agentURL = agent.url else {
throw LaunchAgentManagerError.urlNotSet(label: agent.label)
}
let arguments = ["unload", agentURL.path]
let proc = Process.launchedProcess(launchPath: LaunchAgentManager.launchctl, arguments: arguments)
if ((termHandler) != nil) {
proc.terminationHandler = termHandler
}
}
/// Retreives the status of the LaunchAgent from `launchctl`
///
/// - Returns: the agent's status
public func status(_ agent: LaunchAgent) -> AgentStatus {
let launchctlTask = Process()
let grepTask = Process()
let cutTask = Process()
launchctlTask.launchPath = "/bin/launchctl"
launchctlTask.arguments = ["list"]
grepTask.launchPath = "/usr/bin/grep"
grepTask.arguments = [agent.label]
cutTask.launchPath = "/usr/bin/cut"
cutTask.arguments = ["-f1"]
let pipeLaunchCtlToGrep = Pipe()
launchctlTask.standardOutput = pipeLaunchCtlToGrep
grepTask.standardInput = pipeLaunchCtlToGrep
let pipeGrepToCut = Pipe()
grepTask.standardOutput = pipeGrepToCut
cutTask.standardInput = pipeGrepToCut
let pipeCutToFile = Pipe()
cutTask.standardOutput = pipeCutToFile
let fileHandle: FileHandle = pipeCutToFile.fileHandleForReading as FileHandle
launchctlTask.launch()
grepTask.launch()
cutTask.launch()
let data = fileHandle.readDataToEndOfFile()
let stringResult = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .newlines) ?? ""
let em = RouterManager.shared().eventManager
switch stringResult {
case "-":
if (self.lastState != AgentStatus.loaded) {
self.lastState = AgentStatus.loaded
em.trigger(eventName: "launch_agent_loaded")
}
return .loaded
case "":
if (self.lastState != AgentStatus.unloaded) {
self.lastState = AgentStatus.unloaded
em.trigger(eventName: "launch_agent_unloaded")
}
return .unloaded
default:
if (self.lastState != AgentStatus.running(pid: Int(stringResult)!)) {
self.lastState = AgentStatus.running(pid: Int(stringResult)!)
em.trigger(eventName: "launch_agent_running")
}
return .running(pid: Int(stringResult)!)
}
}
}

View File

@ -1,12 +0,0 @@
//
// AppleStuffExceptionHandler.h
// I2PLauncher
//
// Created by Mikal Villa on 17/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
#import <Foundation/Foundation.h>
extern NSException* __nullable AppleStuffExecuteWithPossibleExceptionInBlock(dispatch_block_t _Nonnull block);

View File

@ -1,22 +0,0 @@
//
// AppleStuffExceptionHandler.m
// I2PLauncher
//
// Created by Mikal Villa on 17/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
#import "AppleStuffExceptionHandler.h"
NSException* __nullable AppleStuffExecuteWithPossibleExceptionInBlock(dispatch_block_t _Nonnull block) {
@try {
if (block != nil) {
block();
}
}
@catch (NSException *exception) {
return exception;
}
return nil;
}

Some files were not shown because too many files have changed in this diff Show More