Browse Source

Move XMPP Parser to parsers.

pull/64/head
Greg Holmes 9 years ago
parent
commit
832ceccbe4

+ 1
- 0
settings.gradle View File

@@ -1,2 +1,3 @@
1 1
 include 'common'
2 2
 include 'irc'
3
+include 'xmpp'

+ 22
- 0
xmpp/build.gradle View File

@@ -0,0 +1,22 @@
1
+configurations {
2
+    bundle
3
+
4
+    compile {
5
+        extendsFrom bundle
6
+    }
7
+}
8
+
9
+dependencies {
10
+    compile find("common")
11
+    bundle group: 'org.igniterealtime.smack', name: 'smack', version: '3.2.1'
12
+    bundle group: 'org.igniterealtime.smack', name: 'smackx', version: '3.2.1'
13
+    compile group: 'com.google.guava', name:'guava', version: '18.0'
14
+    compile group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.0'
15
+    compile group: 'org.slf4j', name: 'slf4j-api', version:'1.7.9'
16
+}
17
+
18
+jar {
19
+    from { configurations.bundle.collect { it.isDirectory() ? it : zipTree(it) } } {
20
+        exclude 'META-INF/**'
21
+    }
22
+}

+ 117
- 0
xmpp/src/com/dmdirc/parser/xmpp/FixedXmppConnection.java View File

@@ -0,0 +1,117 @@
1
+/*
2
+ * Copyright (c) 2006-2015 DMDirc Developers
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.parser.xmpp;
24
+
25
+import java.lang.reflect.Field;
26
+import java.util.HashSet;
27
+import java.util.Set;
28
+
29
+import org.jivesoftware.smack.Connection;
30
+import org.jivesoftware.smack.ConnectionConfiguration;
31
+import org.jivesoftware.smack.ConnectionCreationListener;
32
+import org.jivesoftware.smack.XMPPConnection;
33
+import org.jivesoftware.smack.XMPPException;
34
+import org.jivesoftware.smackx.ServiceDiscoveryManager;
35
+
36
+/**
37
+ * An extension of XMPPConnection that hacks around the problems caused by Smack's ridiculous use of
38
+ * static initialisers (and the dependencies between them). See
39
+ * http://issues.igniterealtime.org/browse/SMACK-315 for info.
40
+ */
41
+public class FixedXmppConnection extends XMPPConnection
42
+        implements ConnectionCreationListener {
43
+
44
+    /** A set of listeners that were registered with the XMPP Parser. */
45
+    private final Set<ConnectionCreationListener> oldListeners = new HashSet<>();
46
+
47
+    /**
48
+     * Creates a new fixed XMPP connection with the specified config.
49
+     *
50
+     * @param config The config to pass on to the underlying connection
51
+     *
52
+     * @see XMPPConnection#XMPPConnection(org.jivesoftware.smack.ConnectionConfiguration)
53
+     */
54
+    public FixedXmppConnection(final ConnectionConfiguration config) {
55
+        super(config);
56
+    }
57
+
58
+    @Override
59
+    public void connect() throws XMPPException {
60
+        // We're fiddling with a static array
61
+        synchronized (FixedXmppConnection.class) {
62
+            final Set<ConnectionCreationListener> rawListeners = getListeners();
63
+
64
+            // Copy them so we can clear and restore later
65
+            oldListeners.addAll(rawListeners);
66
+
67
+            // Get rid of everything and add ourselves.
68
+            rawListeners.clear();
69
+            rawListeners.add(this);
70
+
71
+            try {
72
+                super.connect();
73
+            } finally {
74
+                // Restore to the previous state
75
+                rawListeners.remove(this);
76
+                rawListeners.addAll(oldListeners);
77
+                oldListeners.clear();
78
+            }
79
+        }
80
+    }
81
+
82
+    @Override
83
+    @SuppressWarnings("ResultOfObjectAllocationIgnored")
84
+    public void connectionCreated(final Connection xmppc) {
85
+        // Creating this shoves itself into a static map. Other listeners
86
+        // depend on this entry existing in the map before they're called.
87
+        new ServiceDiscoveryManager(xmppc);
88
+
89
+        // Now execute our real listeners
90
+        // We've already created a discovery manager, don't make another...
91
+        oldListeners.stream()
92
+                .filter(listener -> !ServiceDiscoveryManager.class
93
+                        .equals(listener.getClass().getEnclosingClass()))
94
+                .forEach(listener -> listener.connectionCreated(xmppc));
95
+    }
96
+
97
+    /**
98
+     * Retrieves the set of listeners that have been registered statically with the XMPPConnection
99
+     * class.
100
+     *
101
+     * @return The raw listener set.
102
+     */
103
+    @SuppressWarnings("unchecked")
104
+    private Set<ConnectionCreationListener> getListeners() {
105
+        try {
106
+            final Field field = XMPPConnection.class.getDeclaredField(
107
+                    "connectionEstablishedListeners");
108
+            field.setAccessible(true);
109
+            return (Set<ConnectionCreationListener>) field.get(null);
110
+        } catch (ReflectiveOperationException ex) {
111
+            // Throws a bunch of exceptions... Try to carry on anyway, the
112
+            // horrible concurrency bugs might not bite.
113
+            return new HashSet<>();
114
+        }
115
+    }
116
+
117
+}

+ 84
- 0
xmpp/src/com/dmdirc/parser/xmpp/XmppChannelClientInfo.java View File

@@ -0,0 +1,84 @@
1
+/*
2
+ * Copyright (c) 2006-2015 DMDirc Developers
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.parser.xmpp;
24
+
25
+import com.dmdirc.parser.common.BaseChannelClientInfo;
26
+import com.dmdirc.parser.interfaces.ChannelClientInfo;
27
+import com.dmdirc.parser.interfaces.ChannelInfo;
28
+import com.dmdirc.parser.interfaces.ClientInfo;
29
+
30
+import java.util.Comparator;
31
+
32
+import javax.annotation.Nonnull;
33
+
34
+/**
35
+ * An XMPP-specific channel client info object.
36
+ */
37
+public class XmppChannelClientInfo extends BaseChannelClientInfo {
38
+
39
+    /**
40
+     * Creates a new client info object for the specified channel and client.
41
+     *
42
+     * @param channel The channel the association is with
43
+     * @param client  The user that holds the association
44
+     */
45
+    public XmppChannelClientInfo(final ChannelInfo channel, final ClientInfo client) {
46
+        super(channel, client);
47
+    }
48
+
49
+    @Override
50
+    public String getImportantModePrefix() {
51
+        return ""; // TODO: Implement
52
+    }
53
+
54
+    @Override
55
+    public String getImportantMode() {
56
+        return ""; // TODO: Implement
57
+    }
58
+
59
+    @Override
60
+    public String getAllModes() {
61
+        return ""; // TODO: Implement
62
+    }
63
+
64
+    @Override
65
+    public String getAllModesPrefix() {
66
+        return ""; // TODO: Implement
67
+    }
68
+
69
+    @Override
70
+    public void kick(final String message) {
71
+        throw new UnsupportedOperationException("Not supported yet.");
72
+    }
73
+
74
+    @Override
75
+    public Comparator<String> getImportantModeComparator() {
76
+        return Comparator.naturalOrder();
77
+    }
78
+
79
+    @Override
80
+    public int compareTo(@Nonnull final ChannelClientInfo o) {
81
+        return 0; // TODO: Implement
82
+    }
83
+
84
+}

+ 185
- 0
xmpp/src/com/dmdirc/parser/xmpp/XmppClientInfo.java View File

@@ -0,0 +1,185 @@
1
+/*
2
+ * Copyright (c) 2006-2015 DMDirc Developers
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.parser.xmpp;
24
+
25
+import com.dmdirc.parser.common.AwayState;
26
+import com.dmdirc.parser.common.BaseClientInfo;
27
+import com.dmdirc.parser.interfaces.ChannelClientInfo;
28
+
29
+import com.google.common.collect.Lists;
30
+
31
+import java.util.ArrayList;
32
+import java.util.Collections;
33
+import java.util.HashMap;
34
+import java.util.HashSet;
35
+import java.util.List;
36
+import java.util.Map;
37
+import java.util.Set;
38
+
39
+import org.jivesoftware.smack.RosterEntry;
40
+import org.jivesoftware.smack.packet.Presence;
41
+import org.jivesoftware.smack.packet.Presence.Mode;
42
+import org.jivesoftware.smack.packet.Presence.Type;
43
+
44
+/**
45
+ * An XMPP-specific client info object.
46
+ */
47
+public class XmppClientInfo extends BaseClientInfo {
48
+
49
+    /** A map of known endpoints for this client. */
50
+    private final Map<String, XmppEndpoint> endpoints = new HashMap<>();
51
+
52
+    /**
53
+     * Creates a new XMPP Client Info object with the specified details.
54
+     *
55
+     * @param parser The parser that owns this client info object
56
+     * @param nick   The nickname of the user this object represents
57
+     * @param user   The username of the user this object represents
58
+     * @param host   The hostname of the user this object represents
59
+     */
60
+    public XmppClientInfo(final XmppParser parser, final String nick, final String user,
61
+            final String host) {
62
+        super(parser, nick, user, host);
63
+    }
64
+
65
+    @Override
66
+    public int getChannelCount() {
67
+        return 0;
68
+    }
69
+
70
+    @Override
71
+    public List<ChannelClientInfo> getChannelClients() {
72
+        return Lists.newArrayList();
73
+    }
74
+
75
+    /**
76
+     * Retrieves the map of known endpoints for this client.
77
+     *
78
+     * @return A map of endpoint names to presence states.
79
+     */
80
+    public Map<String, XmppEndpoint> getEndpoints() {
81
+        return Collections.unmodifiableMap(endpoints);
82
+    }
83
+
84
+    /**
85
+     * Sets the presence of one endpoint of this client.
86
+     *
87
+     * @param packet The presence packet received
88
+     */
89
+    public void setPresence(final Presence packet) {
90
+        final String[] parts = getParser().parseHostmask(packet.getFrom());
91
+        final boolean wasAway = isAway();
92
+
93
+        if (packet.getType() == Type.unavailable) {
94
+            endpoints.remove(parts[1]);
95
+        } else {
96
+            if (!endpoints.containsKey(parts[1])) {
97
+                endpoints.put(parts[1], new XmppEndpoint());
98
+            }
99
+
100
+            final XmppEndpoint endpoint = endpoints.get(parts[1]);
101
+            endpoint.setPriority(packet.getPriority());
102
+            endpoint.setMode(packet.getMode());
103
+            endpoint.setType(packet.getType());
104
+            endpoint.setStatus(packet.getStatus());
105
+        }
106
+
107
+        final boolean isAway = isAway();
108
+
109
+        if (wasAway != isAway) {
110
+            // Their away status has changed
111
+            getParser().handleAwayStateChange(this, wasAway);
112
+        }
113
+    }
114
+
115
+    /**
116
+     * Determines whether or not this contact is "away" - that is, their aggregate presence state is
117
+     * "Unavailable", "Away", "DND" or "XA".
118
+     *
119
+     * @return True if the client is away, false otherwise
120
+     */
121
+    public boolean isAway() {
122
+        final Mode presence = getAggregatePresence();
123
+
124
+        return presence == null || presence.compareTo(Mode.away) >= 0;
125
+    }
126
+
127
+    @Override
128
+    public AwayState getAwayState() {
129
+        return isAway() ? AwayState.AWAY : AwayState.HERE;
130
+    }
131
+
132
+    /**
133
+     * Gets the aggregate presence of this contact.
134
+     *
135
+     * @return The highest available state of the contact's highest priority endpoints, * or
136
+     *         <code>null</code> if no endpoints are available.
137
+     */
138
+    public Mode getAggregatePresence() {
139
+        final List<XmppEndpoint> sortedEndpoints = new ArrayList<>(endpoints.values());
140
+        Collections.sort(sortedEndpoints);
141
+
142
+        // Get the set of the highest priority endpoints
143
+        final Set<XmppEndpoint> bestEndpoints = new HashSet<>(sortedEndpoints.size());
144
+        int best = 0;
145
+        for (XmppEndpoint endpoint : sortedEndpoints) {
146
+            if (endpoint.getType() != Type.available) {
147
+                continue;
148
+            }
149
+
150
+            if (bestEndpoints.isEmpty()) {
151
+                best = endpoint.getPriority();
152
+            }
153
+
154
+            if (endpoint.getPriority() == best && best >= 0) {
155
+                bestEndpoints.add(endpoint);
156
+            }
157
+        }
158
+
159
+        // Find the best presence
160
+        Mode bestPresence = null;
161
+        for (XmppEndpoint endpoint : bestEndpoints) {
162
+            final Mode targetMode = endpoint.getMode() == null ? Mode.available : endpoint.getMode();
163
+            if (bestPresence == null || bestPresence.compareTo(targetMode) > 0) {
164
+                bestPresence = targetMode;
165
+            }
166
+        }
167
+
168
+        return bestPresence;
169
+    }
170
+
171
+    /**
172
+     * Updates this client with information from the roster.
173
+     *
174
+     * @param entry The roster entry to update this client with
175
+     */
176
+    public void setRosterEntry(final RosterEntry entry) {
177
+        setRealname(entry.getName());
178
+    }
179
+
180
+    @Override
181
+    public XmppParser getParser() {
182
+        return (XmppParser) super.getParser();
183
+    }
184
+
185
+}

+ 163
- 0
xmpp/src/com/dmdirc/parser/xmpp/XmppEndpoint.java View File

@@ -0,0 +1,163 @@
1
+/*
2
+ * Copyright (c) 2006-2015 DMDirc Developers
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.parser.xmpp;
24
+
25
+import javax.annotation.Nonnull;
26
+
27
+import org.jivesoftware.smack.packet.Presence;
28
+import org.jivesoftware.smack.packet.Presence.Mode;
29
+import org.jivesoftware.smack.packet.Presence.Type;
30
+
31
+/**
32
+ * Represents a single endpoint of one contact.
33
+ */
34
+public class XmppEndpoint implements Comparable<XmppEndpoint> {
35
+
36
+    /** The mode of the last presence update. */
37
+    private Presence.Mode mode;
38
+    /** The type of the last presence update. */
39
+    private Presence.Type type;
40
+    /** The status from the last presence update. */
41
+    private String status;
42
+    /** The priority of this endpoint. */
43
+    private int priority;
44
+
45
+    /**
46
+     * Gets the mode of this endpoint.
47
+     *
48
+     * @return The last seen presence mode
49
+     */
50
+    public Mode getMode() {
51
+        return mode;
52
+    }
53
+
54
+    /**
55
+     * Sets the mode of this endpoint.
56
+     *
57
+     * @param mode The most recently received presence mode
58
+     */
59
+    public void setMode(final Mode mode) {
60
+        this.mode = mode;
61
+    }
62
+
63
+    /**
64
+     * Gets the status of this endpoint.
65
+     *
66
+     * @return The last seen presence status
67
+     */
68
+    public String getStatus() {
69
+        return status;
70
+    }
71
+
72
+    /**
73
+     * Sets the status of this endpoint.
74
+     *
75
+     * @param status The most recently received presence status
76
+     */
77
+    public void setStatus(final String status) {
78
+        this.status = status;
79
+    }
80
+
81
+    /**
82
+     * Gets the type of this endpoint.
83
+     *
84
+     * @return The last seen presence type
85
+     */
86
+    public Type getType() {
87
+        return type;
88
+    }
89
+
90
+    /**
91
+     * Sets the type of this endpoint.
92
+     *
93
+     * @param type The most recently received presence type
94
+     */
95
+    public void setType(final Type type) {
96
+        this.type = type;
97
+    }
98
+
99
+    /**
100
+     * Gets the priority of this endpoint.
101
+     *
102
+     * @return The priority of this endpoint
103
+     */
104
+    public int getPriority() {
105
+        return priority;
106
+    }
107
+
108
+    /**
109
+     * Sets the priority of this endpoint.
110
+     *
111
+     * @param priority The new priority for this endpoint
112
+     */
113
+    public void setPriority(final int priority) {
114
+        this.priority = priority;
115
+    }
116
+
117
+    /**
118
+     * Gets a user-friendly string describing this endpoint's presence.
119
+     *
120
+     * @return The presence of this endpoint in a user-friendly format
121
+     */
122
+    public String getPresence() {
123
+        final String statusText = (status == null || status.isEmpty()) ? "" : " (" + status + ")";
124
+
125
+        if (type == Type.unavailable) {
126
+            return "Unavailable" + statusText;
127
+        } else if (mode == null) {
128
+            return "Available" + statusText;
129
+        } else {
130
+            return getFriendlyMode(mode) + statusText;
131
+        }
132
+    }
133
+
134
+    /**
135
+     * Returns a friendly representation of the specified presence mode.
136
+     *
137
+     * @param mode The mode being represented
138
+     *
139
+     * @return A user-friendly string corresponding to the mode
140
+     */
141
+    private static String getFriendlyMode(final Mode mode) {
142
+        switch (mode) {
143
+            case available:
144
+                return "Available";
145
+            case away:
146
+                return "Away";
147
+            case chat:
148
+                return "Chatty";
149
+            case dnd:
150
+                return "Do not disturb";
151
+            case xa:
152
+                return "Extended Away";
153
+            default:
154
+                return mode.toString();
155
+        }
156
+    }
157
+
158
+    @Override
159
+    public int compareTo(@Nonnull final XmppEndpoint o) {
160
+        return o.getPriority() - priority;
161
+    }
162
+
163
+}

+ 141
- 0
xmpp/src/com/dmdirc/parser/xmpp/XmppFakeChannel.java View File

@@ -0,0 +1,141 @@
1
+/*
2
+ * Copyright (c) 2006-2015 DMDirc Developers
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.parser.xmpp;
24
+
25
+import com.dmdirc.parser.common.BaseChannelInfo;
26
+import com.dmdirc.parser.common.ChannelListModeItem;
27
+import com.dmdirc.parser.interfaces.ChannelClientInfo;
28
+import com.dmdirc.parser.interfaces.ClientInfo;
29
+import com.dmdirc.parser.interfaces.Parser;
30
+import com.dmdirc.parser.interfaces.callbacks.ChannelNamesListener;
31
+
32
+import java.util.Collection;
33
+import java.util.Date;
34
+
35
+/**
36
+ * A 'fake' local channel used to display buddy lists.
37
+ */
38
+public class XmppFakeChannel extends BaseChannelInfo {
39
+
40
+    /**
41
+     * Creates a new fake channel belonging to the specified parser and with the given name.
42
+     *
43
+     * @param parser The XMPP parser that owns this channel
44
+     * @param name   The name of the channel
45
+     */
46
+    public XmppFakeChannel(final Parser parser, final String name) {
47
+        super(parser, name);
48
+    }
49
+
50
+    @Override
51
+    public void setTopic(final String topic) {
52
+        throw new UnsupportedOperationException("Not supported yet.");
53
+    }
54
+
55
+    @Override
56
+    public String getTopic() {
57
+        return "No topic yet!";
58
+    }
59
+
60
+    @Override
61
+    public long getTopicTime() {
62
+        return System.currentTimeMillis(); // TODO
63
+    }
64
+
65
+    @Override
66
+    public String getTopicSetter() {
67
+        return ""; // TODO
68
+    }
69
+
70
+    @Override
71
+    public String getModes() {
72
+        return ""; // TODO
73
+    }
74
+
75
+    @Override
76
+    public String getMode(final char mode) {
77
+        throw new UnsupportedOperationException("Not supported yet.");
78
+    }
79
+
80
+    @Override
81
+    public Collection<ChannelListModeItem> getListMode(final char mode) {
82
+        throw new UnsupportedOperationException("Not supported yet.");
83
+    }
84
+
85
+    @Override
86
+    public void part(final String reason) {
87
+        throw new UnsupportedOperationException("Not supported yet.");
88
+    }
89
+
90
+    @Override
91
+    public void sendWho() {
92
+        throw new UnsupportedOperationException("Not supported yet.");
93
+    }
94
+
95
+    @Override
96
+    public void alterMode(final boolean add, final Character mode, final String parameter) {
97
+        throw new UnsupportedOperationException("Not supported yet.");
98
+    }
99
+
100
+    @Override
101
+    public void flushModes() {
102
+        throw new UnsupportedOperationException("Not supported yet.");
103
+    }
104
+
105
+    @Override
106
+    public void requestListModes() {
107
+        throw new UnsupportedOperationException("Not supported yet.");
108
+    }
109
+
110
+    @Override
111
+    public ChannelClientInfo getChannelClient(final ClientInfo client) {
112
+        return getClient(client.getNickname());
113
+    }
114
+
115
+    @Override
116
+    public ChannelClientInfo getChannelClient(final String client, final boolean create) {
117
+        final String[] parts = getParser().parseHostmask(client);
118
+
119
+        if (create && getClient(parts[0]) == null) {
120
+            return new XmppChannelClientInfo(this, getParser().getClient(client));
121
+        }
122
+
123
+        return getClient(parts[0]);
124
+    }
125
+
126
+    /**
127
+     * Updates this channel with the specified contacts.
128
+     *
129
+     * @param clients The contacts to be added
130
+     */
131
+    public void updateContacts(final Collection<XmppClientInfo> clients) {
132
+        for (XmppClientInfo client : clients) {
133
+            addClient(client.getNickname(), new XmppChannelClientInfo(this, client));
134
+        }
135
+
136
+        // TODO: Delete old contacts, don't needlessly create new objects
137
+        getParser().getCallbackManager().getCallback(ChannelNamesListener.class)
138
+                .onChannelGotNames(getParser(), new Date(), this);
139
+    }
140
+
141
+}

+ 75
- 0
xmpp/src/com/dmdirc/parser/xmpp/XmppLocalClientInfo.java View File

@@ -0,0 +1,75 @@
1
+/*
2
+ * Copyright (c) 2006-2015 DMDirc Developers
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.parser.xmpp;
24
+
25
+import com.dmdirc.parser.interfaces.LocalClientInfo;
26
+
27
+/**
28
+ * A representation of the local XMPP client.
29
+ */
30
+public class XmppLocalClientInfo extends XmppClientInfo implements LocalClientInfo {
31
+
32
+    /**
33
+     * Creates a new XMPP Client Info object with the specified details.
34
+     *
35
+     * @param parser The parser that owns this client info object
36
+     * @param nick   The nickname of the user this object represents
37
+     * @param user   The username of the user this object represents
38
+     * @param host   The hostname of the user this object represents
39
+     */
40
+    public XmppLocalClientInfo(final XmppParser parser, final String nick,
41
+            final String user, final String host) {
42
+        super(parser, nick, user, host);
43
+    }
44
+
45
+    @Override
46
+    public void setNickname(final String name) {
47
+        throw new UnsupportedOperationException("Not supported yet.");
48
+    }
49
+
50
+    @Override
51
+    public String getModes() {
52
+        throw new UnsupportedOperationException("Not supported yet.");
53
+    }
54
+
55
+    @Override
56
+    public void setAway(final String reason) {
57
+        getParser().setAway(reason);
58
+    }
59
+
60
+    @Override
61
+    public void setBack() {
62
+        getParser().setBack();
63
+    }
64
+
65
+    @Override
66
+    public void alterMode(final boolean add, final Character mode) {
67
+        throw new UnsupportedOperationException("Not supported yet.");
68
+    }
69
+
70
+    @Override
71
+    public void flushModes() {
72
+        throw new UnsupportedOperationException("Not supported yet.");
73
+    }
74
+
75
+}

+ 718
- 0
xmpp/src/com/dmdirc/parser/xmpp/XmppParser.java View File

@@ -0,0 +1,718 @@
1
+/*
2
+ * Copyright (c) 2006-2015 DMDirc Developers
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.parser.xmpp;
24
+
25
+import com.dmdirc.parser.common.AwayState;
26
+import com.dmdirc.parser.common.BaseSocketAwareParser;
27
+import com.dmdirc.parser.common.ChannelJoinRequest;
28
+import com.dmdirc.parser.common.ChildImplementations;
29
+import com.dmdirc.parser.common.CompositionState;
30
+import com.dmdirc.parser.common.DefaultStringConverter;
31
+import com.dmdirc.parser.common.ParserError;
32
+import com.dmdirc.parser.common.QueuePriority;
33
+import com.dmdirc.parser.interfaces.ChannelInfo;
34
+import com.dmdirc.parser.interfaces.ClientInfo;
35
+import com.dmdirc.parser.interfaces.LocalClientInfo;
36
+import com.dmdirc.parser.interfaces.StringConverter;
37
+import com.dmdirc.parser.interfaces.callbacks.AwayStateListener;
38
+import com.dmdirc.parser.interfaces.callbacks.CallbackInterface;
39
+import com.dmdirc.parser.interfaces.callbacks.ChannelSelfJoinListener;
40
+import com.dmdirc.parser.interfaces.callbacks.CompositionStateChangeListener;
41
+import com.dmdirc.parser.interfaces.callbacks.ConnectErrorListener;
42
+import com.dmdirc.parser.interfaces.callbacks.DataInListener;
43
+import com.dmdirc.parser.interfaces.callbacks.DataOutListener;
44
+import com.dmdirc.parser.interfaces.callbacks.NumericListener;
45
+import com.dmdirc.parser.interfaces.callbacks.OtherAwayStateListener;
46
+import com.dmdirc.parser.interfaces.callbacks.PrivateActionListener;
47
+import com.dmdirc.parser.interfaces.callbacks.PrivateMessageListener;
48
+import com.dmdirc.parser.interfaces.callbacks.ServerReadyListener;
49
+import com.dmdirc.parser.interfaces.callbacks.SocketCloseListener;
50
+
51
+import java.net.URI;
52
+import java.util.Collection;
53
+import java.util.Collections;
54
+import java.util.Date;
55
+import java.util.HashMap;
56
+import java.util.List;
57
+import java.util.Map;
58
+import java.util.regex.Matcher;
59
+import java.util.regex.Pattern;
60
+
61
+import org.jivesoftware.smack.Chat;
62
+import org.jivesoftware.smack.ChatManagerListener;
63
+import org.jivesoftware.smack.ConnectionConfiguration;
64
+import org.jivesoftware.smack.ConnectionListener;
65
+import org.jivesoftware.smack.PacketListener;
66
+import org.jivesoftware.smack.RosterEntry;
67
+import org.jivesoftware.smack.RosterListener;
68
+import org.jivesoftware.smack.XMPPConnection;
69
+import org.jivesoftware.smack.XMPPException;
70
+import org.jivesoftware.smack.filter.PacketFilter;
71
+import org.jivesoftware.smack.packet.Message;
72
+import org.jivesoftware.smack.packet.Packet;
73
+import org.jivesoftware.smack.packet.Presence;
74
+import org.jivesoftware.smackx.ChatState;
75
+import org.jivesoftware.smackx.ChatStateListener;
76
+import org.jivesoftware.smackx.ChatStateManager;
77
+import org.jivesoftware.smackx.muc.MultiUserChat;
78
+import org.slf4j.LoggerFactory;
79
+
80
+/**
81
+ * A parser which can understand the XMPP protocol.
82
+ */
83
+@ChildImplementations({
84
+    XmppClientInfo.class, XmppLocalClientInfo.class, XmppFakeChannel.class,
85
+    XmppChannelClientInfo.class
86
+})
87
+public class XmppParser extends BaseSocketAwareParser {
88
+
89
+    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(XmppParser.class);
90
+    /** Pattern to use to extract priority. */
91
+    private static final Pattern PRIORITY_PATTERN = Pattern.compile(
92
+            "(?i)(?:^|&)priority=([0-9]+)(?:$|&)");
93
+    /** The connection to use. */
94
+    private XMPPConnection connection;
95
+    /** The state manager for the current connection. */
96
+    private ChatStateManager stateManager;
97
+    /** A cache of known chats. */
98
+    private final Map<String, Chat> chats = new HashMap<>();
99
+    /** A cache of known clients. */
100
+    private final Map<String, XmppClientInfo> contacts = new HashMap<>();
101
+    /** Whether or not to use a fake local channel for a buddy list replacement. */
102
+    private final boolean useFakeChannel;
103
+    /** The priority of this endpoint. */
104
+    private final int priority;
105
+    /** The fake channel to use is useFakeChannel is enabled. */
106
+    private XmppFakeChannel fakeChannel;
107
+
108
+    /**
109
+     * Creates a new XMPP parser for the specified address.
110
+     *
111
+     * @param address The address to connect to
112
+     */
113
+    public XmppParser(final URI address) {
114
+        super(address);
115
+
116
+        if (address.getQuery() == null) {
117
+            useFakeChannel = false;
118
+            priority = 0;
119
+        } else {
120
+            final Matcher matcher = PRIORITY_PATTERN.matcher(address.getQuery());
121
+
122
+            useFakeChannel = address.getQuery().matches("(?i).*(^|&)showchannel($|&).*");
123
+            priority = matcher.find() ? Integer.parseInt(matcher.group(1)) : 0;
124
+        }
125
+
126
+        LOG.debug(
127
+                "XMPP parser created with query string {}, parsed fake channel = {}, priority = {}",
128
+                address.getQuery(), useFakeChannel, priority);
129
+    }
130
+
131
+    @Override
132
+    public void disconnect(final String message) {
133
+        super.disconnect(message);
134
+        // TODO: Pass quit message on as presence?
135
+        connection.disconnect();
136
+    }
137
+
138
+    @Override
139
+    public void joinChannels(final ChannelJoinRequest... channels) {
140
+        for (ChannelJoinRequest request : channels) {
141
+            final MultiUserChat muc = new MultiUserChat(connection, request.getName().substring(1));
142
+
143
+            try {
144
+                if (request.getPassword() == null) {
145
+                    muc.join(getLocalClient().getNickname());
146
+                } else {
147
+                    muc.join(getLocalClient().getNickname(), request.getPassword());
148
+                }
149
+
150
+                // TODO: Send callbacks etc
151
+            } catch (XMPPException ex) {
152
+                // TODO: handle
153
+            }
154
+        }
155
+    }
156
+
157
+    @Override
158
+    public ChannelInfo getChannel(final String channel) {
159
+        // TODO: Implement
160
+        throw new UnsupportedOperationException("Not supported yet.");
161
+    }
162
+
163
+    @Override
164
+    public Collection<? extends ChannelInfo> getChannels() {
165
+        return Collections.<ChannelInfo>emptyList();
166
+    }
167
+
168
+    @Override
169
+    public int getMaxLength(final String type, final String target) {
170
+        return Integer.MAX_VALUE;
171
+    }
172
+
173
+    @Override
174
+    public int getMaxLength() {
175
+        return Integer.MAX_VALUE;
176
+    }
177
+
178
+    @Override
179
+    public LocalClientInfo getLocalClient() {
180
+        final String[] parts = parseHostmask(connection.getUser());
181
+
182
+        // TODO: Cache this
183
+        return new XmppLocalClientInfo(this, parts[0], parts[2], parts[1]);
184
+    }
185
+
186
+    @Override
187
+    public XmppClientInfo getClient(final String details) {
188
+        final String[] parts = parseHostmask(details);
189
+
190
+        if (!contacts.containsKey(parts[0])) {
191
+            contacts.put(parts[0], new XmppClientInfo(this, parts[0], parts[2], parts[1]));
192
+        }
193
+
194
+        return contacts.get(parts[0]);
195
+    }
196
+
197
+    @Override
198
+    public void sendRawMessage(final String message) {
199
+        // Urgh, hacky horrible rubbish. These commands should call methods.
200
+        if (message.toUpperCase().startsWith("WHOIS ")) {
201
+            handleWhois(message.split(" ")[1]);
202
+        } else if (!message.isEmpty() && message.charAt(0) == '<') {
203
+            // Looks vaguely like XML, let's send it.
204
+            connection.sendPacket(new Packet() {
205
+
206
+                @Override
207
+                public String toXML() {
208
+                    return message;
209
+                }
210
+            });
211
+        }
212
+    }
213
+
214
+    /**
215
+     * Handles a whois request for the specified target.
216
+     *
217
+     * @param target The user being WHOIS'd
218
+     */
219
+    private void handleWhois(final String target) {
220
+        // Urgh, hacky horrible rubbish. This should be abstracted.
221
+        if (contacts.containsKey(target)) {
222
+            final XmppClientInfo client = contacts.get(target);
223
+            final String[] userParts = client.getNickname().split("@", 2);
224
+
225
+            callNumericCallback(311, target, userParts[0], userParts[1], "*", client.getRealname());
226
+
227
+            for (Map.Entry<String, XmppEndpoint> endpoint : client.getEndpoints().entrySet()) {
228
+                callNumericCallback(399, target, endpoint.getKey(),
229
+                        "(" + endpoint.getValue().getPresence() + ")", "has endpoint");
230
+            }
231
+        } else {
232
+            callNumericCallback(401, target, "No such contact found");
233
+        }
234
+
235
+        callNumericCallback(318, target, "End of /WHOIS.");
236
+    }
237
+
238
+    private void callNumericCallback(final int numeric, final String... args) {
239
+        final String[] newArgs = new String[args.length + 3];
240
+        newArgs[0] = ":xmpp.server";
241
+        newArgs[1] = (numeric < 100 ? "0" : "") + (numeric < 10 ? "0" : "") + numeric;
242
+        newArgs[2] = getLocalClient().getNickname();
243
+        System.arraycopy(args, 0, newArgs, 3, args.length);
244
+
245
+        getCallback(NumericListener.class).onNumeric(this, new Date(), numeric, newArgs);
246
+    }
247
+
248
+    @Override
249
+    public void sendRawMessage(final String message, final QueuePriority priority) {
250
+        sendRawMessage(message);
251
+    }
252
+
253
+    @Override
254
+    public StringConverter getStringConverter() {
255
+        return new DefaultStringConverter();
256
+    }
257
+
258
+    @Override
259
+    public boolean isValidChannelName(final String name) {
260
+        return false; // TODO: Implement
261
+    }
262
+
263
+    @Override
264
+    public boolean compareURI(final URI uri) {
265
+        throw new UnsupportedOperationException("Not supported yet.");
266
+    }
267
+
268
+    @Override
269
+    public Collection<? extends ChannelJoinRequest> extractChannels(final URI uri) {
270
+        return Collections.<ChannelJoinRequest>emptyList();
271
+    }
272
+
273
+    @Override
274
+    public String getNetworkName() {
275
+        return "XMPP"; // TODO
276
+    }
277
+
278
+    @Override
279
+    public String getServerSoftware() {
280
+        return "Unknown"; // TODO
281
+    }
282
+
283
+    @Override
284
+    public String getServerSoftwareType() {
285
+        return "XMPP"; // TODO
286
+    }
287
+
288
+    @Override
289
+    public List<String> getServerInformationLines() {
290
+        return Collections.emptyList(); // TODO
291
+    }
292
+
293
+    @Override
294
+    public int getMaxTopicLength() {
295
+        return 0; // TODO
296
+    }
297
+
298
+    @Override
299
+    public String getBooleanChannelModes() {
300
+        return ""; // TODO
301
+    }
302
+
303
+    @Override
304
+    public String getListChannelModes() {
305
+        return ""; // TODO
306
+    }
307
+
308
+    @Override
309
+    public int getMaxListModes(final char mode) {
310
+        return 0; // TODO
311
+    }
312
+
313
+    @Override
314
+    public boolean isUserSettable(final char mode) {
315
+        throw new UnsupportedOperationException("Not supported yet.");
316
+    }
317
+
318
+    @Override
319
+    public String getParameterChannelModes() {
320
+        return ""; // TODO
321
+    }
322
+
323
+    @Override
324
+    public String getDoubleParameterChannelModes() {
325
+        return ""; // TODO
326
+    }
327
+
328
+    @Override
329
+    public String getUserModes() {
330
+        return ""; // TODO
331
+    }
332
+
333
+    @Override
334
+    public String getChannelUserModes() {
335
+        return ""; // TODO
336
+    }
337
+
338
+    @Override
339
+    public String getChannelPrefixes() {
340
+        return "#";
341
+    }
342
+
343
+    @Override
344
+    public long getServerLatency() {
345
+        return 1000L; // TODO
346
+    }
347
+
348
+    @Override
349
+    public void sendCTCP(final String target, final String type, final String message) {
350
+        throw new UnsupportedOperationException("Not supported yet.");
351
+    }
352
+
353
+    @Override
354
+    public void sendCTCPReply(final String target, final String type, final String message) {
355
+        throw new UnsupportedOperationException("Not supported yet.");
356
+    }
357
+
358
+    @Override
359
+    public void sendMessage(final String target, final String message) {
360
+        if (!chats.containsKey(target)) {
361
+            LOG.debug("Creating new chat for {}", target);
362
+            chats.put(target, connection.getChatManager().createChat(target,
363
+                    new MessageListenerImpl()));
364
+        }
365
+
366
+        try {
367
+            chats.get(target).sendMessage(message);
368
+        } catch (XMPPException ex) {
369
+            // TODO: Handle this
370
+        }
371
+    }
372
+
373
+    @Override
374
+    public void sendNotice(final String target, final String message) {
375
+        throw new UnsupportedOperationException("Not supported yet.");
376
+    }
377
+
378
+    @Override
379
+    public void sendAction(final String target, final String message) {
380
+        sendMessage(target, "/me " + message);
381
+    }
382
+
383
+    @Override
384
+    public void sendInvite(final String channel, final String user) {
385
+        throw new UnsupportedOperationException("Not supported yet.");
386
+    }
387
+
388
+    @Override
389
+    public void sendWhois(final String nickname) {
390
+        // TODO: Implement this
391
+    }
392
+
393
+    @Override
394
+    public String getLastLine() {
395
+        return "TODO: Implement me";
396
+    }
397
+
398
+    @Override
399
+    public String[] parseHostmask(final String hostmask) {
400
+        return new XmppProtocolDescription().parseHostmask(hostmask);
401
+    }
402
+
403
+    @Override
404
+    public long getPingTime() {
405
+        throw new UnsupportedOperationException("Not supported yet.");
406
+    }
407
+
408
+    @Override
409
+    public void run() {
410
+        if (getURI().getUserInfo() == null || !getURI().getUserInfo().contains(":")) {
411
+            getCallback(ConnectErrorListener.class).onConnectError(this,
412
+                    new Date(), new ParserError(ParserError.ERROR_USER,
413
+                            "User name and password must be specified in URI", ""));
414
+            return;
415
+        }
416
+        final String[] userInfoParts = getURI().getUserInfo().split(":", 2);
417
+        final String[] userParts = userInfoParts[0].split("@", 2);
418
+
419
+        final ConnectionConfiguration config = new ConnectionConfiguration(getURI().getHost(),
420
+                getURI().getPort(), userParts[0]);
421
+        config.setSecurityMode(getURI().getScheme().equalsIgnoreCase("xmpps")
422
+                ? ConnectionConfiguration.SecurityMode.required
423
+                : ConnectionConfiguration.SecurityMode.disabled);
424
+        config.setSASLAuthenticationEnabled(true);
425
+        config.setReconnectionAllowed(false);
426
+        config.setRosterLoadedAtLogin(true);
427
+        config.setSocketFactory(getSocketFactory());
428
+        connection = new FixedXmppConnection(config);
429
+
430
+        try {
431
+            connection.connect();
432
+
433
+            connection.addConnectionListener(new ConnectionListenerImpl());
434
+            connection.addPacketListener(new PacketListenerImpl(DataInListener.class),
435
+                    new AcceptAllPacketFilter());
436
+            connection.addPacketSendingListener(new PacketListenerImpl(DataOutListener.class),
437
+                    new AcceptAllPacketFilter());
438
+            connection.getChatManager().addChatListener(new ChatManagerListenerImpl());
439
+
440
+            try {
441
+                connection.login(userInfoParts[0], userInfoParts[1], "DMDirc.");
442
+            } catch (XMPPException ex) {
443
+                getCallback(ConnectErrorListener.class).onConnectError(this,
444
+                        new Date(), new ParserError(ParserError.ERROR_USER,
445
+                                ex.getMessage(), ""));
446
+                return;
447
+            }
448
+
449
+            connection.sendPacket(new Presence(Presence.Type.available, null, priority,
450
+                    Presence.Mode.available));
451
+            connection.getRoster().addRosterListener(new RosterListenerImpl());
452
+
453
+            stateManager = ChatStateManager.getInstance(connection);
454
+
455
+            setServerName(connection.getServiceName());
456
+
457
+            getCallback(ServerReadyListener.class).onServerReady(this, new Date());
458
+
459
+            for (RosterEntry contact : connection.getRoster().getEntries()) {
460
+                getClient(contact.getUser()).setRosterEntry(contact);
461
+            }
462
+
463
+            if (useFakeChannel) {
464
+                fakeChannel = new XmppFakeChannel(this, "&contacts");
465
+                getCallback(ChannelSelfJoinListener.class).
466
+                        onChannelSelfJoin(null, null, fakeChannel);
467
+                fakeChannel.updateContacts(contacts.values());
468
+
469
+                contacts.values().stream().filter(XmppClientInfo::isAway).forEach(client ->
470
+                        getCallback(OtherAwayStateListener.class).onAwayStateOther(this, new Date(),
471
+                                client, AwayState.UNKNOWN, AwayState.AWAY));
472
+            }
473
+        } catch (XMPPException ex) {
474
+            LOG.debug("Go an XMPP exception", ex);
475
+
476
+            connection = null;
477
+
478
+            final ParserError error = new ParserError(ParserError.ERROR_ERROR, "Unable to connect",
479
+                    "");
480
+
481
+            if (ex.getWrappedThrowable() instanceof Exception) {
482
+                // Pass along the underlying exception instead of an XMPP
483
+                // specific one
484
+                error.setException((Exception) ex.getWrappedThrowable());
485
+            } else {
486
+                error.setException(ex);
487
+            }
488
+
489
+            getCallback(ConnectErrorListener.class).onConnectError(this, new Date(), error);
490
+        }
491
+    }
492
+
493
+    /**
494
+     * Handles a client's away state changing.
495
+     *
496
+     * @param client The client whose state is changing
497
+     * @param isBack True if the client is coming back, false if they're going away
498
+     */
499
+    public void handleAwayStateChange(final ClientInfo client, final boolean isBack) {
500
+        LOG.debug("Handling away state change for {} to {}", client.getNickname(), isBack);
501
+
502
+        if (useFakeChannel) {
503
+            getCallback(OtherAwayStateListener.class)
504
+                    .onAwayStateOther(null, null, client,
505
+                            isBack ? AwayState.AWAY : AwayState.HERE,
506
+                            isBack ? AwayState.HERE : AwayState.AWAY);
507
+        }
508
+    }
509
+
510
+    /**
511
+     * Marks the local user as away with the specified reason.
512
+     *
513
+     * @param reason The away reason
514
+     */
515
+    public void setAway(final String reason) {
516
+        connection.sendPacket(new Presence(Presence.Type.available, reason,
517
+                priority, Presence.Mode.away));
518
+
519
+        getCallback(AwayStateListener.class).onAwayState(this, new Date(),
520
+                AwayState.HERE, AwayState.AWAY, reason);
521
+    }
522
+
523
+    /**
524
+     * Marks the local user as back.
525
+     */
526
+    public void setBack() {
527
+        connection.sendPacket(new Presence(Presence.Type.available, null,
528
+                priority, Presence.Mode.available));
529
+
530
+        getCallback(AwayStateListener.class).onAwayState(this, new Date(),
531
+                AwayState.AWAY, AwayState.HERE, null);
532
+    }
533
+
534
+    @Override
535
+    public void setCompositionState(final String host, final CompositionState state) {
536
+        LOG.debug("Setting composition state for {} to {}", host, state);
537
+
538
+        final Chat chat = chats.get(parseHostmask(host)[0]);
539
+
540
+        final ChatState newState;
541
+
542
+        switch (state) {
543
+            case ENTERED_TEXT:
544
+                newState = ChatState.paused;
545
+                break;
546
+            case TYPING:
547
+                newState = ChatState.composing;
548
+                break;
549
+            case IDLE:
550
+            default:
551
+                newState = ChatState.active;
552
+                break;
553
+        }
554
+
555
+        if (chat != null && stateManager != null) {
556
+            try {
557
+                stateManager.setCurrentState(newState, chat);
558
+            } catch (XMPPException ex) {
559
+                // Can't set chat state... Oh well?
560
+                LOG.info("Couldn't set composition state", ex);
561
+            }
562
+        }
563
+    }
564
+
565
+    @Override
566
+    public void requestGroupList(final String searchTerms) {
567
+        // Do nothing
568
+    }
569
+
570
+    private class ConnectionListenerImpl implements ConnectionListener {
571
+
572
+        @Override
573
+        public void connectionClosed() {
574
+            getCallback(SocketCloseListener.class).onSocketClosed(XmppParser.this, new Date());
575
+        }
576
+
577
+        @Override
578
+        public void connectionClosedOnError(final Exception excptn) {
579
+            // TODO: Handle exception
580
+            getCallback(SocketCloseListener.class).onSocketClosed(XmppParser.this, new Date());
581
+        }
582
+
583
+        @Override
584
+        public void reconnectingIn(final int i) {
585
+            throw new UnsupportedOperationException("Not supported yet.");
586
+        }
587
+
588
+        @Override
589
+        public void reconnectionSuccessful() {
590
+            throw new UnsupportedOperationException("Not supported yet.");
591
+        }
592
+
593
+        @Override
594
+        public void reconnectionFailed(final Exception excptn) {
595
+            throw new UnsupportedOperationException("Not supported yet.");
596
+        }
597
+
598
+    }
599
+
600
+    private class RosterListenerImpl implements RosterListener {
601
+
602
+        @Override
603
+        public void entriesAdded(final Collection<String> clctn) {
604
+            // Do nothing, yet
605
+        }
606
+
607
+        @Override
608
+        public void entriesUpdated(final Collection<String> clctn) {
609
+            // Do nothing, yet
610
+        }
611
+
612
+        @Override
613
+        public void entriesDeleted(final Collection<String> clctn) {
614
+            // Do nothing, yet
615
+        }
616
+
617
+        @Override
618
+        public void presenceChanged(final Presence prsnc) {
619
+            getClient(prsnc.getFrom()).setPresence(prsnc);
620
+        }
621
+
622
+    }
623
+
624
+    private class ChatManagerListenerImpl implements ChatManagerListener {
625
+
626
+        @Override
627
+        public void chatCreated(final Chat chat, final boolean bln) {
628
+            if (!bln) {
629
+                // Only add chats that weren't created locally
630
+                chats.put(parseHostmask(chat.getParticipant())[0], chat);
631
+                chat.addMessageListener(new MessageListenerImpl());
632
+            }
633
+        }
634
+
635
+    }
636
+
637
+    private class MessageListenerImpl implements ChatStateListener {
638
+
639
+        @Override
640
+        public void processMessage(final Chat chat, final Message msg) {
641
+            if (msg.getType() == Message.Type.error) {
642
+                getCallback(NumericListener.class).onNumeric(XmppParser.this, new Date(),
643
+                        404, new String[]{
644
+                            ":xmpp", "404", getLocalClient().getNickname(),
645
+                            msg.getFrom(),
646
+                            "Cannot send message: " + msg.getError().toString()
647
+                        });
648
+                return;
649
+            }
650
+
651
+            if (msg.getBody() != null) {
652
+                if (msg.getBody().startsWith("/me ")) {
653
+                    getCallback(PrivateActionListener.class).onPrivateAction(XmppParser.this,
654
+                            new Date(), msg.getBody().substring(4), msg.getFrom());
655
+                } else {
656
+                    getCallback(PrivateMessageListener.class).onPrivateMessage(XmppParser.this,
657
+                            new Date(), msg.getBody(), msg.getFrom());
658
+                }
659
+            }
660
+        }
661
+
662
+        @Override
663
+        public void stateChanged(final Chat chat, final ChatState cs) {
664
+            final CompositionState state;
665
+
666
+            switch (cs) {
667
+                case paused:
668
+                    state = CompositionState.ENTERED_TEXT;
669
+                    break;
670
+                case composing:
671
+                    state = CompositionState.TYPING;
672
+                    break;
673
+                case active:
674
+                case gone:
675
+                case inactive:
676
+                default:
677
+                    state = CompositionState.IDLE;
678
+                    break;
679
+            }
680
+
681
+            getCallback(CompositionStateChangeListener.class)
682
+                    .onCompositionStateChanged(XmppParser.this, new Date(), state,
683
+                            chat.getParticipant());
684
+        }
685
+
686
+    }
687
+
688
+    private class PacketListenerImpl implements PacketListener {
689
+
690
+        private final Class<? extends CallbackInterface> callback;
691
+
692
+        public PacketListenerImpl(final Class<? extends CallbackInterface> callback) {
693
+            this.callback = callback;
694
+        }
695
+
696
+        @Override
697
+        public void processPacket(final Packet packet) {
698
+            if (callback.equals(DataOutListener.class)) {
699
+                getCallback(DataOutListener.class).onDataOut(XmppParser.this, new Date(),
700
+                        packet.toXML(), true);
701
+            } else {
702
+                getCallback(DataInListener.class).onDataIn(XmppParser.this, new Date(),
703
+                        packet.toXML());
704
+            }
705
+        }
706
+
707
+    }
708
+
709
+    private static class AcceptAllPacketFilter implements PacketFilter {
710
+
711
+        @Override
712
+        public boolean accept(final Packet packet) {
713
+            return true;
714
+        }
715
+
716
+    }
717
+
718
+}

+ 50
- 0
xmpp/src/com/dmdirc/parser/xmpp/XmppProtocolDescription.java View File

@@ -0,0 +1,50 @@
1
+/*
2
+ * Copyright (c) 2006-2015 DMDirc Developers
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.parser.xmpp;
24
+
25
+import com.dmdirc.parser.interfaces.ProtocolDescription;
26
+
27
+import java.net.URI;
28
+
29
+/**
30
+ * A description of the XMPP protocol.
31
+ */
32
+public class XmppProtocolDescription implements ProtocolDescription {
33
+
34
+    @Override
35
+    public int getDefaultPort() {
36
+        return 5222;
37
+    }
38
+
39
+    @Override
40
+    public String[] parseHostmask(final String hostmask) {
41
+        final String[] parts = hostmask.split("/");
42
+        return new String[]{parts[0], parts.length > 1 ? parts[1] : "unknown", ""};
43
+    }
44
+
45
+    @Override
46
+    public boolean isSecure(final URI uri) {
47
+        return uri.getScheme().endsWith("s");
48
+    }
49
+
50
+}

Loading…
Cancel
Save