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
28
build.xml
@ -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 -->
|
||||
|
@ -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/
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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"
|
||||
)
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -1 +0,0 @@
|
||||
libraryDependencies += "org.xeustechnologies" % "jcl-core" % "2.8"
|
@ -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.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
@ -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"))
|
||||
|
||||
}
|
@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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]
|
||||
}
|
36
launchers/macosx/.vscode/settings.json
vendored
@ -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"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 1.7 KiB |
@ -1,3 +0,0 @@
|
||||
github 'sparkle-project/Sparkle' == 1.21.0
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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>
|
@ -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 {}
|
||||
*/
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 27 KiB |
@ -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"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 2.8 KiB |
@ -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"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 59 KiB |
@ -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"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 5.7 KiB |
@ -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"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 131 KiB |
@ -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"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 131 KiB |
@ -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"
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 5.7 KiB |
@ -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"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 2.1 KiB |
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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"
|
||||
}
|
@ -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>
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 */
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 */
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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 */
|
@ -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>
|
@ -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>
|
@ -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 & 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>
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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)))
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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 {
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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"
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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"
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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)!)
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|