├── .gitignore ├── LICENSE ├── README.md ├── akka ├── .gitignore ├── project │ ├── Build.scala │ ├── build.properties │ └── plugins.sbt └── src │ └── main │ ├── resources │ └── application.conf │ └── scala │ └── edu │ └── agh │ └── mindmapd │ ├── Main.scala │ ├── actors │ ├── MapsSupervisor.scala │ ├── MindMap.scala │ ├── Supervisor.scala │ └── http │ │ ├── Poller.scala │ │ ├── Service.scala │ │ └── Updater.scala │ ├── extensions │ ├── CustomJsonFormats.scala │ └── Settings.scala │ ├── json │ ├── PollResponse.scala │ ├── UpdateRequest.scala │ └── UpdateResponse.scala │ ├── model │ └── MindNode.scala │ └── storage │ ├── SquerylStorage.scala │ └── Storage.scala ├── android ├── .gitignore ├── project │ ├── Build.scala │ ├── build.properties │ └── plugins.sbt └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── .keep │ ├── java │ └── edu │ │ └── agh │ │ └── mindmap │ │ └── util │ │ └── ExplicitNull.java │ ├── res │ ├── drawable-hdpi │ │ ├── icon_close.png │ │ ├── icon_create.png │ │ └── icon_import.png │ ├── layout │ │ ├── main.xml │ │ ├── map.xml │ │ ├── mind_node.xml │ │ ├── recent_list.xml │ │ └── recent_list_item.xml │ └── values │ │ ├── ids.xml │ │ └── strings.xml │ └── scala │ └── edu │ └── agh │ └── mindmap │ ├── activity │ └── MainActivity.scala │ ├── component │ ├── ArrowView.scala │ ├── HorizontalScrollViewWithPropagation.scala │ └── NodeView.scala │ ├── fragment │ ├── MapFragment.scala │ └── MapListFragment.scala │ ├── model │ ├── DBUser.scala │ ├── MindMap.scala │ └── MindNode.scala │ └── util │ ├── DBHelper.scala │ ├── Exporter.scala │ ├── Importer.scala │ ├── JsonMessages.scala │ ├── MapPainter.scala │ ├── Refresher.scala │ └── Synchronizer.scala └── docs ├── .gitignore ├── bsc-slides ├── android-akka-spray.tex ├── beamerthemeAGH.sty ├── collaboration.tex ├── graphics-mockup-map.png ├── graphics-screenshot-big-map.png ├── graphics-screenshot-list.png ├── main.tex ├── mind-map.png ├── mind-maps.tex ├── mindmapping_screenshot.png ├── our-app.tex ├── page.png ├── problems.tex ├── recreation-1.pdf ├── recreation-2.pdf ├── recreation-3.pdf ├── recreation-4.pdf ├── recreation.vsdx ├── subtree-recreation.tex ├── summary.tex ├── synchronization.tex ├── titlepage.png ├── titlepagepl.png └── xmind.pdf └── bsc-thesis ├── abstract.tex ├── akkaio-app.tex ├── android-akka-communication.tex ├── android-app.tex ├── bibliography.bib ├── bsc.cls ├── code-repo.tex ├── component-diagram.pdf ├── data-representation.tex ├── graphics-erd.pdf ├── graphics-erd.vsd ├── graphics-mockup-list.png ├── graphics-mockup-map.png ├── graphics-screenshot-big-map.png ├── graphics-screenshot-import.png ├── graphics-screenshot-list.png ├── graphics-spray-benchmark.png ├── impl-intro.tex ├── implementation.tex ├── introduction.tex ├── main-components.tex ├── main.tex ├── problems-and-solutions.tex ├── project.tex ├── requirements.tex ├── subtree-recreation-algo.tex ├── summary.tex ├── theory-server.tex ├── theory.tex └── xmind-import.tex /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | agh-mindmap 2 | =========== 3 | 4 | Proof-of-concept mindmapping for Android with XMind support and real-time collaboration and synchronization (and conflict resolution) for changes introduced offline. 5 | 6 | How to run it 7 | ------------- 8 | 9 | Connect your phone via USB, turn on USB debugging and: 10 | 11 | $ git clone https://github.com/michalrus/agh-mindmap.git 12 | $ cd agh-mindmap/android/ 13 | $ sbt run 14 | 15 | ... where `sbt` is [Scala Build Tool](http://www.scala-sbt.org/0.13.1/docs/Getting-Started/Setup.html). 16 | 17 | License 18 | ------- 19 | 20 | Licensed under [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). 21 | -------------------------------------------------------------------------------- /akka/.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2013 Katarzyna Szawan 3 | # and Michał Rus 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # SBT and IntelliJ Idea files 19 | 20 | /.idea* 21 | /.history 22 | /target/ 23 | /project/target/ 24 | /project/project/target/ 25 | 26 | # dev notes 27 | 28 | /_/ 29 | -------------------------------------------------------------------------------- /akka/project/Build.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import sbt._ 19 | import Keys._ 20 | 21 | import sbtassembly.Plugin._ 22 | import AssemblyKeys._ 23 | 24 | object Build extends Build { 25 | 26 | // --- root 27 | 28 | lazy val root = Project(id = "root", base = file(".")).settings( 29 | assemblySettings ++ Seq( 30 | 31 | name := "agh-mindmapd", 32 | version := "1.0", 33 | scalaVersion := "2.10.3", 34 | 35 | javacOptions in Compile ++= Seq("-Xlint:deprecation"), 36 | scalacOptions in Compile ++= Seq("-feature", "-deprecation", "-Yno-adapted-args", "-Ywarn-all", "-Xfatal-warnings", 37 | "-Xlint", "-Ywarn-value-discard", "-Ywarn-numeric-widen", "-Ywarn-dead-code", "-unchecked"), 38 | 39 | resolvers += "michalrus.com repo" at "https://maven.michalrus.com/", 40 | addCompilerPlugin("org.brianmckenna" % "wartremover" % "0.6-SNAPSHOT" cross CrossVersion.full), 41 | scalacOptions += "-P:wartremover:traverser:org.brianmckenna.wartremover.warts.Unsafe", 42 | 43 | resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/", 44 | libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.2.3", 45 | 46 | resolvers += "spray repo" at "http://repo.spray.io", 47 | libraryDependencies += "io.spray" % "spray-can" % "1.2.0", 48 | libraryDependencies += "io.spray" % "spray-routing" % "1.2.0", 49 | libraryDependencies += "io.spray" %% "spray-json" % "1.2.5", 50 | 51 | libraryDependencies += "org.squeryl" %% "squeryl" % "0.9.5-6", 52 | libraryDependencies += "postgresql" % "postgresql" % "8.4-701.jdbc4" 53 | 54 | ): _*) 55 | 56 | } 57 | -------------------------------------------------------------------------------- /akka/project/build.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2013 Katarzyna Szawan 3 | # and Michał Rus 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | sbt.version=0.13.1 19 | -------------------------------------------------------------------------------- /akka/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2013 Katarzyna Szawan 3 | // and Michał Rus 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.5.2") 19 | 20 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.10.1") 21 | -------------------------------------------------------------------------------- /akka/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2013 Katarzyna Szawan 3 | # and Michał Rus 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | akka { 19 | # loglevel = debug 20 | # actor.debug.lifecycle = on 21 | } 22 | 23 | # this *HAS TO* be larger than `mindmapd.poll-timeout`! 24 | spray.can.server.request-timeout = 25 seconds 25 | 26 | mindmapd { 27 | is-production = false 28 | hostname = "0.0.0.0" 29 | port = 8090 30 | 31 | timeout { 32 | poll = 20 seconds 33 | maps-response = 0.5 seconds 34 | update = 3 seconds 35 | internal-message = 1 seconds 36 | } 37 | 38 | squeryl { 39 | driver = org.postgresql.Driver 40 | url = "jdbc:postgresql://localhost/agh_mindmap" 41 | user = "agh_mindmap" 42 | password = "rsega0eyTj2MdJ0GVNKx" 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /akka/src/main/scala/edu/agh/mindmapd/Main.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package edu.agh.mindmapd 19 | 20 | object Main extends App { 21 | 22 | akka.Main main Array(classOf[edu.agh.mindmapd.actors.Supervisor].getCanonicalName) 23 | 24 | } 25 | -------------------------------------------------------------------------------- /akka/src/main/scala/edu/agh/mindmapd/actors/MapsSupervisor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package edu.agh.mindmapd.actors 19 | 20 | import akka.actor.{ActorRef, Props, Actor} 21 | import java.util.UUID 22 | import edu.agh.mindmapd.storage.SquerylStorage 23 | import edu.agh.mindmapd.extensions.Settings 24 | 25 | object MapsSupervisor { 26 | 27 | case class Find(uuid: UUID) 28 | case class Subscribe(whom: ActorRef, since: Long) 29 | case class Unsubscribe(whom: ActorRef) 30 | 31 | def props = Props(classOf[MapsSupervisor]) 32 | 33 | } 34 | 35 | class MapsSupervisor extends Actor { 36 | import MapsSupervisor._ 37 | 38 | // create MindMap actors for mind maps already existing in Storage 39 | val _ = SquerylStorage allMaps Settings(context.system) map (refFor(_, Map.empty)) 40 | 41 | def receive = initial(Map.empty) 42 | 43 | def initial(subscribers: Map[ActorRef, Long]): Receive = { 44 | case Find(uuid) => sender ! refFor(uuid, subscribers) 45 | 46 | case Subscribe(whom, since) => 47 | context.children foreach (_ ! MindMap.Subscribe(whom, since)) 48 | context become initial(subscribers + (whom -> since)) 49 | 50 | case Unsubscribe(whom) => 51 | context.children foreach (_ ! MindMap.Unsubscribe(whom)) 52 | context become initial(subscribers - whom) 53 | } 54 | 55 | def refFor(uuid: UUID, subscribers: Map[ActorRef, Long]): ActorRef = { 56 | val s = uuid.toString 57 | context child s match { 58 | case Some(ref) => ref 59 | case _ => 60 | val ref = context actorOf (MindMap.props(uuid), s) 61 | for ((whom, since) <- subscribers) ref ! MindMap.Subscribe(whom, since) 62 | ref 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /akka/src/main/scala/edu/agh/mindmapd/actors/MindMap.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package edu.agh.mindmapd.actors 19 | 20 | import akka.actor.{ActorRef, Props, Actor} 21 | import java.util.UUID 22 | import edu.agh.mindmapd.model.MindNode 23 | import edu.agh.mindmapd.extensions.Settings 24 | import edu.agh.mindmapd.storage.SquerylStorage 25 | 26 | object MindMap { 27 | 28 | case class Update(lastServerTime: Long, nodes: List[MindNode]) 29 | case class UpdateResult(orphanNodes: List[UUID]) 30 | case class Subscribe(whom: ActorRef, since: Long) 31 | case class Unsubscribe(whom: ActorRef) 32 | 33 | case class Changed(node: MindNode) 34 | 35 | def props(mapUuid: UUID) = Props(classOf[MindMap], mapUuid) 36 | 37 | } 38 | 39 | class MindMap(mapUuid: UUID) extends Actor { 40 | import MindMap._ 41 | 42 | val storage = SquerylStorage(mapUuid, Settings(context.system)) 43 | 44 | def receive = initial(Set.empty) 45 | 46 | def initial(subscribers: Set[ActorRef]): Receive = { 47 | case Subscribe(whom, since) => 48 | storage findSince (since, limit = 50) foreach (whom ! Changed(_)) 49 | context become initial(subscribers + whom) 50 | 51 | case Unsubscribe(whom) => 52 | context become initial(subscribers - whom) 53 | 54 | case Update(atTime, updates) => 55 | orphanNodes(updates) match { 56 | case orphans if orphans.nonEmpty => 57 | sender ! UpdateResult(orphans.toList) 58 | case _ => 59 | mergeIn(updates, atTime, subscribers) 60 | sender ! UpdateResult(orphanNodes = Nil) 61 | } 62 | } 63 | 64 | def mergeIn(updates: List[MindNode], atTime: Long, subscribers: Set[ActorRef]) = 65 | updates foreach { suggestion => 66 | val update = merged(suggestion, atTime). 67 | copy(cloudTime = System.currentTimeMillis) 68 | 69 | storage insertOrReplace update 70 | subscribers foreach (_ ! Changed(update)) 71 | } 72 | 73 | def merged(fromClient: MindNode, atTime: Long): MindNode = 74 | storage find fromClient.uuid match { 75 | case None => fromClient // new node creation 76 | 77 | case Some(existing) => // `existing` node update 78 | (existing.content, fromClient.content) match { 79 | 80 | case (Some(o), Some(n)) => // content change 81 | if (existing.cloudTime > atTime && o != n) // *** CONFLICT!!! *** 82 | fromClient.copy(content = Some(o + "\n" + n), hasConflict = true) 83 | else // normal content change 84 | fromClient.copy(hasConflict = false) 85 | 86 | case (Some(o), None) => // subtree deletion 87 | if (storage wasAnyChangedInSubtree (existing, atTime)) { 88 | storage touchTimesOfSubtree existing.uuid 89 | existing.copy(hasConflict = false) 90 | } else { 91 | storage deleteChildrenOf existing.uuid 92 | fromClient.copy(hasConflict = false) 93 | } 94 | 95 | case (None, Some(n)) => // recreation? 96 | fromClient.copy(hasConflict = false) 97 | 98 | case (None, None) => // can happen rarely, not so important 99 | fromClient.copy(hasConflict = false) 100 | 101 | } 102 | 103 | } 104 | 105 | def orphanNodes(potentialUpdates: List[MindNode]): Set[UUID] = { 106 | val request = (potentialUpdates map (n => n.uuid -> n)).toMap 107 | 108 | def toOrphan(node: MindNode): Option[UUID] = { 109 | if (storage contains node.uuid) 110 | None // cool, modifying already existing node 111 | else node.parent match { 112 | case Some(parent) => 113 | if ((storage find parent exists (_.content.isDefined)) || (request contains parent)) 114 | None // cool, adding a new child to a known and not already deleted parent 115 | else 116 | Some(node.uuid) // not cool, no parent known for this node :( 117 | case None => 118 | if (storage.hasNoNodesYet) 119 | None // cool, new map creation 120 | else 121 | Some(node.uuid) // not cool, should not happen (adding a second root?!?!) 122 | } 123 | } 124 | 125 | request.values.toSet flatMap toOrphan 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /akka/src/main/scala/edu/agh/mindmapd/actors/Supervisor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package edu.agh.mindmapd.actors 19 | 20 | import akka.actor.{Terminated, Actor} 21 | import edu.agh.mindmapd.extensions.Settings 22 | 23 | class Supervisor extends Actor { 24 | 25 | val s = Settings(context.system) 26 | 27 | val mapsSupervisor = context actorOf (MapsSupervisor.props, "maps") 28 | 29 | val httpService = context actorOf (http.Service.props(mapsSupervisor), "http-service") 30 | val _ = context watch httpService 31 | 32 | def receive = { 33 | case Terminated(`httpService`) => context stop self 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /akka/src/main/scala/edu/agh/mindmapd/actors/http/Poller.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package edu.agh.mindmapd.actors.http 19 | 20 | import akka.actor.{Cancellable, ActorRef, Props, Actor} 21 | import edu.agh.mindmapd.json.PollResponse 22 | import edu.agh.mindmapd.actors.{MindMap, MapsSupervisor} 23 | import concurrent.duration._ 24 | import edu.agh.mindmapd.model.MindNode 25 | import edu.agh.mindmapd.extensions.Settings 26 | 27 | object Poller { 28 | 29 | def props(mapsSupervisor: ActorRef) = Props(classOf[Poller], mapsSupervisor) 30 | 31 | case class Process(since: Long) 32 | 33 | private case object PollTimeout 34 | private case object MapsTimeout 35 | 36 | } 37 | 38 | class Poller(mapsSupervisor: ActorRef) extends Actor { 39 | import Poller._ 40 | import context.dispatcher 41 | 42 | def receive = initial 43 | 44 | val settings = Settings(context.system) 45 | 46 | case class State(requester: ActorRef, cancellables: List[Cancellable], data: List[MindNode]) 47 | 48 | def initial: Receive = { 49 | case Process(since) => 50 | mapsSupervisor ! MapsSupervisor.Subscribe(self, since) 51 | val tm = context.system.scheduler scheduleOnce (settings.timeout.poll, self, PollTimeout) 52 | context become waitingForFirst(State( 53 | requester = sender, 54 | cancellables = tm :: Nil, 55 | data = Nil)) 56 | } 57 | 58 | def waitingForFirst(state: State): Receive = { 59 | case PollTimeout => complete(state) 60 | 61 | case MindMap.Changed(node) => 62 | val tm = context.system.scheduler scheduleOnce(settings.timeout.mapResponse, self, MapsTimeout) 63 | context become waitingForRest(state copy( 64 | cancellables = tm :: state.cancellables, 65 | data = node :: state.data)) 66 | } 67 | 68 | def waitingForRest(state: State): Receive = { 69 | case PollTimeout | MapsTimeout => complete(state) 70 | 71 | case MindMap.Changed(node) => 72 | context become waitingForRest(state copy ( 73 | data = node :: state.data)) 74 | } 75 | 76 | def complete(state: State) { 77 | import state._ 78 | cancellables foreach (_.cancel()) 79 | requester ! PollResponse(data) 80 | mapsSupervisor ! MapsSupervisor.Unsubscribe(self) 81 | context stop self 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /akka/src/main/scala/edu/agh/mindmapd/actors/http/Service.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package edu.agh.mindmapd.actors.http 19 | 20 | import akka.pattern.ask 21 | import akka.actor.{PoisonPill, ActorRef, ActorLogging, Props} 22 | import concurrent.duration._ 23 | import spray.routing.HttpServiceActor 24 | import spray.httpx.SprayJsonSupport 25 | import akka.io.IO 26 | import spray.can.Http 27 | import edu.agh.mindmapd.extensions.Settings 28 | import java.util.UUID 29 | import edu.agh.mindmapd.actors.MapsSupervisor 30 | import edu.agh.mindmapd.json.{UpdateResponse, UpdateRequest, PollResponse} 31 | import akka.util.Timeout 32 | import scala.concurrent.Future 33 | import spray.http.StatusCodes 34 | 35 | object Service { 36 | 37 | def props(mapsSupervisor: ActorRef) = 38 | Props(classOf[Service], mapsSupervisor) 39 | 40 | } 41 | 42 | class Service(mapsSupervisor: ActorRef) 43 | extends HttpServiceActor with ActorLogging with SprayJsonSupport { 44 | import context.dispatcher 45 | 46 | val settings = Settings(context.system) 47 | 48 | IO(Http)(context.system) ! Http.Bind(self, settings.hostname, settings.port) 49 | 50 | def receive = runRoute { 51 | path("update") { 52 | post { entity(as[UpdateRequest]) { req => 53 | complete { 54 | val updater = context actorOf Updater.props 55 | implicit val tm = Timeout(settings.timeout.update + settings.timeout.internalMessage) 56 | for { 57 | map <- mindMapFor(req.mindMap) 58 | resp <- (updater ? Updater.Process(req, map)).mapTo[UpdateResponse] 59 | } yield resp 60 | } 61 | }} 62 | } ~ 63 | path("poll" / "since" / LongNumber) { since => 64 | get { complete { 65 | val poller = context actorOf Poller.props(mapsSupervisor) 66 | implicit val tm = Timeout(settings.timeout.poll + settings.timeout.internalMessage) 67 | (poller ? Poller.Process(since)).mapTo[PollResponse] 68 | }} 69 | } ~ 70 | path("die") { get { complete { 71 | if (Settings(context.system).isProduction) { 72 | (StatusCodes.Forbidden, "Won't die at production, u mad? =,=\n") 73 | } else { 74 | val _ = context.system.scheduler scheduleOnce (100.millis, self, PoisonPill) 75 | "Dying...\n" 76 | } 77 | }}} 78 | } 79 | 80 | def mindMapFor(uuid: UUID): Future[ActorRef] = { 81 | implicit val timeout = Timeout(settings.timeout.internalMessage) 82 | (mapsSupervisor ? MapsSupervisor.Find(uuid)).mapTo[ActorRef] 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /akka/src/main/scala/edu/agh/mindmapd/actors/http/Updater.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package edu.agh.mindmapd.actors.http 19 | 20 | import akka.actor.{Props, ActorRef, Actor} 21 | import edu.agh.mindmapd.actors.MindMap 22 | import edu.agh.mindmapd.json.{UpdateResponse, UpdateRequest} 23 | 24 | object Updater { 25 | 26 | def props = Props(classOf[Updater]) 27 | 28 | case class Process(update: UpdateRequest, mindMap: ActorRef) 29 | 30 | } 31 | 32 | class Updater extends Actor { 33 | import Updater._ 34 | 35 | def receive = initial 36 | 37 | def initial: Receive = { 38 | case Process(update, mindMap) => 39 | mindMap ! MindMap.Update(update.lastServerTime, update.nodes) 40 | context become waitingForMap(sender) 41 | } 42 | 43 | def waitingForMap(requester: ActorRef): Receive = { 44 | case MindMap.UpdateResult(orphanNodes) => 45 | requester ! UpdateResponse(orphanNodes) 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /akka/src/main/scala/edu/agh/mindmapd/extensions/CustomJsonFormats.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package edu.agh.mindmapd.extensions 19 | 20 | import spray.json._ 21 | import java.util.UUID 22 | 23 | trait CustomJsonFormats { 24 | 25 | implicit object UuidFormat extends JsonFormat[UUID] { 26 | def write(obj: UUID): JsValue = JsString(obj.toString) 27 | 28 | def read(json: JsValue): UUID = json match { 29 | case JsString(x) => try { UUID fromString x } 30 | catch { case e: Throwable => deserializationError(s"UUID could not be parsed from `$x'", e) } 31 | case x => deserializationError(s"Expected UUID as JsString, but got $x") 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /akka/src/main/scala/edu/agh/mindmapd/extensions/Settings.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package edu.agh.mindmapd.extensions 19 | 20 | import scala.concurrent.duration._ 21 | import akka.actor.{ExtensionIdProvider, ExtensionId, Extension, ExtendedActorSystem} 22 | import com.typesafe.config.Config 23 | 24 | class Settings(cf: Config) extends Extension { 25 | 26 | private implicit class KeyOps(k: String) { 27 | def boo = cf getBoolean k 28 | def dur = FiniteDuration(cf getMilliseconds k, MILLISECONDS) 29 | def int = cf getInt k 30 | def str = cf getString k 31 | def cls = Class forName (cf getString k) 32 | } 33 | 34 | val isProduction = "mindmapd.is-production".boo 35 | 36 | val hostname = "mindmapd.hostname".str 37 | val port = "mindmapd.port".int 38 | 39 | object timeout { 40 | val poll = "mindmapd.timeout.poll".dur 41 | val mapResponse = "mindmapd.timeout.maps-response".dur 42 | val update = "mindmapd.timeout.update".dur 43 | val internalMessage = "mindmapd.timeout.internal-message".dur 44 | } 45 | 46 | object squeryl { 47 | val driver = "mindmapd.squeryl.driver".cls 48 | val url = "mindmapd.squeryl.url".str 49 | val user = "mindmapd.squeryl.user".str 50 | val password = "mindmapd.squeryl.password".str 51 | } 52 | 53 | val _ = (timeout, squeryl) // ~hack: create the objects *now* 54 | 55 | } 56 | 57 | object Settings extends ExtensionId[Settings] with ExtensionIdProvider { 58 | def lookup() = Settings 59 | def createExtension(system: ExtendedActorSystem) = new Settings(system.settings.config) 60 | } 61 | -------------------------------------------------------------------------------- /akka/src/main/scala/edu/agh/mindmapd/json/PollResponse.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package edu.agh.mindmapd.json 19 | 20 | import spray.json.DefaultJsonProtocol 21 | import edu.agh.mindmapd.model.MindNode 22 | 23 | object PollResponse extends DefaultJsonProtocol { 24 | implicit val format = jsonFormat1(apply) 25 | } 26 | 27 | case class PollResponse(nodes: List[MindNode]) 28 | -------------------------------------------------------------------------------- /akka/src/main/scala/edu/agh/mindmapd/json/UpdateRequest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package edu.agh.mindmapd.json 19 | 20 | import spray.json.DefaultJsonProtocol 21 | import edu.agh.mindmapd.model.MindNode 22 | import java.util.UUID 23 | import edu.agh.mindmapd.extensions.CustomJsonFormats 24 | 25 | object UpdateRequest extends DefaultJsonProtocol with CustomJsonFormats { 26 | implicit val format = jsonFormat3(apply) 27 | } 28 | 29 | case class UpdateRequest(mindMap: UUID, lastServerTime: Long, nodes: List[MindNode]) 30 | -------------------------------------------------------------------------------- /akka/src/main/scala/edu/agh/mindmapd/json/UpdateResponse.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package edu.agh.mindmapd.json 19 | 20 | import spray.json.DefaultJsonProtocol 21 | import java.util.UUID 22 | import edu.agh.mindmapd.extensions.CustomJsonFormats 23 | 24 | object UpdateResponse extends DefaultJsonProtocol with CustomJsonFormats { 25 | implicit val format = jsonFormat1(apply) 26 | } 27 | 28 | case class UpdateResponse(orphanNodes: List[UUID]) 29 | -------------------------------------------------------------------------------- /akka/src/main/scala/edu/agh/mindmapd/model/MindNode.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package edu.agh.mindmapd.model 19 | 20 | import java.util.UUID 21 | import spray.json._ 22 | import edu.agh.mindmapd.extensions.CustomJsonFormats 23 | import org.squeryl.KeyedEntity 24 | import org.squeryl.PrimitiveTypeMode._ 25 | import org.squeryl.dsl.CompositeKey2 26 | 27 | object MindNode extends DefaultJsonProtocol with CustomJsonFormats { 28 | implicit val format = jsonFormat7(apply) 29 | } 30 | 31 | case class MindNode(uuid: UUID, 32 | mindMap: UUID, 33 | parent: Option[UUID], 34 | ordering: Double, 35 | content: Option[String], 36 | hasConflict: Boolean, 37 | cloudTime: Long) extends KeyedEntity[CompositeKey2[UUID, UUID]] { 38 | 39 | def id = compositeKey(mindMap, uuid) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /akka/src/main/scala/edu/agh/mindmapd/storage/SquerylStorage.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package edu.agh.mindmapd.storage 19 | 20 | import java.util.UUID 21 | import edu.agh.mindmapd.model.MindNode 22 | import org.squeryl.{Schema, Session, SessionFactory, PrimitiveTypeMode} 23 | import PrimitiveTypeMode._ 24 | import edu.agh.mindmapd.extensions.Settings 25 | import org.squeryl.adapters.PostgreSqlAdapter 26 | import org.squeryl.dsl.ast.LogicalBoolean 27 | 28 | object SquerylStorage extends Schema { 29 | 30 | def allMaps(settings: Settings): Vector[UUID] = { 31 | init(settings) 32 | inTransaction { from(mindNodes)(n => select(&(n.mindMap))).distinct.toVector } 33 | } 34 | 35 | def apply(mindMap: UUID, settings: Settings): Storage = { 36 | init(settings) 37 | new SquerylStorage(mindMap) 38 | } 39 | 40 | private[this] def init(settings: Settings) { 41 | init(settings.squeryl.url, settings.squeryl.user, settings.squeryl.password) 42 | } 43 | 44 | private[this] def init(url: String, user: String, password: String) { 45 | if (SessionFactory.concreteFactory.isEmpty) 46 | SessionFactory.concreteFactory = Some(() => Session create ( 47 | java.sql.DriverManager getConnection(url, user, password), 48 | new PostgreSqlAdapter 49 | )) 50 | } 51 | 52 | private val mindNodes = table[MindNode] 53 | 54 | on(mindNodes)(n => declare( 55 | columns(n.mindMap, n.parent) are indexed, 56 | n.content is dbType("text"), 57 | columns(n.mindMap, n.cloudTime) are indexed 58 | )) 59 | 60 | } 61 | 62 | class SquerylStorage private(val mindMap: UUID) extends Storage { 63 | import SquerylStorage._ 64 | 65 | def contains(node: UUID): Boolean = find(node).isDefined 66 | 67 | def find(node: UUID): Option[MindNode] = inTransaction { 68 | from(mindNodes)(n => 69 | where(n.mindMap.~ === mindMap and n.uuid.~ === node) 70 | select n).headOption 71 | } 72 | 73 | def findSince(time: Long, limit: Int): Vector[MindNode] = inTransaction { 74 | val q = from(mindNodes)(n => 75 | where(n.mindMap.~ === mindMap and n.cloudTime.~ >= time) 76 | select n orderBy n.cloudTime.asc) page (0, limit) 77 | q.toVector 78 | } 79 | 80 | def insertOrReplace(node: MindNode) = inTransaction { 81 | if (contains(node.uuid)) mindNodes update node 82 | else { val _ = mindNodes insert node } 83 | } 84 | 85 | private def children(parent: UUID): Iterable[MindNode] = inTransaction { 86 | from(mindNodes)(n => 87 | where(n.mindMap.~ === mindMap and n.parent.~ === Some(parent)) 88 | select n 89 | ) 90 | } 91 | 92 | def deleteChildrenOf(parent: UUID) = inTransaction { 93 | for (ch <- children(parent)) deleteChildrenOf(ch.uuid) 94 | // *** DFS, DO NOT CHANGE ORDER! *** 95 | val _ = mindNodes deleteWhere (n => n.mindMap.~ === mindMap and n.parent.~ === Some(parent)) 96 | } 97 | 98 | def touchTimesOfSubtree(parent: UUID) = inTransaction { 99 | val now = System.currentTimeMillis 100 | def upd(f: MindNode => LogicalBoolean) { 101 | val _ = update(mindNodes)(n => where(n.mindMap.~ === mindMap and f(n)) set(n.cloudTime := now)) 102 | } 103 | 104 | def touchChildrenOf(parent: UUID) { 105 | for (ch <- children(parent)) touchChildrenOf(ch.uuid) 106 | upd(_.parent.~ === Some(parent)) 107 | } 108 | 109 | touchChildrenOf(parent) 110 | upd(_.uuid.~ === parent) 111 | } 112 | 113 | def wasAnyChangedInSubtree(parent: MindNode, since: Long): Boolean = inTransaction { 114 | if (parent.cloudTime > since) true 115 | else children(parent.uuid) exists (wasAnyChangedInSubtree(_, since)) 116 | } 117 | 118 | def hasNoNodesYet: Boolean = inTransaction { 119 | val num: Long = from(mindNodes)(n => where(n.mindMap.~ === mindMap) compute count).single.measures 120 | num == 0 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /akka/src/main/scala/edu/agh/mindmapd/storage/Storage.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package edu.agh.mindmapd.storage 19 | 20 | import java.util.UUID 21 | import edu.agh.mindmapd.model.MindNode 22 | 23 | trait Storage { 24 | 25 | def mindMap: UUID 26 | 27 | def contains(node: UUID): Boolean 28 | 29 | def find(node: UUID): Option[MindNode] 30 | 31 | def findSince(time: Long, limit: Int): Vector[MindNode] 32 | 33 | def insertOrReplace(node: MindNode): Unit 34 | 35 | def deleteChildrenOf(parent: UUID): Unit 36 | 37 | def touchTimesOfSubtree(parent: UUID): Unit 38 | 39 | def wasAnyChangedInSubtree(parent: MindNode, since: Long): Boolean 40 | 41 | def hasNoNodesYet: Boolean 42 | 43 | } 44 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2013 Katarzyna Szawan 3 | # and Michał Rus 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # SBT and IntelliJ Idea files 19 | 20 | /.idea* 21 | /.history 22 | /target/ 23 | /project/target/ 24 | /project/project/target/ 25 | 26 | # dev notes 27 | 28 | /_/ 29 | -------------------------------------------------------------------------------- /android/project/Build.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import sbt._ 19 | import sbt.Keys._ 20 | 21 | import android.ArbitraryProject 22 | import android.Keys._ 23 | import android.Dependencies.{LibraryProject, apklib} 24 | 25 | import org.sbtidea.SbtIdeaPlugin._ 26 | 27 | object Build extends Build { 28 | 29 | // --- common 30 | 31 | val PlatformTarget = "android-17" 32 | 33 | val SupportV4 = "com.android.support" % "support-v4" % "18.0.0" 34 | 35 | val UnwantedSubprojectJars = Seq("android-support-v4.jar") 36 | def inUnwantedSubprojectJars(file: Attributed[File]) = 37 | UnwantedSubprojectJars contains file.data.getName 38 | def inUnwantedSubprojectJars(file: File) = 39 | UnwantedSubprojectJars contains file.getName 40 | 41 | // --- dload github:iPaulPro/aFileChooser 42 | 43 | val afcGit = uri("https://github.com/iPaulPro/aFileChooser.git#42d10d3bf3bddfb7ed4856c3264fc26ead4625a1") 44 | val afcBase = (ArbitraryProject git afcGit) / "aFileChooser" 45 | lazy val afcSettings = android.Plugin.androidBuild ++ Seq( 46 | platformTarget in Android := PlatformTarget, 47 | libraryProject in Android := true, 48 | 49 | unmanagedJars in Compile ~= (_ filterNot inUnwantedSubprojectJars), 50 | libraryDependencies += SupportV4, 51 | 52 | ideaSourcesClassifiers := Seq("src"), 53 | ideaJavadocsClassifiers := Seq(), 54 | ideaBasePackage := None, 55 | ideaPackagePrefix := None 56 | ) 57 | lazy val afc = RootProject(afcBase) 58 | 59 | // --- build loaders 60 | 61 | override def buildLoaders = ArbitraryProject settingsLoader Map( 62 | afcBase -> afcSettings 63 | ) 64 | 65 | // --- root 66 | 67 | lazy val root = Project(id = "root", base = file(".")).settings( 68 | android.Plugin.androidBuild ++ Seq( 69 | name := "agh-mindmap", 70 | scalaVersion := "2.10.3", 71 | 72 | javacOptions in Compile ++= Seq("-Xlint:deprecation"), 73 | scalacOptions in Compile ++= Seq("-feature", "-deprecation", "-Yno-adapted-args", "-Ywarn-all", "-Xfatal-warnings", 74 | "-Xlint", "-Ywarn-value-discard", "-Ywarn-numeric-widen", "-Ywarn-dead-code", "-unchecked"), 75 | 76 | platformTarget in Android := PlatformTarget, 77 | 78 | run <<= run in Android, 79 | 80 | libraryDependencies += SupportV4, 81 | libraryDependencies += apklib("com.actionbarsherlock" % "actionbarsherlock" % "4.4.0" intransitive()), 82 | localProjects in Android += LibraryProject(afcBase), 83 | 84 | resolvers += "spray" at "http://repo.spray.io/", 85 | libraryDependencies += "io.spray" %% "spray-json" % "1.2.5", 86 | 87 | resolvers += "michalrus.com repo" at "https://maven.michalrus.com/", 88 | libraryDependencies += "com.michalrus" %% "android-scala-helpers" % "0.3.0-SNAPSHOT", 89 | 90 | addCompilerPlugin("org.brianmckenna" % "wartremover" % "0.6-SNAPSHOT" cross CrossVersion.full), 91 | scalacOptions += "-P:wartremover:traverser:org.brianmckenna.wartremover.warts.UnsafeWithVar", 92 | 93 | proguardInputs in Android ~= (p => p.copy(injars = p.injars filterNot inUnwantedSubprojectJars)), 94 | dexInputs in Android ~= (_ filterNot inUnwantedSubprojectJars), 95 | 96 | useProguard in Android := true, 97 | 98 | proguardOptions in Android += "-keep class android.support.v4.app.** { *; }", 99 | proguardOptions in Android += "-keep interface android.support.v4.app.** { *; }", 100 | proguardOptions in Android += "-keep class com.actionbarsherlock.** { *; }", 101 | proguardOptions in Android += "-keep interface com.actionbarsherlock.** { *; }", 102 | proguardOptions in Android += "-keepattributes *Annotation*" 103 | ) 104 | :_*) dependsOn afc aggregate afc 105 | 106 | } 107 | -------------------------------------------------------------------------------- /android/project/build.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2013 Katarzyna Szawan 3 | # and Michał Rus 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | sbt.version=0.13.1 19 | -------------------------------------------------------------------------------- /android/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2013 Katarzyna Szawan 3 | // and Michał Rus 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | addSbtPlugin("com.hanhuy.sbt" % "android-sdk-plugin" % "1.1.1") 19 | 20 | addSbtPlugin("com.hanhuy.sbt" % "sbt-idea" % "1.6.0") 21 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 36 | 37 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /android/src/main/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalrus/agh-mindmap/39f94618d9d5adaee6d5e209f15aecc91816c21d/android/src/main/assets/.keep -------------------------------------------------------------------------------- /android/src/main/java/edu/agh/mindmap/util/ExplicitNull.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Katarzyna Szawan 3 | * and Michał Rus 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package edu.agh.mindmap.util; 19 | 20 | import android.content.DialogInterface; 21 | import android.database.sqlite.SQLiteDatabase; 22 | import android.os.Bundle; 23 | 24 | public final class ExplicitNull { 25 | 26 | public static final SQLiteDatabase.CursorFactory CursorFactory = null; 27 | 28 | public static final String String = null; 29 | public static final String[] StringArray = null; 30 | 31 | public static final Long Long = null; 32 | 33 | public static final Bundle Bundle = null; 34 | 35 | public static final DialogInterface.OnClickListener OnClickListener = null; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /android/src/main/res/drawable-hdpi/icon_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalrus/agh-mindmap/39f94618d9d5adaee6d5e209f15aecc91816c21d/android/src/main/res/drawable-hdpi/icon_close.png -------------------------------------------------------------------------------- /android/src/main/res/drawable-hdpi/icon_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalrus/agh-mindmap/39f94618d9d5adaee6d5e209f15aecc91816c21d/android/src/main/res/drawable-hdpi/icon_create.png -------------------------------------------------------------------------------- /android/src/main/res/drawable-hdpi/icon_import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalrus/agh-mindmap/39f94618d9d5adaee6d5e209f15aecc91816c21d/android/src/main/res/drawable-hdpi/icon_import.png -------------------------------------------------------------------------------- /android/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 24 | 25 | 29 | 30 | 36 | 37 | 42 | 43 | 44 | 45 | 50 | 51 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /android/src/main/res/layout/map.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 24 | 25 | 30 | 31 | 36 | 37 | 42 | 43 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /android/src/main/res/layout/mind_node.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 23 | 24 | 28 | 29 | 40 | 41 |