electron version of meet plugin

This commit is contained in:
Dele Olajide
2020-09-02 00:19:42 +01:00
parent 928a0ad85e
commit 2d6102d779
13 changed files with 1026 additions and 325 deletions

View File

@ -10,10 +10,10 @@
</parent>
<artifactId>meet</artifactId>
<version>0.1</version>
<version>0.0.5</version>
<name>Meetings Plugin</name>
<description>Adds support for Openfire Meetings to the Spark IM client.</description>
<name>Pade Meetings Plugin</name>
<description>Adds support for Pade Meetings to the Spark IM client.</description>
<contributors>
<contributor>
@ -24,4 +24,4 @@
</contributor>
</contributors>
</project>
</project>

View File

@ -1,2 +1,17 @@
This is a plugin for Spark that allows users to join audio and video conferences hosted by Openfire Meetings.
This is a plugin for Spark that allows users to join audio and video conferences hosted by [Openfire Meetings](https://github.com/igniterealtime/community-plugins/tree/master/ofmeet). See the documentation for more details. It provides a button from a Multi User Chat (MUC) room or chat window within the Spark client, to open a Chrome window using the same URL as the Jitsi Meet web client. It therefore assumes you have Chrome installed and configured as your default browser. It works, but the user experience is not ideal.
It uses Electron instead of depending on Chrome installed and configured as the default browser.
![](https://community.igniterealtime.org/servlet/JiveServlet/downloadImage/38-1827-157172/sparkmeet.png)
It is a much better user experience than opening a Chrome browser window out of context somewhere else.
It does not do desktop sharing, but does whole screen sharing. All Electron runtime platforms (windows, Linux & OSX) are supported. I have only tested it at home on my windows win32 desktop.
Feeback will be appreciated from Linux and OSX users.
The sparkmeet.jar plugin is built using the plugin ANT build.xml file. The following targets can be used
1. win32 - Windows 32 Only
1. win - Bothe win32 and win64
1. linux - Linux32 & linux64
1. osx - OSX 64
1. all - Multi-platform support. The plugin will be over 200MB. Spark takes over a minute to start the very first time the plugin is deployed.

View File

@ -0,0 +1,37 @@
package de.mxro.process;
/**
* A listener to intercept outputs from the process.
*
* @author Max
*
*/
public interface ProcessListener {
/**
* When the process wrote a line to its standard output stream.
*
* @param line
*/
public void onOutputLine(String line);
/**
* When the process wrote a line to its error output stream.
*
* @param line
*/
public void onErrorLine(String line);
/**
* When the output stream is closed.
*/
public void onProcessQuit(int returnValue);
/**
* When an unexpected error is thrown while interacting with the process.
*
* @param t
*/
public void onError(Throwable t);
}

View File

@ -0,0 +1,129 @@
package de.mxro.process;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import de.mxro.process.internal.Engine;
public class Spawn {
/**
* Start a new process.
*
* @param command
* @param listener
* @param folder
* @return
*/
public static XProcess startProcess(final String command, final File folder, final ProcessListener listener) {
return startProcess(command.split(" "), folder, listener);
}
public static XProcess startProcess(final String[] command, final File folder, final ProcessListener listener) {
return Engine.startProcess(command, listener, folder);
}
public static String runCommand(final String command, final File folder) {
return runCommand(command.split(" "), folder);
}
/**
* Runs a command in a new process and stops the process thereafter.
*
* @param command
* @param folder
*/
public static String runCommand(final String[] command, final File folder) {
final CountDownLatch latch = new CountDownLatch(2);
final List<Throwable> exceptions = Collections.synchronizedList(new LinkedList<Throwable>());
final List<String> output = Collections.synchronizedList(new LinkedList<String>());
latch.countDown();
final XProcess process = startProcess(command, folder, new ProcessListener() {
@Override
public void onProcessQuit(final int returnValue) {
latch.countDown();
}
@Override
public void onOutputLine(final String line) {
output.add(line);
}
@Override
public void onErrorLine(final String line) {
output.add(line);
}
@Override
public void onError(final Throwable t) {
exceptions.add(t);
}
});
try {
latch.await();
// Thread.sleep(300); // just wait for input to gobble in
} catch (final InterruptedException e) {
throw new RuntimeException(e);
}
if (exceptions.size() > 0) {
throw new RuntimeException(exceptions.get(0));
}
final StringBuilder sb = new StringBuilder();
for (final String line : new ArrayList<String>(output)) {
sb.append(line + "\n");
}
process.destory();
return sb.toString();
}
public interface Callback<Type> {
public void onDone(Type t);
}
/**
* Runs bash scripts (*.sh) in a UNIX environment.
*
* @param bashScriptFile
* @return
*/
public static String runBashScript(final File bashScriptFile) {
return runCommand("/bin/bash -c " + bashScriptFile.getAbsolutePath(), bashScriptFile.getParentFile());
}
public static boolean isWindows() {
return System.getProperty("os.name").startsWith("Windows");
}
public static String sh(final File folder, final String bashCommand) {
if (!isWindows()) {
return runCommand(new String[] { "/bin/bash", "-c", bashCommand }, folder);
} else {
return runCommand(new String[] { "cmd.exe", "/C", bashCommand }, folder);
}
}
/**
* Executes a bash command.
*
* @param bashCommand
* @return
*/
public static String sh(final String bashCommand) {
return sh(null, bashCommand);
}
}

View File

@ -0,0 +1,24 @@
package de.mxro.process;
/**
* A wrapper for {@link java.lang.Process}.
*
* @author <a href="http://www.mxro.de/">Max Rohde</a>
*
*/
public interface XProcess {
/**
* Call to push the specified {@link String} into the started processes
* input.
*
* @param line
*/
public void sendLine(String line);
/**
* Try to destroy the process
*/
public void destory();
}

View File

@ -0,0 +1,183 @@
package de.mxro.process.internal;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.ProcessBuilder.Redirect;
import java.util.Date;
import java.util.concurrent.atomic.AtomicLong;
import de.mxro.process.ProcessListener;
import de.mxro.process.XProcess;
public class Engine {
public static XProcess startProcess(final String[] command,
final ProcessListener listener, final File folder) {
final ProcessBuilder pb;
final Process process;
try {
pb = new ProcessBuilder(command);
pb.directory(folder);
pb.redirectOutput(Redirect.PIPE);
pb.redirectInput(Redirect.PIPE);
process = pb.start();
} catch (final IOException e) {
throw new RuntimeException(e);
}
final AtomicLong lastOutput = new AtomicLong();
final long startTime = new Date().getTime();
lastOutput.set(0);
final OutputStream outputStream = process.getOutputStream();
final InputStream inputStream = process.getInputStream();
final InputStream errorStream = process.getErrorStream();
//BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
// String line;
// try {
// line = reader.readLine();
// while (line != null && !line.trim().equals("--EOF--")) {
// listener.onOutputLine(line);
// line = reader.readLine();
// }
// } catch (IOException e1) {
// // TODO Auto-generated catch block
// e1.printStackTrace();
// }
final StreamReader streamReader = new StreamReader(inputStream,
new StreamListener() {
@Override
public void onOutputLine(final String line) {
//System.out.println("Receiving line");
lastOutput.set(new Date().getTime());
listener.onOutputLine(line);
}
@Override
public void onError(final Throwable t) {
listener.onError(new Exception(
"Error while reading standard output", t));
}
@Override
public void onClosed() {
}
});
final StreamReader errorStreamReader = new StreamReader(errorStream,
new StreamListener() {
@Override
public void onOutputLine(final String line) {
lastOutput.set(new Date().getTime());
listener.onErrorLine(line);
}
@Override
public void onError(final Throwable t) {
listener.onError(new Exception(
"Error while reading error output", t));
}
@Override
public void onClosed() {
}
});
new Thread() {
@Override
public void run() {
errorStreamReader.read();
}
}.start();
new Thread() {
@Override
public void run() {
try {
streamReader.read();
final int returnValue = process.waitFor();
//while (lastOutput.get() == 0 && new Date().getTime() - startTime < 500) {
// Thread.sleep(10);
//}
//while ( new Date().getTime() - lastOutput.get() < 1000) {
// System.out.println("DELAY ... "+(new Date().getTime() - lastOutput.get()));
// Thread.sleep(500);
//}
//System.out.println("processes finished with "+returnValue);
listener.onProcessQuit(returnValue);
} catch (final InterruptedException e) {
listener.onError(e);
return;
}
}
}.start();
return new XProcess() {
@Override
public synchronized void sendLine(final String line) {
final BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(outputStream));
try {
writer.append(line);
} catch (final IOException e) {
listener.onError(e);
}
}
@Override
public void destory() {
process.destroy();
try {
process.waitFor();
} catch (final InterruptedException e) {
throw new RuntimeException(e);
}
try {
errorStreamReader.stop();
outputStream.close();
inputStream.close();
errorStream.close();
} catch (final IOException e) {
listener.onError(e);
}
}
};
}
}

View File

@ -0,0 +1,11 @@
package de.mxro.process.internal;
public interface StreamListener {
public void onOutputLine(String line);
public void onClosed();
public void onError(Throwable t);
}

View File

@ -0,0 +1,100 @@
package de.mxro.process.internal;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class StreamReader {
private final class WorkerThread extends Thread {
private final StreamListener listener;
private final InputStream stream;
private transient int timeout;
private WorkerThread(final StreamListener listener,
final InputStream stream) {
this.listener = listener;
this.stream = stream;
this.timeout = 10;
}
@Override
public void run() {
final BufferedReader reader = new BufferedReader(
new InputStreamReader(stream));
try {
String read;
this.timeout = 10;
read = reader.readLine();
while (!stop && read != null && !read.trim().equals("--EOF--")) {
if (stop) {
stopReader();
return;
}
if (read != null) {
listener.onOutputLine(read);
}
// waitForInput();
read = reader.readLine();
}
} catch (final Exception e) {
listener.onError(e);
}
}
/**
* Wait longer and longer to not keep CPU busy.
*/
private final void waitForInput() {
try {
Thread.sleep(this.timeout);
} catch (final InterruptedException e) {
throw new RuntimeException();
}
if (this.timeout < 2000) {
this.timeout = this.timeout + (this.timeout);
}
}
private void stopReader() throws IOException {
//stream.close();
stopped = true;
listener.onClosed();
}
}
private final Thread t;
private volatile boolean stop = false;
private volatile boolean stopped = false;
public void read() {
t.run();
}
public void stop() {
if (stopped) {
return;
}
stop = true;
//while (!stopped) {
// Thread.yield();
//}
}
public StreamReader(final InputStream stream, final StreamListener listener) {
super();
this.t = new WorkerThread(listener, stream);
}
}

View File

@ -0,0 +1,188 @@
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jitsi.util;
/**
* Utility fields for OS detection.
*
* @author Sebastien Vincent
* @author Lubomir Marinov
*/
public class OSUtils
{
/** <tt>true</tt> if architecture is 32 bit. */
public static final boolean IS_32_BIT;
/** <tt>true</tt> if architecture is 64 bit. */
public static final boolean IS_64_BIT;
/** <tt>true</tt> if OS is Android */
public static final boolean IS_ANDROID;
/** <tt>true</tt> if OS is Linux. */
public static final boolean IS_LINUX;
/** <tt>true</tt> if OS is Linux 32-bit. */
public static final boolean IS_LINUX32;
/** <tt>true</tt> if OS is Linux 64-bit. */
public static final boolean IS_LINUX64;
/** <tt>true</tt> if OS is MacOSX. */
public static final boolean IS_MAC;
/** <tt>true</tt> if OS is MacOSX 32-bit. */
public static final boolean IS_MAC32;
/** <tt>true</tt> if OS is MacOSX 64-bit. */
public static final boolean IS_MAC64;
/** <tt>true</tt> if OS is Windows. */
public static final boolean IS_WINDOWS;
/** <tt>true</tt> if OS is Windows 32-bit. */
public static final boolean IS_WINDOWS32;
/** <tt>true</tt> if OS is Windows 64-bit. */
public static final boolean IS_WINDOWS64;
/** <tt>true</tt> if OS is Windows Vista. */
public static final boolean IS_WINDOWS_VISTA;
/** <tt>true</tt> if OS is Windows 7. */
public static final boolean IS_WINDOWS_7;
/** <tt>true</tt> if OS is Windows 8. */
public static final boolean IS_WINDOWS_8;
/** <tt>true</tt> if OS is FreeBSD. */
public static final boolean IS_FREEBSD;
static
{
// OS
String osName = System.getProperty("os.name");
if (osName == null)
{
IS_ANDROID = false;
IS_LINUX = false;
IS_MAC = false;
IS_WINDOWS = false;
IS_WINDOWS_VISTA = false;
IS_WINDOWS_7 = false;
IS_WINDOWS_8 = false;
IS_FREEBSD = false;
}
else if (osName.startsWith("Linux"))
{
String javaVmName = System.getProperty("java.vm.name");
if ((javaVmName != null) && javaVmName.equalsIgnoreCase("Dalvik"))
{
IS_ANDROID = true;
IS_LINUX = false;
}
else
{
IS_ANDROID = false;
IS_LINUX = true;
}
IS_MAC = false;
IS_WINDOWS = false;
IS_WINDOWS_VISTA = false;
IS_WINDOWS_7 = false;
IS_WINDOWS_8 = false;
IS_FREEBSD = false;
}
else if (osName.startsWith("Mac"))
{
IS_ANDROID = false;
IS_LINUX = false;
IS_MAC = true;
IS_WINDOWS = false;
IS_WINDOWS_VISTA = false;
IS_WINDOWS_7 = false;
IS_WINDOWS_8 = false;
IS_FREEBSD = false;
}
else if (osName.startsWith("Windows"))
{
IS_ANDROID = false;
IS_LINUX = false;
IS_MAC = false;
IS_WINDOWS = true;
IS_WINDOWS_VISTA = (osName.indexOf("Vista") != -1);
IS_WINDOWS_7 = (osName.indexOf("7") != -1);
IS_WINDOWS_8 = (osName.indexOf("8") != -1);
IS_FREEBSD = false;
}
else if (osName.startsWith("FreeBSD"))
{
IS_ANDROID = false;
IS_LINUX = false;
IS_MAC = false;
IS_WINDOWS = false;
IS_WINDOWS_VISTA = false;
IS_WINDOWS_7 = false;
IS_WINDOWS_8 = false;
IS_FREEBSD = true;
}
else
{
IS_ANDROID = false;
IS_LINUX = false;
IS_MAC = false;
IS_WINDOWS = false;
IS_WINDOWS_VISTA = false;
IS_WINDOWS_7 = false;
IS_WINDOWS_8 = false;
IS_FREEBSD = false;
}
// arch i.e. x86, amd64
String osArch = System.getProperty("sun.arch.data.model");
if(osArch == null)
{
IS_32_BIT = true;
IS_64_BIT = false;
}
else if (osArch.indexOf("32") != -1)
{
IS_32_BIT = true;
IS_64_BIT = false;
}
else if (osArch.indexOf("64") != -1)
{
IS_32_BIT = false;
IS_64_BIT = true;
}
else
{
IS_32_BIT = false;
IS_64_BIT = false;
}
// OS && arch
IS_LINUX32 = IS_LINUX && IS_32_BIT;
IS_LINUX64 = IS_LINUX && IS_64_BIT;
IS_MAC32 = IS_MAC && IS_32_BIT;
IS_MAC64 = IS_MAC && IS_64_BIT;
IS_WINDOWS32 = IS_WINDOWS && IS_32_BIT;
IS_WINDOWS64 = IS_WINDOWS && IS_64_BIT;
}
/**
* Allows the extending of the <tt>OSUtils</tt> class but disallows
* initializing non-extended <tt>OSUtils</tt> instances.
*/
protected OSUtils()
{
}
}

View File

@ -26,9 +26,9 @@ import org.jivesoftware.spark.component.RolloverButton;
import org.jivesoftware.spark.ui.ChatRoom;
import org.jivesoftware.spark.util.*;
import org.jivesoftware.spark.util.log.*;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.packet.*;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.parts.Localpart;
import org.jxmpp.util.XmppStringUtils;
import sun.misc.BASE64Decoder;
@ -47,58 +47,56 @@ public class ChatRoomDecorator
try {
BASE64Decoder decoder = new BASE64Decoder();
String imageString = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACZUlEQVR42o1TS09TQRg999mWgtTGQosiLEQhXVA0Me5swsYFRjBxoyatvwBi4g9g66p7F+2exIDvBYldGOIzbUwMGqw2Sr1YH+21Qsud2zvOTB9UkMQv+e6de2fOme+c+QaUUtCdjLF8TPdGpv7rc4wUnoEYL2F/X4VTKwuMxB8selmmWUawK5zKOpzSB5D8Mqy1e1n2K6qPTJn6iRnIB49B3Q9cL74GtSoirXeLIGt3YPuNiKIhXX2SicKxTT18RRDMdYLtjVeQJBlW7iH7qMHZLMIurGC7x0D/VVFtxLwtzVm5R/P62KU2QQP85Tlo7SdI4Sns9RUG3gBIFZRsQvJ0yCKYk4F5PuYEPlHy1wwk1S3A5P0D2MYLkBDKUJBykVBaK3eVi7ckUBU+veyf1ifOQeoKCAIRlDogqwsgH5cFWBnFIgYQZ1Omy3sd1bs3oP32Q3b7oY5OLmkj5yH3HAGrBHlOwHVD9QiwcxSp3ot0xn+GgmVyW7lZsgOgsrefqsOTJVf4clI+MMjNFwSptjhZ4c88c/oae483yePewaRP5h7IKuTuoA+6N85253PjfxPULbYglLC2mHeV+4mWP5U3U6gTVqXi7jxpPpdQW0fIPZCYBMkbzGqGAXNpKqrxIhmQmIBmAMrESTAZnURRTjAtjiN4qmGmXYXFB58yqDdX6R5mXt9hBu6DMnAaSiDcLoO3Mm1qzbK2zdKtbwlrdcGEopfqpZyQoBwaE02lHb/Q3qgZ+dZd2LHhx9tG48hajLVzCno3lN4hIXEXWEjovIn/yrP8JtL9Y3ZPBf8RQ03feJMN/wHiFFIvPgJ8vgAAAABJRU5ErkJggg==";
String imageString = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAKDSURBVDhPXVM7axVBFP5m7maN5gFyJTdNEEVDUt3C/yAIKkRSidjEUsTKRyrtLSyi/gArSRfFOiBqY5UqICaRYDCvm7177+7Ozj7G70wSSJzlmzk75/WdmTMqLwq3ubKCcGQEOgjgBgKoYACq0YBrKGh+rqrh6gKqqFGVBVCUKJMexqenoba2t937qSmcu3QRmk5aN7yz4tfQGrVzkOG4ermuUBPdzT+4v7wMHYYhMg3k1JkaSJktyQyigwPsdfYRRV0keY7EWpiyRFpWMGJbVQjPDCLQSiGJe9Bxn2lI0VUYao1jcmYGDTJJow6+v3mH860xlkSGcNBOI4oPyBGgv0bKmpIiR7+wPmP7ziyuP37EWi2utK/hdZKgZwyyNEWaW29r8hJK84w0p5yRLBWWNAVRt4vVL9+w+OIlXt27i6+LHzC78BbR7i5y6gUZfRTZ0531EDlrt5VDQWS5Qd9knmJIbP1cg7HG6wViK0lFHwiDHQqms8eZTGqHDg9tdH8XG/y/PDmJG/PP8XC8hR5Pv087cfxLaEq8KEbkT8lAHpTjuIuxdhvzH5dw8+kTzDWb2NneQR3oU3aHh8hFAuQ8WSugbEjxx6fPeHbrNhbmHqDb6bA3gkM9IbapBOCtkYA7OgPeLyHBMpvD8r4HKQtN9qDXHcPbUsd7lxKopGjJR8BGRZJkiPsxqhP7/0MS0R/K9mN3dXgUY7JxNKQMqXGIOGzk04P++E382lgnQ7avZK25e4yA/0JfgggLjxN6gfhI+Srd33PDzQtoycbR4NPwmY9XGbIyl88u2CKi1VWosq5db30N4dkh35q+tRRfI7tMwImJ6MpsruZKyMussgzhxAT+AQRKd557vsR7AAAAAElFTkSuQmCC";
byte[] imageByte = decoder.decodeBuffer(imageString);
ImageIcon ofmeetIcon = new ImageIcon(imageByte);
ofmeetButton = new RolloverButton(ofmeetIcon);
ofmeetButton.setToolTipText(GraphicUtils.createToolTip("Openfire Meetings"));
final Localpart roomId = room.getRoomJid().getLocalpart();
ofmeetButton.setToolTipText(GraphicUtils.createToolTip("Pade Meetings"));
final String roomId = getNode(room.getRoomname().toString());
final String sessionID = roomId + "-" + System.currentTimeMillis();
final Localpart nickname = SparkManager.getSessionManager().getJID().getLocalpart();
final String nickname = getNode(XmppStringUtils.parseBareAddress(SparkManager.getSessionManager().getJID().toString()));
ofmeetButton.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
String newUrl;
Localpart newRoomId;
String newUrl, newRoomId;
if ("groupchat".equals(room.getChatType().toString()))
{
newRoomId = roomId;
newUrl = url + "/" + newRoomId;
sendInvite(room.getRoomJid(), newUrl, Message.Type.groupchat);
newRoomId = roomId + "-" + sessionID;
newUrl = url + newRoomId;
plugin.handleClick(newUrl, room, newUrl, Message.Type.groupchat);
} else {
newRoomId = Localpart.fromOrThrowUnchecked(sessionID);
newUrl = url + "/" + newRoomId;
sendInvite(room.getRoomJid(), newUrl, Message.Type.chat);
newRoomId = sessionID;
newUrl = url + newRoomId;
plugin.handleClick(newUrl, room, newUrl, Message.Type.chat);
}
plugin.openUrl(newUrl, newRoomId);
}
});
room.getEditorBar().add(ofmeetButton);
} catch (Exception e) {
Log.error("cannot create openfire meetings icon", e);
Log.error("cannot create pade meetings icon", e);
}
}
public void finished()
{
Log.warning("ChatRoomDecorator: finished " + room.getRoomJid());
Log.warning("ChatRoomDecorator: finished " + room.getRoomname());
}
private void sendInvite(Jid jid, String url, Message.Type type)
private String getNode(String jid)
{
Message message2 = new Message();
message2.setTo(jid);
message2.setType(type);
message2.setBody(url);
room.sendMessage(message2);
String node = jid;
int pos = node.indexOf("@");
if (pos > -1)
node = jid.substring(0, pos);
return node;
}
}

View File

@ -17,122 +17,97 @@
package org.jivesoftware.spark.plugin.ofmeet;
import org.jivesoftware.Spark;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.spark.SparkManager;
import org.jivesoftware.spark.plugin.Plugin;
import org.jivesoftware.spark.ui.ChatRoom;
import org.jivesoftware.spark.ui.ChatRoomListener;
import org.jivesoftware.spark.ui.GlobalMessageListener;
import org.jivesoftware.spark.util.log.Log;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Localpart;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.util.zip.*;
import java.io.*;
import java.net.*;
import java.lang.reflect.*;
import org.jivesoftware.Spark;
import org.jivesoftware.spark.*;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.spark.component.*;
import org.jivesoftware.spark.component.browser.*;
import org.jivesoftware.spark.plugin.*;
import org.jivesoftware.spark.ui.rooms.*;
import org.jivesoftware.spark.ui.*;
import org.jivesoftware.spark.util.*;
import org.jivesoftware.smack.*;
import org.jivesoftware.spark.util.log.*;
import org.jitsi.util.OSUtils;
import de.mxro.process.*;
import org.jxmpp.jid.parts.*;
public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageListener
{
private org.jivesoftware.spark.ChatManager chatManager;
private String protocol = "https";
private DomainBareJid server = null;
private String port = "7443";
private String url = null;
private int width = 1024;
private int height = 768;
private String path = "ofmeet";
private static File pluginsettings = new File(Spark.getSparkUserHome() + "/ofmeet.properties");
private Map<EntityBareJid, ChatRoomDecorator> decorators = new HashMap<>();
private Browser browser = null;
private JFrame frame = null;
private static File pluginsettings = new File(System.getProperty("user.home") + System.getProperty("file.separator") + "Spark" + System.getProperty("file.separator") + "ofmeet.properties");
private Map<String, ChatRoomDecorator> decorators = new HashMap<String, ChatRoomDecorator>();
private String electronExePath = null;
private String electronHomePath = null;
private XProcess electronThread = null;
private JPanel inviteAlert;
public SparkMeetPlugin()
{
}
public void initialize()
{
checkNatives();
chatManager = SparkManager.getChatManager();
server = SparkManager.getSessionManager().getServerAddress();
String server = SparkManager.getSessionManager().getServerAddress().toString();
String port = "7443";
url = "https://" + server + ":" + port + "/ofmeet/";
Properties props = new Properties();
if (pluginsettings.exists())
{
Log.debug("ofmeet-info: Properties-file does exist= " + pluginsettings.getPath());
Log.warning("ofmeet-info: Properties-file does exist= " + pluginsettings.getPath());
try {
props.load(new FileInputStream(pluginsettings));
if (props.getProperty("port") != null)
if (props.getProperty("url") != null)
{
port = props.getProperty("port");
Log.debug("ofmeet-info: ofmeet-port from properties-file is= " + port);
url = props.getProperty("url");
Log.warning("ofmeet-info: ofmeet url from properties-file is= " + url);
}
if (props.getProperty("protocol") != null)
{
protocol = props.getProperty("protocol");
Log.debug("ofmeet-info: ofmeet-protocol from properties-file is= " + protocol);
}
if (props.getProperty("server") != null)
{
server = JidCreate.domainBareFrom(props.getProperty("server"));
Log.debug("ofmeet-info: ofmeet-server from properties-file is= " + server);
}
if (props.getProperty("path") != null)
{
path = props.getProperty("path");
Log.debug("ofmeet-info: ofmeet-path from properties-file is= " + path);
}
if (props.getProperty("width") != null)
{
width = Integer.parseInt(props.getProperty("width"));
Log.debug("ofmeet-info: ofmeet-width from properties-file is= " + width);
}
if (props.getProperty("height") != null)
{
height = Integer.parseInt(props.getProperty("height"));
Log.debug("ofmeet-info: ofmeet-height from properties-file is= " + height);
}
} catch (Exception e) {
System.err.println(e);
} catch (IOException ioe) {
Log.warning("ofmeet-Error:", ioe);
}
} else {
Log.warning("ofmeet-Error: Properties-file does not exist= " + pluginsettings.getPath() + ", using default " + url);
}
url = "https://" + server + ":" + port + "/" + path;
chatManager.addChatRoomListener(this);
chatManager.addGlobalMessageListener(this);
}
public void shutdown()
{
try
{
Log.warning("shutdown");
chatManager.removeChatRoomListener(this);
if (electronThread != null) electronThread.destory();
electronThread = null;
}
catch(Exception e)
{
Log.warning("shutdown ", e);
}
}
@Override
@ -141,9 +116,10 @@ public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageL
try {
Localpart roomId = room.getRoomJid().getLocalpart();
String body = message.getBody();
int pos = body.indexOf("https://");
if ( body.startsWith("https://") && body.endsWith("/" + roomId) ) {
showInvitationAlert(message.getBody(), room, roomId);
if ( pos > -1 && (body.contains("/" + roomId + "-") || body.contains("meeting")) ) {
showInvitationAlert(message.getBody().substring(pos), room, roomId);
}
@ -153,214 +129,6 @@ public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageL
}
@Override
public void messageSent(ChatRoom room, Message message) {
}
private String getNode(String jid)
{
String node = jid;
int pos = node.indexOf("@");
if (pos > -1)
node = jid.substring(0, pos);
return node;
}
public void shutdown()
{
try
{
Log.debug("shutdown");
chatManager.removeChatRoomListener(this);
chatManager.removeGlobalMessageListener(this);
}
catch(Exception e)
{
Log.warning("shutdown ", e);
}
}
public boolean canShutDown()
{
return true;
}
public void uninstall()
{
}
// openUrl keep only one ofmeet window opened at any time
// to close window, send hangup command and wait for 1 sec.
// before disposing browser and jframe window
public void openUrl(String url, CharSequence roomId)
{
String meetUrl = url;
try {
String username = URLEncoder.encode(SparkManager.getSessionManager().getUsername(), "UTF-8");
String password = URLEncoder.encode(SparkManager.getSessionManager().getPassword(), "UTF-8");
if (meetUrl.startsWith("https://"))
{
meetUrl = "https://" + username + ":" + password + "@" + url.substring(8);
openRoom(meetUrl, roomId);
} else Log.warning("openUrl: unexpected url " + meetUrl);
} catch (Exception e) {
Log.warning("Error with username/password: " + meetUrl);
openRoom(meetUrl, roomId);
}
}
private void openRoom(String roomUrl, CharSequence roomId)
{
try {
if (browser != null)
{
ActionListener taskPerformer = new ActionListener()
{
public void actionPerformed(ActionEvent evt)
{
if (browser != null) browser.dispose();
if (frame != null) frame.dispose();
open(roomUrl, roomId);
}
};
browser.executeJavaScript("APP.conference.hangup();");
javax.swing.Timer timer = new javax.swing.Timer(1000 ,taskPerformer);
timer.setRepeats(false);
timer.start();
} else open(roomUrl, roomId);
} catch (Exception t) {
Log.warning("openRoom " + roomUrl, t);
}
}
private void open(String roomUrl, CharSequence roomId)
{
browser = new Browser();
BrowserView view = new BrowserView(browser);
frame = new JFrame();
frame.add(view, BorderLayout.CENTER);
frame.setSize(width, height);
frame.setVisible(true);
frame.setTitle("Openfire Meetings - " + roomId);
frame.addWindowListener(new java.awt.event.WindowAdapter()
{
@Override public void windowClosing(java.awt.event.WindowEvent windowEvent)
{
close();
}
});
browser.loadURL(roomUrl);
}
private void close()
{
ActionListener taskPerformer = new ActionListener()
{
public void actionPerformed(ActionEvent evt)
{
if (browser != null)
{
browser.dispose();
browser = null;
}
if (frame != null)
{
frame.dispose();
frame = null;
}
}
};
browser.executeJavaScript("APP.conference.hangup();");
javax.swing.Timer timer = new javax.swing.Timer(1000 ,taskPerformer);
timer.setRepeats(false);
timer.start();
}
public void chatRoomLeft(ChatRoom chatroom)
{
}
public void chatRoomClosed(ChatRoom chatroom)
{
Localpart roomId = chatroom.getRoomJid().getLocalpart();
Log.debug("chatRoomClosed: " + roomId);
if (decorators.containsKey(roomId))
{
ChatRoomDecorator decorator = decorators.remove(roomId);
decorator.finished();
decorator = null;
}
if (browser != null)
{
browser.dispose();
browser = null;
}
}
public void chatRoomActivated(ChatRoom chatroom)
{
EntityBareJid roomId = chatroom.getRoomJid();
Log.debug("chatRoomActivated: " + roomId);
}
public void userHasJoined(ChatRoom room, String s)
{
EntityBareJid roomId = room.getRoomJid();
Log.debug("userHasJoined: " + roomId + " " + s);
}
public void userHasLeft(ChatRoom room, String s)
{
EntityBareJid roomId = room.getRoomJid();
Log.debug("userHasLeft: " + roomId + " " + s);
}
public void chatRoomOpened(final ChatRoom room)
{
EntityBareJid roomId = room.getRoomJid();
Log.debug("chatRoomOpened: " + roomId);
if (!decorators.containsKey(roomId))
{
decorators.put(roomId, new ChatRoomDecorator(room, url, this));
}
}
/**
* Display an alert that allows the user to accept or reject a meet
* invitation.
*/
private void showInvitationAlert(final String meetUrl, final ChatRoom room, final CharSequence roomId)
{
// Got an offer to start a new meet. So, make sure that a chat is
@ -389,11 +157,11 @@ public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageL
// Hide the response panel. TODO: make this work.
room.getTranscriptWindow().remove(inviteAlert);
inviteAlert.remove(1);
inviteAlert.add(new JLabel("Joining audio/conference conference ..."), BorderLayout.CENTER);
inviteAlert.add(new JLabel("Meeting at " + meetUrl), BorderLayout.CENTER);
declineButton.setEnabled(false);
acceptButton.setEnabled(false);
openUrl(meetUrl, roomId);
openURL(meetUrl);
}
});
buttonPanel.add(acceptButton);
@ -417,4 +185,252 @@ public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageL
// Add the response panel to the transcript window.
room.getTranscriptWindow().addComponent(inviteAlert);
}
@Override
public void messageSent(ChatRoom room, Message message) {
}
public boolean canShutDown()
{
return true;
}
public void uninstall()
{
}
public void handleClick(String newUrl, ChatRoom room, String url, Message.Type type)
{
if (electronThread != null)
{
electronThread.destory();
electronThread = null;
return;
}
sendInvite(room, url, type);
openURL(newUrl);
}
public void openURL(String newUrl)
{
checkNatives();
try {
String username = URLEncoder.encode(SparkManager.getSessionManager().getUsername(), "UTF-8");
String password = URLEncoder.encode(SparkManager.getSessionManager().getPassword(), "UTF-8");
electronThread = Spawn.startProcess(electronExePath + " --ignore-certificate-errors " + newUrl, new File(electronHomePath), new ProcessListener() {
public void onOutputLine(final String line) {
System.out.println(line);
}
public void onProcessQuit(int code) {
electronThread = null;
}
public void onOutputClosed() {
System.out.println("process completed");
}
public void onErrorLine(final String line) {
if (!line.contains("Corrupt JPEG data"))
{
Log.warning("Electron error " + line);
}
}
public void onError(final Throwable t) {
Log.warning("Electron error", t);
}
});
} catch (Exception t) {
Log.warning("Error opening url " + newUrl, t);
}
}
public void chatRoomLeft(ChatRoom chatroom)
{
}
public void chatRoomClosed(ChatRoom chatroom)
{
String roomId = chatroom.getRoomname().toString();
Log.warning("chatRoomClosed: " + roomId);
if (decorators.containsKey(roomId))
{
ChatRoomDecorator decorator = decorators.remove(roomId);
decorator.finished();
decorator = null;
}
if (electronThread != null)
{
electronThread.destory();
electronThread = null;
return;
}
}
public void chatRoomActivated(ChatRoom chatroom)
{
String roomId = chatroom.getRoomname().toString();
Log.warning("chatRoomActivated: " + roomId);
}
public void userHasJoined(ChatRoom room, String s)
{
String roomId = room.getRoomname().toString();
Log.warning("userHasJoined: " + roomId + " " + s);
}
public void userHasLeft(ChatRoom room, String s)
{
String roomId = room.getRoomname().toString();
Log.warning("userHasLeft: " + roomId + " " + s);
}
public void chatRoomOpened(final ChatRoom room)
{
String roomId = room.getRoomname().toString();
Log.warning("chatRoomOpened: " + roomId);
if (roomId.indexOf('/') == -1)
{
decorators.put(roomId, new ChatRoomDecorator(room, url, this));
}
}
private void checkNatives()
{
Log.warning("checkNatives");
// Find the root path of the class that will be our plugin lib folder.
try
{
String nativeLibsJarPath = Spark.getSparkUserHome() + File.separator + "plugins" + File.separator + "meet" + File.separator + "lib";
File nativeLibFolder = new File(nativeLibsJarPath, "native");
electronHomePath = nativeLibsJarPath + File.separator + "native";
electronExePath = electronHomePath + File.separator + "electron";
if(!nativeLibFolder.exists())
{
nativeLibFolder.mkdir();
String jarFileSuffix = null;
if(OSUtils.IS_LINUX32)
{
jarFileSuffix = "-linux-ia32.zip";
}
else if(OSUtils.IS_LINUX64)
{
jarFileSuffix = "-linux-x64.zip";
}
else if(OSUtils.IS_WINDOWS32)
{
jarFileSuffix = "-win32-ia32.zip";
}
else if(OSUtils.IS_WINDOWS64)
{
jarFileSuffix = "-win32-x64.zip";
}
else if(OSUtils.IS_MAC)
{
jarFileSuffix = "-darwin-x64.zip";
}
InputStream inputStream = new URL("https://github.com/electron/electron/releases/download/v10.1.1/electron-v10.1.1" + jarFileSuffix).openStream();
ZipInputStream zipIn = new ZipInputStream(inputStream);
ZipEntry entry = zipIn.getNextEntry();
while (entry != null)
{
try
{
String filePath = electronHomePath + File.separator + entry.getName();
Log.warning("writing file..." + filePath);
if (!entry.isDirectory())
{
File file = new File(filePath);
file.setReadable(true, true);
file.setWritable(true, true);
file.setExecutable(true, true);
new File(file.getParent()).mkdirs();
extractFile(zipIn, filePath);
}
zipIn.closeEntry();
entry = zipIn.getNextEntry();
}
catch(Exception e) {
Log.error("Error", e);
}
}
zipIn.close();
Log.warning("Native lib folder created and natives extracted");
}
else {
Log.warning("Native lib folder already exist.");
}
String libPath = nativeLibFolder.getCanonicalPath();
if (!System.getProperty("java.library.path").contains(libPath))
{
String newLibPath = libPath + File.pathSeparator + System.getProperty("java.library.path");
System.setProperty("java.library.path", newLibPath);
// this will reload the new setting
Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
fieldSysPath.setAccessible(true);
fieldSysPath.set(System.class.getClassLoader(), null);
}
}
catch (Exception e)
{
Log.warning(e.getMessage(), e);
}
}
private void extractFile(ZipInputStream zipIn, String filePath) throws IOException
{
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
byte[] bytesIn = new byte[4096];
int read = 0;
while ((read = zipIn.read(bytesIn)) != -1)
{
bos.write(bytesIn, 0, read);
}
bos.close();
}
private void sendInvite(ChatRoom room, String url, Message.Type type)
{
Message message2 = new Message();
message2.setTo(room.getRoomname().toString());
message2.setType(type);
message2.setBody(url);
room.sendMessage(message2);
}
}

View File

@ -2,7 +2,7 @@
<name>${project.name}</name>
<version>${project.version}</version>
<description>${project.description}</description>
<author>Ignite Realtime</author>
<author>Dele Olajide</author>
<homePage>http://igniterealtime.org</homePage>
<email>support@igniterealtime.org</email>
<class>org.jivesoftware.spark.plugin.ofmeet.SparkMeetPlugin</class>

View File

@ -77,7 +77,7 @@
<module>plugins/flashing</module>
<module>plugins/growl</module>
<!--<module>plugins/jingle</module>-->
<!--<module>plugins/meet</module>-->
<module>plugins/meet</module>
<!--<module>plugins/otr</module>-->
<module>plugins/reversi</module>
<module>plugins/roar</module>