metacoma / mindwm

22 stars 1 forks source link

concept documentation available? #23

Open nnako opened 1 year ago

nnako commented 1 year ago

Hi,

I am a power-user and script-developer within the Freeplane community. And I recently stumbled over your great concept research concerning a "remote control of the Freeplane editor". I really like the idea but I don't understand the tech stack you use, yet. My questions are:

And if that question was answered, I would spend some time to evaluate:

If I had some kind of structural concept description, the reasoning could be much easier. Please see the image below, where I have sketched my own library development freeplane-python-io to interface with Freeplane files. It might be possible to find Python libraries which realize parts of the remote mechanism you implemented?:

image

Within the image, I tried to put question marks where I don't know the implications, yet, regarding your remote control concept.

If you have questions about interpreting the structure map, please feel free to get back to me. As everyone uses his own "language" for illustration of structure, it would be a miracle if somebody could get it by just looking at it ;-) .

Thanks.

metacoma commented 1 year ago

heeey @nnako Thank you for your interest

Q: how do the different components (e.g. tmux, bash scripts, ...) work together to achieve the remote control? A: Tmux -> Freeplane

1.1. Tmux: Tmux starts inside terminal emulator (xterm in my case, but it's doesn't matter) bash session Tmux allows any new changes to a pane to be passed to a command. Then we can redirect the tmux pane as a stream into the named pipe file /tmp/tmux_pane.fifo

$ mkfifo /tmp/tmux_pane.fifo
$ tmux pipe-pane "cat > /tmp/tmux_pane.fifo"

As a result, we can run `$ tail -f /tmp/tmux_pane.fifo" and see the all changes in the tmux pane in "real-time".

1.2. Freeplane:

I wrote a small groovy script, this script starts in a separate java thread and continuously reads the named pipe /tmp/freeplane.fifo file. The script assumes that all data in this named pipe file is raw groovy commands.

Before apply this script on the freeplane node, you need to create named pipe file /tmp/freeplane.fifo

$ mkfifo /tmp/freeplane.fifo

And here the script code

  InputStream Fifo = new FileInputStream("/tmp/freeplane.fifo");
  int buffer_size = 65536;
  byte[] bytes = new byte[0x10000];
  int bytes_read = -1;
  while(1) {
    while ((bytes_read = Fifo.read(bytes, 0, buffer_size)) == -1) {
//          print "."
      Thread.sleep(1000)
    }
    String text = new String(bytes, 0, bytes_read)
    String groovy_script = """
import org.freeplane.plugin.script.proxy.ScriptUtils;
def c = ScriptUtils.c();
def node = ScriptUtils.node();
""" + text;
    println groovy_script
    EventQueue.invokeAndWait(() -> Eval.me(groovy_script))
  }

At this point, you able to send the groovy code to freeplane instance through the write the groovy code to /tmp/freeplane.fifo file.

$ echo 'node.createChild("hello github")' >> /tmp/freeplane.fifo

1.3. Text processing

Okay, we have two named pipe /tmp/tmux_pane.fifo and /tmp/freeplane.fifo files.

Let's create a small text processor for example, let's write parser for ifconfig command. This parser will accept the output from ifconfig command, and produce groovy code to stdout. The following code is awk code.

  /: flags/ {
    IF_NAME=gensub(/:/, "", "g", $1)
    FREEPLANE_IF_NAME=gensub(/\-/, "_", "g", IF_NAME)
    printf("def %s = node.createChild(\"%s\");\n", FREEPLANE_IF_NAME, IF_NAME)
    printf("def bmon_%s = %s.createChild(\"%s\");\n", FREEPLANE_IF_NAME, FREEPLANE_IF_NAME, "bmon")
    printf("def iftop_%s = %s.createChild(\"%s\");\n", FREEPLANE_IF_NAME, FREEPLANE_IF_NAME, "iftop")

    printf("bmon_%s.link.text = \"%s\";\n", FREEPLANE_IF_NAME, gensub(/ /, "%20", "g", "execute:_xterm -e ssh -t daria sudo bmon -p "IF_NAME))
    printf("iftop_%s.link.text = \"%s\";\n", FREEPLANE_IF_NAME, gensub(/ /, "%20", "g", "execute:_xterm -e ssh -t daria sudo iftop -i "IF_NAME))
    printf("%s.setFolded(true);\n", FREEPLANE_IF_NAME)
  }
  /[RT]X packets/ {
    printf("%s[\"%s\"] = \"%s\";\n", FREEPLANE_IF_NAME, $1 , $3)
  }

This simple awk scripts converts the following ifconfig vlan-public output

vlan-public: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.25.255.223  netmask 255.255.0.0  broadcast 172.25.255.255
        inet6 fe80::f66d:4ff:fe6f:f9fb  prefixlen 64  scopeid 0x20<link>
        ether f4:6d:04:6f:f9:fb  txqueuelen 1000  (Ethernet)
        RX packets 198892854  bytes 293609323848 (293.6 GB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 55235119  bytes 5329144404 (5.3 GB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

to this groovy code

def vlan_public = node.createChild("vlan-public");
def bmon_vlan_public = vlan_public.createChild("bmon");
def iftop_vlan_public = vlan_public.createChild("iftop");
bmon_vlan_public.link.text = "execute:_xterm%20-e%20ssh%20-t%20daria%20sudo%20bmon%20-p%20vlan-public";
iftop_vlan_public.link.text = "execute:_xterm%20-e%20ssh%20-t%20daria%20sudo%20iftop%20-i%20vlan-public";
vlan_public.setFolded(true);
vlan_public["RX"] = "198895464";
vlan_public["TX"] = "55236599";

Lastly, groovy code will be written to the /tmp/freeplane.fifo and the groovy script from step 2 will execute it within the Freeplane Java context. That completes the process!

speaking very very roughly, it's working like this:

$ tail -f /tmp/tmux_pane.fifo | text_processing >> /tmp/freeplane.fifo 
  1. Freeplane -> Tmux It's very easy, tmux has a socket for remote control. I just send the ${node.text} (the node name in freeplane) to the specific pane in the specific tmux window in the specific tmux session, like:
$ tmux send-keys -t ${TMUX_SESSION}.0 "${INPUT}" ENTER
$ tmux send-keys -t ${TMUX_SESSION}.0 "" ENTER

Just take a look to this groovy script:

node.link.text = 'execute:_xterm%20-e%20tmux%20attach%20-t%20' + node.id + '-Node'
node['script1'] = '''
node.children.each{ it.delete() }
output = FP.execNode("execNode.sh " + node.id + " '" + node.text + "'")

For example, bind this script execution on the some hot key (ctrl + e) Then change node name in freeplane, for example to id and press ctrl + e combination. The ${node.text} will be passed as a second argument to bash script, that sends ${node.text} as stdin in bash session and press "Enter" button.

And per each line that produced in stdout/stderr the id ($node.text}) command, the following code will create new freeplane node.

 if (output?.trim()) {
  for( String values : output.split("\\n") ) {
     node.createChild(values)
  }
}

Q: within your introduction videos, there are different windows which pop up, but I don't see where they belong and how they interact. A: Yeah, randomly appearing windows can lead to confusion. Interaction between Mindwm components is based on a "message bus." For example, the Tmux pane generates three types of message events: bytestream, linestream, and IO-context stream. line-stream event:

{
    "metadata": {
        "tmux_pane_id": "%57", # unique tmux pane id
        "fp_node_id": "0x10321312", # uniq freeplane node id
    } 
    "message": {
       "line": "hello world"
    } 
}

io-context event:

{
   "metadata": {
       "tmux_pane_id": "%403" # uniq tmux pane id
       "fp_node_id": "0x32134132" # uniq freeplane node id
   }
   "io-context": {
        "input": "uptime"
        "output": "10:56:11  up   0:16,  1 user,  load average: 0.28, 0.29, 0.21"
   }
}

I use vector.dev (processing), kafka (message queue), opensearch (storage + API) and GRPC to process these events: The green color of the nodes indicates that they have been implemented and are ready to use in this repository.

mindwm

Q: would it be possible to realize your concept within a Windows operating system? A: I believe it is possible. I package all of the necessary MindWM components into Kubernetes resources. Personally, I use minikube to deploy MindWM on my workstation. I hope the following diagram will explain the "multi-user" architecture: mindwm_multi

Q: can the concept be used to implement control scripts (text files which contain control sequences) for controlling the Freeplane editor? A: Yes, it is very easy to achieve.

  1. Copy this script https://github.com/metacoma/mindwm/blob/main/files/groovy-scripts/log.groovy to freeplane script directory.
  2. run bash command: $ mkfifo /tmp/LOG
  3. create /tmp/example.groovy file with following content:
    node.createChild('test')
    node.createChild('test1')
    node.createChild('test2')
  4. Start freeplane, create new mindmap and apply the groovy script from the p1 (log.groovy) on the root node.
  5. Run in bash terminal command: cat example.groovy | base64 >> /tmp/LOG

If I had some kind of structural concept description, the reasoning could be much easier. Please see the image below, where I have sketched my own library development freeplane-python-io to interface with Freeplane files. First of all, I really like the idea of freeplane-python-io. As I understand it, the main difference between our approaches is that MindWM run commands inside the running Freeplane instance, while the freeplane-python-io library uses raw XML to make the necessary changes to the mind map, correct?

Regarding your sketch, I hope my answers shed some light on the questions spots

Q: It might be possible to find Python libraries which realize parts of the remote mechanism you implemented?: A: It's a good question. I plan to write a Freeplane GRPC plugin. If I understand correctly, this would allow any GRPC client written in any language (such as Python, Ruby, or Lua) to interact with the mindmap objects within a running instance of Freeplane.

I am a power-user and script-developer within the Freeplane community.

Great, in the near future I plan to begin writing a Freeplane GRPC plugin, If you are interested in this, please join!

metacoma commented 1 year ago

Apologies for any confusion. Although the issue was referenced in the pull request merge, it was actually invalid and should not have been closed. Therefore, the issue remains open.