From 44d672d134fc520f49786c9a8bc26f2c8b1df0e4 Mon Sep 17 00:00:00 2001 From: Damian Minkov Date: Wed, 5 Mar 2014 19:22:06 +0200 Subject: [PATCH] Adds recent messages contact source. --- resources/languages/resources.properties | 3 + .../MetaContactListServiceImpl.java | 2 +- .../msghistory/MessageHistoryActivator.java | 62 +- .../msghistory/MessageHistoryServiceImpl.java | 279 +++++-- .../impl/msghistory/MessageSourceContact.java | 416 +++++++++++ .../impl/msghistory/MessageSourceService.java | 684 ++++++++++++++++++ .../impl/msghistory/msghistory.manifest.mf | 3 +- .../contactsource/ContactSourceService.java | 5 + .../msghistory/MessageHistoryService.java | 8 + .../communicator/service/muc/MUCService.java | 11 + 10 files changed, 1414 insertions(+), 59 deletions(-) create mode 100644 src/net/java/sip/communicator/impl/msghistory/MessageSourceContact.java create mode 100644 src/net/java/sip/communicator/impl/msghistory/MessageSourceService.java diff --git a/resources/languages/resources.properties b/resources/languages/resources.properties index e957d025a..dff8f40ab 100644 --- a/resources/languages/resources.properties +++ b/resources/languages/resources.properties @@ -290,6 +290,7 @@ service.gui.INSERT_SMILEY=Insert smiley service.gui.INCOMING_CALL=Incoming call received from: {0} service.gui.INCOMING_CALL_STATUS=Incoming call service.gui.INSTANT_MESSAGINGS=IMs +service.gui.IM=IM service.gui.INITIATING_CALL_STATUS=Initiating call service.gui.INVITATION=Invitation text service.gui.INVITATION_RECEIVED=Invitation received @@ -441,6 +442,7 @@ service.gui.PUT_OFF_HOLD=Put off hold service.gui.PUT_ON_HOLD=Put on hold service.gui.QUIT=&Quit service.gui.READY=Ready +service.gui.RECENT_MESSAGES=Recent messages service.gui.REASON=Reason service.gui.RECEIVED={0} received service.gui.RECONNECTION_LIMIT_EXCEEDED=You have have been disconnecting and reconnecting to the server too fast. The following account: User name: {0}, Server name: {1} is temporarily banned and would have to wait a bit before trying to login again. @@ -1010,6 +1012,7 @@ plugin.generalconfig.ERROR_PORT_NUMBER=Wrong port number plugin.generalconfig.CHECK_FOR_UPDATES=Check for updates on startup plugin.generalconfig.STARTUP_CONFIG=Startup plugin.generalconfig.LEAVE_CHATROOM_ON_WINDOW_CLOSE=Leave chat rooms when closing window +plugin.generalconfig.SHOW_RECENT_MESSAGES=Show recent messages (depends on chat history) plugin.generalconfig.REMOVE_SPECIAL_PHONE_SYMBOLS=Remove special symbols before calling phone numbers plugin.generalconfig.ACCEPT_PHONE_NUMBER_WITH_ALPHA_CHARS=Convert letters in phone numbers plugin.generalconfig.ACCEPT_PHONE_NUMBER_WITH_ALPHA_CHARS_EXAMPLE=e.g. +1-800-MYPHONE -> +1-800-694663 diff --git a/src/net/java/sip/communicator/impl/contactlist/MetaContactListServiceImpl.java b/src/net/java/sip/communicator/impl/contactlist/MetaContactListServiceImpl.java index 6bd71b33b..c12e8175e 100644 --- a/src/net/java/sip/communicator/impl/contactlist/MetaContactListServiceImpl.java +++ b/src/net/java/sip/communicator/impl/contactlist/MetaContactListServiceImpl.java @@ -3668,6 +3668,6 @@ public void supportedOperationSetsChanged(ContactCapabilitiesEvent event) */ public int getSourceIndex() { - return 1; + return 2; } } diff --git a/src/net/java/sip/communicator/impl/msghistory/MessageHistoryActivator.java b/src/net/java/sip/communicator/impl/msghistory/MessageHistoryActivator.java index b9562f343..484c5c469 100644 --- a/src/net/java/sip/communicator/impl/msghistory/MessageHistoryActivator.java +++ b/src/net/java/sip/communicator/impl/msghistory/MessageHistoryActivator.java @@ -11,6 +11,8 @@ import net.java.sip.communicator.service.msghistory.*; import net.java.sip.communicator.util.*; +import org.jitsi.service.configuration.*; +import org.jitsi.service.resources.*; import org.osgi.framework.*; /** @@ -32,13 +34,23 @@ public class MessageHistoryActivator /** * The MessageHistoryService reference. */ - private MessageHistoryServiceImpl msgHistoryService = null; + private static MessageHistoryServiceImpl msgHistoryService = null; + + /** + * The ResourceManagementService reference. + */ + private static ResourceManagementService resourcesService; /** * The MetaContactListService reference. */ private static MetaContactListService metaCListService; + /** + * The ConfigurationService reference. + */ + private static ConfigurationService configService; + /** * The BundleContext of the service. */ @@ -114,4 +126,52 @@ public static MetaContactListService getContactListService() } return metaCListService; } + + /** + * Returns the MessageHistoryService registered to the bundle + * context. + * @return the MessageHistoryService registered to the bundle + * context + */ + public static MessageHistoryServiceImpl getMessageHistoryService() + { + return msgHistoryService; + } + + /** + * Returns the ResourceManagementService, through which we will + * access all resources. + * + * @return the ResourceManagementService, through which we will + * access all resources. + */ + public static ResourceManagementService getResources() + { + if (resourcesService == null) + { + resourcesService + = ServiceUtils.getService( + bundleContext, + ResourceManagementService.class); + } + return resourcesService; + } + + /** + * Returns the ConfigurationService obtained from the bundle + * context. + * @return the ConfigurationService obtained from the bundle + * context + */ + public static ConfigurationService getConfigurationService() + { + if(configService == null) + { + configService + = ServiceUtils.getService( + bundleContext, + ConfigurationService.class); + } + return configService; + } } diff --git a/src/net/java/sip/communicator/impl/msghistory/MessageHistoryServiceImpl.java b/src/net/java/sip/communicator/impl/msghistory/MessageHistoryServiceImpl.java index 1c05a6588..27c9e5baf 100644 --- a/src/net/java/sip/communicator/impl/msghistory/MessageHistoryServiceImpl.java +++ b/src/net/java/sip/communicator/impl/msghistory/MessageHistoryServiceImpl.java @@ -15,6 +15,7 @@ import java.util.*; import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.service.contactsource.*; import net.java.sip.communicator.service.history.*; import net.java.sip.communicator.service.history.event.*; import net.java.sip.communicator.service.history.event.ProgressEvent; @@ -27,7 +28,6 @@ import net.java.sip.communicator.util.account.*; import org.jitsi.service.configuration.*; -import org.jitsi.service.resources.*; import org.osgi.framework.*; /** @@ -84,13 +84,20 @@ public class MessageHistoryServiceImpl private MessageHistoryPropertyChangeListener msgHistoryPropListener; - private static ResourceManagementService resourcesService; - /** * Indicates if history logging is enabled. */ private static boolean isHistoryLoggingEnabled; + /** + * The message source service, can be null if not enabled. + */ + private MessageSourceService messageSourceService; + + /** + * The message source service registration. + */ + private ServiceRegistration messageSourceServiceReg = null; /** * Returns the history service. @@ -342,15 +349,20 @@ public Collection findLast(MetaContact contact, int count) * Returns the messages for the recently contacted count contacts. * * @param count contacts count + * @param providerToFilter can be filtered by provider, or null to + * search for all providers + * @param contactToFilter can be filtered by contac, or null to + * search for all contacts * @return Collection of MessageReceivedEvents or MessageDeliveredEvents * @throws RuntimeException */ - Collection findRecentMessagesPerContact(int count) + Collection findRecentMessagesPerContact( + int count, String providerToFilter, String contactToFilter) throws RuntimeException { TreeSet result = new TreeSet( - new MessageEventComparator()); + new MessageEventComparator(true)); List historyIDs= this.historyService.getExistingHistories( @@ -363,11 +375,31 @@ Collection findRecentMessagesPerContact(int count) try { - // find contact for historyID - Contact contact = getContactForHistory(id); + // this history id is: "messages", localId, account, remoteId + if(id.getID().length != 4) + continue; + + // filter by protocol provider + String accountID = id.getID()[2].replace('_', ':'); + if(providerToFilter != null + && !accountID.startsWith(providerToFilter)) + { + continue; + } + + if(contactToFilter != null + && !contactToFilter.equals(id.getID()[3])) + { + continue; + } + + // find contact or chatroom for historyID + Object descriptor = getContactOrRoomByID( + accountID, + id.getID()[3]); // skip not found contacts, disabled accounts and hidden one - if(contact == null) + if(descriptor == null) continue; History history; @@ -386,8 +418,20 @@ Collection findRecentMessagesPerContact(int count) Iterator recs = reader.findLast(1); while (recs.hasNext()) { - result.add(convertHistoryRecordToMessageEvent( - recs.next(), contact)); + if(descriptor instanceof Contact) + { + EventObject o = convertHistoryRecordToMessageEvent( + recs.next(), (Contact) descriptor); + + result.add(o); + } + if(descriptor instanceof ChatRoom) + { + EventObject o = convertHistoryRecordToMessageEvent( + recs.next(), (ChatRoom) descriptor); + + result.add(o); + } break; } } @@ -401,21 +445,16 @@ Collection findRecentMessagesPerContact(int count) } /** - * Founds the contact corresponding this HistoryID. Checks the account and - * then searches for the contact. + * Founds the contact or chat room corresponding this HistoryID. Checks the + * account and then searches for the contact or chat room. * Will skip hidden and disabled accounts. * - * @param id the history id. - * @return + * @param accountID the account id. + * @param id the contact or room id. + * @return contact or chat room. */ - private Contact getContactForHistory(HistoryID id) + private Object getContactOrRoomByID(String accountID, String id) { - // this history id is: "messages", localId, account, remoteId - if(id.getID().length != 4) - return null; - - String accountID = id.getID()[2].replace('_', ':'); - AccountID account = null; for(AccountID acc : AccountUtils.getStoredAccounts()) { @@ -443,7 +482,29 @@ private Contact getContactForHistory(HistoryID id) if(opSetPresence == null) return null; - return opSetPresence.findContactByID(id.getID()[3]); + Contact contact = opSetPresence.findContactByID(id); + + if(contact != null) + return contact; + + OperationSetMultiUserChat opSetMuc = + pps.getOperationSet(OperationSetMultiUserChat.class); + + if(opSetMuc == null) + return null; + + try + { + // will remove the server part + id = id.substring(0, id.lastIndexOf('@')); + + return opSetMuc.findRoom(id); + } + catch(Exception e) + { + //logger.error("Cannot find room", e); + return null; + } } /** @@ -915,7 +976,8 @@ public void start(BundleContext bc) // service, and if not do not register the service. boolean isMessageHistoryEnabled = configService.getBoolean( MessageHistoryService.PNAME_IS_MESSAGE_HISTORY_ENABLED, - Boolean.parseBoolean(getResources().getSettingsString( + Boolean.parseBoolean( + MessageHistoryActivator.getResources().getSettingsString( MessageHistoryService.PNAME_IS_MESSAGE_HISTORY_ENABLED)) ); @@ -944,6 +1006,31 @@ public void start(BundleContext bc) } } + /** + * Loads and registers the contact source service. + */ + private void loadRecentMessages() + { + this.messageSourceService = new MessageSourceService(); + messageSourceServiceReg = bundleContext.registerService( + ContactSourceService.class.getName(), + messageSourceService, null); + } + + /** + * Unloads the contact source service. + */ + private void stopRecentMessages() + { + if(messageSourceServiceReg != null) + { + messageSourceServiceReg.unregister(); + messageSourceServiceReg = null; + + this.messageSourceService = null; + } + } + /** * Stops the service. * @@ -1342,6 +1429,9 @@ private void handleProviderAdded(ProtocolProviderService provider) if (opSetIm != null) { opSetIm.addMessageListener(this); + + if(this.messageSourceService != null) + opSetIm.addMessageListener(messageSourceService); } else { @@ -1355,6 +1445,9 @@ private void handleProviderAdded(ProtocolProviderService provider) if (opSetSMS != null) { opSetSMS.addMessageListener(this); + + if(this.messageSourceService != null) + opSetIm.addMessageListener(messageSourceService); } else { @@ -1377,12 +1470,26 @@ private void handleProviderAdded(ProtocolProviderService provider) } opSetMultiUChat.addPresenceListener(this); + + if(messageSourceService != null) + opSetMultiUChat.addPresenceListener(messageSourceService); } else { if (logger.isTraceEnabled()) logger.trace("Service did not have a multi im op. set."); } + + OperationSetPresence opSetPresence = + provider.getOperationSet(OperationSetPresence.class); + + if (opSetPresence != null && messageSourceService != null) + { + opSetPresence + .addContactPresenceStatusListener(messageSourceService); + opSetPresence + .addProviderPresenceStatusListener(messageSourceService); + } } /** @@ -1399,6 +1506,9 @@ private void handleProviderRemoved(ProtocolProviderService provider) if (opSetIm != null) { opSetIm.removeMessageListener(this); + + if(this.messageSourceService != null) + opSetIm.removeMessageListener(messageSourceService); } OperationSetSmsMessaging opSetSMS = @@ -1407,6 +1517,9 @@ private void handleProviderRemoved(ProtocolProviderService provider) if (opSetSMS != null) { opSetSMS.removeMessageListener(this); + + if(this.messageSourceService != null) + opSetIm.removeMessageListener(messageSourceService); } OperationSetMultiUserChat opSetMultiUChat = @@ -1422,6 +1535,22 @@ private void handleProviderRemoved(ProtocolProviderService provider) ChatRoom room = iter.next(); room.removeMessageListener(this); } + + opSetMultiUChat.removePresenceListener(this); + + if(messageSourceService != null) + opSetMultiUChat.removePresenceListener(messageSourceService); + } + + OperationSetPresence opSetPresence = + provider.getOperationSet(OperationSetPresence.class); + + if (opSetPresence != null && messageSourceService != null) + { + opSetPresence + .removeContactPresenceStatusListener(messageSourceService); + opSetPresence + .removeProviderPresenceStatusListener(messageSourceService); } } @@ -1438,11 +1567,19 @@ public void localUserPresenceChanged(LocalUserChatRoomPresenceChangeEvent evt) LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_JOINED) { if (!evt.getChatRoom().isSystem()) + { evt.getChatRoom().addMessageListener(this); + + if(this.messageSourceService != null) + evt.getChatRoom().addMessageListener(messageSourceService); + } } else { evt.getChatRoom().removeMessageListener(this); + + if(this.messageSourceService != null) + evt.getChatRoom().removeMessageListener(messageSourceService); } } @@ -2116,30 +2253,6 @@ public Collection findLastMessagesBefore( ChatRoom room, return resultAsList.subList(startIndex, resultAsList.size()); } - /** - * Get ResourceManagementService registered. - * - * @return ResourceManagementService - */ - public static ResourceManagementService getResources() - { - if (resourcesService == null) - { - ServiceReference serviceReference - = MessageHistoryActivator.bundleContext - .getServiceReference(ResourceManagementService.class.getName()); - - if(serviceReference == null) - return null; - - resourcesService = (ResourceManagementService) - MessageHistoryActivator.bundleContext - .getService(serviceReference); - } - - return resourcesService; - } - /** * A wrapper around HistorySearchProgressListener * that fires events for MessageHistorySearchProgressListener @@ -2254,6 +2367,18 @@ public Date getMessageReceivedDate() private static class MessageEventComparator implements Comparator { + private final boolean reverseOrder; + + MessageEventComparator(boolean reverseOrder) + { + this.reverseOrder = reverseOrder; + } + + MessageEventComparator() + { + this(false); + } + public int compare(T o1, T o2) { Date date1; @@ -2263,6 +2388,10 @@ public int compare(T o1, T o2) date1 = ((MessageDeliveredEvent)o1).getTimestamp(); else if(o1 instanceof MessageReceivedEvent) date1 = ((MessageReceivedEvent)o1).getTimestamp(); + else if(o1 instanceof ChatRoomMessageDeliveredEvent) + date1 = ((ChatRoomMessageDeliveredEvent)o1).getTimestamp(); + else if(o1 instanceof ChatRoomMessageReceivedEvent) + date1 = ((ChatRoomMessageReceivedEvent)o1).getTimestamp(); else return 0; @@ -2270,10 +2399,17 @@ else if(o1 instanceof MessageReceivedEvent) date2 = ((MessageDeliveredEvent)o2).getTimestamp(); else if(o2 instanceof MessageReceivedEvent) date2 = ((MessageReceivedEvent)o2).getTimestamp(); + else if(o2 instanceof ChatRoomMessageDeliveredEvent) + date2 = ((ChatRoomMessageDeliveredEvent)o2).getTimestamp(); + else if(o2 instanceof ChatRoomMessageReceivedEvent) + date2 = ((ChatRoomMessageReceivedEvent)o2).getTimestamp(); else return 0; - return date1.compareTo(date2); + if(reverseOrder) + return date2.compareTo(date1); + else + return date1.compareTo(date2); } } @@ -2410,15 +2546,35 @@ private class MessageHistoryPropertyChangeListener { public void propertyChange(PropertyChangeEvent evt) { - String newPropertyValue = (String) evt.getNewValue(); - isHistoryLoggingEnabled - = new Boolean(newPropertyValue).booleanValue(); + if(evt.getPropertyName() + .equals(MessageHistoryService.PNAME_IS_MESSAGE_HISTORY_ENABLED)) + { + String newPropertyValue = (String) evt.getNewValue(); + isHistoryLoggingEnabled + = new Boolean(newPropertyValue).booleanValue(); - // If the message history is not enabled we stop here. - if (isHistoryLoggingEnabled) - loadMessageHistoryService(); - else - stop(bundleContext); + // If the message history is not enabled we stop here. + if (isHistoryLoggingEnabled) + loadMessageHistoryService(); + else + stop(bundleContext); + } + else if(evt.getPropertyName().equals( + MessageHistoryService.PNAME_IS_RECENT_MESSAGES_DISABLED)) + { + String newPropertyValue = (String) evt.getNewValue(); + boolean isDisabled + = new Boolean(newPropertyValue).booleanValue(); + + if(isDisabled) + { + stopRecentMessages(); + } + else if(isHistoryLoggingEnabled) + { + loadRecentMessages(); + } + } } } @@ -2429,6 +2585,17 @@ public void propertyChange(PropertyChangeEvent evt) */ private void loadMessageHistoryService() { + configService.addPropertyChangeListener( + MessageHistoryService.PNAME_IS_RECENT_MESSAGES_DISABLED, + msgHistoryPropListener); + + boolean isRecentMessagesDisabled = configService.getBoolean( + MessageHistoryService.PNAME_IS_RECENT_MESSAGES_DISABLED, + false); + + if(!isRecentMessagesDisabled) + loadRecentMessages(); + // start listening for newly register or removed protocol providers bundleContext.addServiceListener(this); diff --git a/src/net/java/sip/communicator/impl/msghistory/MessageSourceContact.java b/src/net/java/sip/communicator/impl/msghistory/MessageSourceContact.java new file mode 100644 index 000000000..cbd1d9c26 --- /dev/null +++ b/src/net/java/sip/communicator/impl/msghistory/MessageSourceContact.java @@ -0,0 +1,416 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.msghistory; + +import net.java.sip.communicator.service.contactsource.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + +import java.util.*; + +/** + * Represents a contact source displaying a recent message for contact. + * @author Damian Minkov + */ +public class MessageSourceContact + extends DataObject + implements SourceContact, + Comparable +{ + /** + * The parent service. + */ + private final MessageSourceService service; + + /** + * The address. + */ + private String address = null; + + /** + * The display name. + */ + private String displayName = null; + + /** + * The protocol provider. + */ + private ProtocolProviderService ppService = null; + + /** + * The status. + */ + private PresenceStatus status = null; + + /** + * The image. + */ + private byte[] image = null; + + /** + * The message content. + */ + private String messageContent = null; + + /** + * A list of all contact details. + */ + private final List contactDetails + = new LinkedList(); + + /** + * The contact instance. + */ + private Contact contact = null; + + /** + * The room instance. + */ + private ChatRoom room = null; + + /** + * The timestamp. + */ + private Date timestamp = null; + + /** + * The protocol provider. + * @return the protocol provider. + */ + public ProtocolProviderService getProtocolProviderService() + { + return ppService; + } + + /** + * Constructs MessageSourceContact. + * @param source the source event. + * @param service the message source service. + */ + MessageSourceContact(EventObject source, + MessageSourceService service) + { + update(source); + + if(source instanceof MessageDeliveredEvent + || source instanceof MessageReceivedEvent) + { + initDetails(false); + } + else if(source instanceof ChatRoomMessageDeliveredEvent + || source instanceof ChatRoomMessageReceivedEvent) + { + initDetails(true); + } + + this.service = service; + + if(this.messageContent != null + && this.messageContent.length() > 60) + { + // do not display too long texts + this.messageContent = this.messageContent.substring(0, 60); + this.messageContent += "..."; + } + } + + /** + * Updates fields. + * @param source the event object + */ + void update(EventObject source) + { + if(source instanceof MessageDeliveredEvent) + { + MessageDeliveredEvent e = (MessageDeliveredEvent)source; + + this.contact = e.getDestinationContact(); + + this.address = contact.getAddress(); + this.displayName = contact.getDisplayName(); + this.ppService = contact.getProtocolProvider(); + this.image = contact.getImage(); + this.status = contact.getPresenceStatus(); + this.messageContent = e.getSourceMessage().getContent(); + this.timestamp = e.getTimestamp(); + } + else if(source instanceof MessageReceivedEvent) + { + MessageReceivedEvent e = (MessageReceivedEvent)source; + + this.contact = e.getSourceContact(); + + this.address = contact.getAddress(); + this.displayName = contact.getDisplayName(); + this.ppService = contact.getProtocolProvider(); + this.image = contact.getImage(); + this.status = contact.getPresenceStatus(); + this.messageContent = e.getSourceMessage().getContent(); + this.timestamp = e.getTimestamp(); + } + else if(source instanceof ChatRoomMessageDeliveredEvent) + { + ChatRoomMessageDeliveredEvent e + = (ChatRoomMessageDeliveredEvent)source; + + this.room = e.getSourceChatRoom(); + + this.address = room.getIdentifier(); + this.displayName = room.getName(); + this.ppService = room.getParentProvider(); + this.image = null; + this.status = null; + this.messageContent = e.getMessage().getContent(); + this.timestamp = e.getTimestamp(); + } + else if(source instanceof ChatRoomMessageReceivedEvent) + { + ChatRoomMessageReceivedEvent e + = (ChatRoomMessageReceivedEvent)source; + + this.room = e.getSourceChatRoom(); + + this.address = room.getIdentifier(); + this.displayName = room.getName(); + this.ppService = room.getParentProvider(); + this.image = null; + this.status = null; + this.messageContent = e.getMessage().getContent(); + this.timestamp = e.getTimestamp(); + } + } + + /** + * We will the details for this source contact. + * Will skip OperationSetBasicInstantMessaging for chat rooms. + * @param isChatRoom is current source contact a chat room. + */ + private void initDetails(boolean isChatRoom) + { + ContactDetail contactDetail = + new ContactDetail( + this.address, + this.displayName); + + Map, ProtocolProviderService> + preferredProviders; + + ProtocolProviderService preferredProvider + = this.ppService; + + if (preferredProvider != null) + { + preferredProviders + = new Hashtable, + ProtocolProviderService>(); + + LinkedList> supportedOpSets + = new LinkedList>(); + + for(Class opset + : preferredProvider.getSupportedOperationSetClasses()) + { + // skip opset IM as we want explicitly muc support + if(opset.equals(OperationSetPresence.class) + || opset.equals(OperationSetPersistentPresence.class) + || (isChatRoom + && opset.equals( + OperationSetBasicInstantMessaging.class))) + { + continue; + } + + preferredProviders.put(opset, preferredProvider); + + supportedOpSets.add(opset); + } + + contactDetail.setPreferredProviders(preferredProviders); + + contactDetail.setSupportedOpSets(supportedOpSets); + } + + contactDetails.add(contactDetail); + } + + @Override + public String getDisplayName() + { + if(this.displayName != null) + return this.displayName; + else + return MessageHistoryActivator.getResources() + .getI18NString("service.gui.UNKNOWN"); + } + + @Override + public String getContactAddress() + { + if(this.address != null) + return this.address; + + return null; + } + + @Override + public ContactSourceService getContactSource() + { + return service; + } + + @Override + public String getDisplayDetails() + { + return messageContent; + } + + /** + * Returns a list of available contact details. + * @return a list of available contact details + */ + @Override + public List getContactDetails() + { + return new LinkedList(contactDetails); + } + + /** + * Returns a list of all ContactDetails supporting the given + * OperationSet class. + * @param operationSet the OperationSet class we're looking for + * @return a list of all ContactDetails supporting the given + * OperationSet class + */ + @Override + public List getContactDetails( + Class operationSet) + { + List res = new LinkedList(); + + for(ContactDetail det : contactDetails) + { + if(det.getPreferredProtocolProvider(operationSet) != null) + res.add(det); + } + + return res; + } + + /** + * Returns a list of all ContactDetails corresponding to the given + * category. + * @param category the OperationSet class we're looking for + * @return a list of all ContactDetails corresponding to the given + * category + */ + @Override + public List getContactDetails( + ContactDetail.Category category) + throws OperationNotSupportedException + { + // We don't support category for message source history details, + // so we return null. + throw new OperationNotSupportedException( + "Categories are not supported for message source contact history."); + } + + /** + * Returns the preferred ContactDetail for a given + * OperationSet class. + * @param operationSet the OperationSet class, for which we would + * like to obtain a ContactDetail + * @return the preferred ContactDetail for a given + * OperationSet class + */ + @Override + public ContactDetail getPreferredContactDetail( + Class operationSet) + { + return contactDetails.get(0); + } + + @Override + public byte[] getImage() + { + return image; + } + + @Override + public boolean isDefaultImage() + { + return image == null; + } + + @Override + public void setContactAddress(String contactAddress) + {} + + @Override + public PresenceStatus getPresenceStatus() + { + return status; + } + + /** + * Sets current status. + * @param status + */ + public void setStatus(PresenceStatus status) + { + this.status = status; + } + + @Override + public int getIndex() + { + return service.getIndex(this); + } + + /** + * The contact. + * @return the contact. + */ + public Contact getContact() + { + return contact; + } + + /** + * The room. + * @return the room. + */ + public ChatRoom getRoom() + { + return room; + } + + /** + * The timestamp of the message. + * @return the timestamp of the message. + */ + public Date getTimestamp() + { + return timestamp; + } + + /** + * Compares two MessageSourceContacts. + * @param o the object to compare with + * @return 0, less than zero, greater than zero, if equals, less or greater. + */ + @Override + public int compareTo(MessageSourceContact o) + { + if(o == null + || o.getTimestamp() == null) + return 1; + + return o.getTimestamp() + .compareTo(getTimestamp()); + } +} diff --git a/src/net/java/sip/communicator/impl/msghistory/MessageSourceService.java b/src/net/java/sip/communicator/impl/msghistory/MessageSourceService.java new file mode 100644 index 000000000..50dff396e --- /dev/null +++ b/src/net/java/sip/communicator/impl/msghistory/MessageSourceService.java @@ -0,0 +1,684 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.msghistory; + +import net.java.sip.communicator.service.contactsource.*; +import net.java.sip.communicator.service.history.*; +import net.java.sip.communicator.service.history.records.*; +import net.java.sip.communicator.service.muc.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import org.jitsi.util.*; + +import java.beans.*; +import java.io.*; +import java.text.*; +import java.util.*; +import java.util.regex.*; + +/** + * The source contact service. The will show most recent messages. + * + * @author Damian Minkov + */ +public class MessageSourceService + implements ContactSourceService, + ContactPresenceStatusListener, + ProviderPresenceStatusListener, + LocalUserChatRoomPresenceListener, + MessageListener, + ChatRoomMessageListener, + AdHocChatRoomMessageListener +{ + /** + * The logger for this class. + */ + private static Logger logger = Logger + .getLogger(MessageSourceService.class); + + /** + * The display name of this contact source. + */ + private final String MESSAGE_HISTORY_NAME; + + /** + * The type of the source service, the place to be shown in the ui. + */ + private int sourceServiceType = RECENT_MESSAGES_TYPE; + + /** + * Whether to show recent messages in history or in contactlist. + * By default we show it in contactlist. + */ + private static final String IN_HISTORY_PROPERTY + = "net.java.sip.communicator.impl.msghistory.contactsrc.IN_HISTORY"; + + /** + * Property to control number of recent messages. + */ + private static final String NUMBER_OF_RECENT_MSGS_PROP + = "net.java.sip.communicator.impl.msghistory.contactsrc.MSG_NUMBER"; + + /** + * Number of messages to show. + */ + private int numberOfMessages = 10; + + /** + * The structure to save recent messages list. + */ + private static final String[] STRUCTURE_NAMES + = new String[] { "provider", "contact"}; + + /** + * The structure. + */ + private static final HistoryRecordStructure recordStructure = + new HistoryRecordStructure(STRUCTURE_NAMES); + + /** + * Recent messages history ID. + */ + private static final HistoryID historyID = HistoryID.createFromRawID( + new String[] { "recent_messages"}); + + /** + * List of recent messages. + */ + private List recentMessages = null; + + /** + * The last query created. + */ + private MessageHistoryContactQuery recentQuery = null; + + /** + * Constructs MessageSourceService. + */ + MessageSourceService() + { + if(MessageHistoryActivator.getConfigurationService() + .getBoolean(IN_HISTORY_PROPERTY , false)) + { + sourceServiceType = HISTORY_TYPE; + } + + MESSAGE_HISTORY_NAME = MessageHistoryActivator.getResources() + .getI18NString("service.gui.RECENT_MESSAGES"); + + numberOfMessages = MessageHistoryActivator.getConfigurationService() + .getInt(NUMBER_OF_RECENT_MSGS_PROP, numberOfMessages); + } + + /** + * Returns the display name of this contact source. + * @return the display name of this contact source + */ + @Override + public String getDisplayName() + { + return MESSAGE_HISTORY_NAME; + } + + /** + * Returns default type to indicate that this contact source can be queried + * by default filters. + * + * @return the type of this contact source + */ + @Override + public int getType() + { + return sourceServiceType; + } + + /** + * Returns the index of the contact source in the result list. + * + * @return the index of the contact source in the result list + */ + @Override + public int getIndex() + { + return 0; + } + + /** + * Creates query for the given searchString. + * @param queryString the string to search for + * @return the created query + */ + @Override + public ContactQuery createContactQuery(String queryString) + { + recentQuery = + (MessageHistoryContactQuery)createContactQuery( + queryString, numberOfMessages); + + return recentQuery; + } + + private List getRecentMessages() + { + if(recentMessages == null) + { + // find locally stored list of recent messages + // time, provider, contact + List cachedRecent + = getRecentMessagesFromHistory(); + + if(cachedRecent != null) + { + recentMessages = cachedRecent; + + Collections.sort(recentMessages); + + return recentMessages; + } + + recentMessages = new LinkedList(); + + // If missing search and construct it and save it + + MessageHistoryServiceImpl msgHistoryService = + MessageHistoryActivator.getMessageHistoryService(); + Collection res = msgHistoryService + .findRecentMessagesPerContact(numberOfMessages, null, null); + + for(EventObject obj : res) + { + recentMessages.add( + new MessageSourceContact(obj, MessageSourceService.this)); + } + Collections.sort(recentMessages); + + // save it + saveRecentMessagesToHistory(); + } + + return recentMessages; + } + + /** + * Loads recent messages if saved in history. + * @return + */ + private List getRecentMessagesFromHistory() + { + MessageHistoryServiceImpl msgService + = MessageHistoryActivator.getMessageHistoryService(); + HistoryService historyService = msgService.getHistoryService(); + + List res + = new LinkedList(); + + // and load it + try + { + SimpleDateFormat sdf + = new SimpleDateFormat(HistoryService.DATE_FORMAT); + + History history; + if (historyService.isHistoryExisting(historyID)) + history = historyService.getHistory(historyID); + else + history + = historyService.createHistory(historyID, recordStructure); + + Iterator recs + = history.getReader().findLast(numberOfMessages); + while(recs.hasNext()) + { + HistoryRecord hr = recs.next(); + + String provider = null; + String contact = null; + + for (int i = 0; i < hr.getPropertyNames().length; i++) + { + String propName = hr.getPropertyNames()[i]; + + if (propName.equals(STRUCTURE_NAMES[0])) + provider = hr.getPropertyValues()[i]; + else if (propName.equals(STRUCTURE_NAMES[1])) + contact = hr.getPropertyValues()[i]; + } + + if(provider == null || contact == null) + return res; + + for(EventObject ev : msgService.findRecentMessagesPerContact( + numberOfMessages, provider, contact)) + { + res.add(new MessageSourceContact(ev, this)); + } + } + } + catch(IOException ex) + { + logger.error("cannot create recent_messages history", ex); + return null; + } + + return res; + } + + /** + * Saves cached list of recent messages in history. + */ + private void saveRecentMessagesToHistory() + { + HistoryService historyService = MessageHistoryActivator + .getMessageHistoryService().getHistoryService(); + + if (historyService.isHistoryExisting(historyID)) + { + // delete it + try + { + historyService.purgeLocallyStoredHistory(historyID); + } + catch(IOException ex) + { + logger.error("Cannot delete recent_messages history", ex); + return; + } + } + + // and create it + try + { + History history = historyService.createHistory( + historyID, recordStructure); + + HistoryWriter writer = history.getWriter(); + SimpleDateFormat sdf + = new SimpleDateFormat(HistoryService.DATE_FORMAT); + + for(MessageSourceContact msc : recentMessages) + { + writer.addRecord( + new String[] + { + msc.getProtocolProviderService() + .getAccountID().getAccountUniqueID(), + msc.getContactAddress() + }); + } + } + catch(IOException ex) + { + logger.error("cannot create recent_messages history", ex); + return; + } + } + + /** + * Returns the index of the source contact, in the list of recent messages. + * @param messageSourceContact + * @return + */ + int getIndex(MessageSourceContact messageSourceContact) + { + return recentMessages.indexOf(messageSourceContact); + } + + /** + * Creates query for the given searchString. + * @param queryString the string to search for + * @param contactCount the maximum count of result contacts + * @return the created query + */ + @Override + public ContactQuery createContactQuery(String queryString, int contactCount) + { + if(!StringUtils.isNullOrEmpty(queryString)) + return null; + + recentQuery = new MessageHistoryContactQuery(numberOfMessages); + + return recentQuery; + } + + /** + * Updates contact source contacts with status. + * @param evt the ContactPresenceStatusChangeEvent describing the status + */ + @Override + public void contactPresenceStatusChanged(ContactPresenceStatusChangeEvent evt) + { + if(recentQuery == null) + return; + + for(MessageSourceContact msgSC : getRecentMessages()) + { + if(msgSC.getContact() != null + && msgSC.getContact().equals(evt.getSourceContact())) + { + msgSC.setStatus(evt.getNewStatus()); + recentQuery.fireContactChanged(msgSC); + } + } + } + + @Override + public void providerStatusChanged(ProviderPresenceStatusChangeEvent evt) + { + if(!evt.getNewStatus().isOnline()) + return; + + // now check for chat rooms as we are connected + MessageHistoryServiceImpl msgHistoryService = + MessageHistoryActivator.getMessageHistoryService(); + Collection res = msgHistoryService + .findRecentMessagesPerContact( + numberOfMessages, + evt.getProvider().getAccountID().getAccountUniqueID(), + null); + + List recentMessagesForProvider = new LinkedList(); + for(MessageSourceContact msc : recentMessages) + { + if(msc.getProtocolProviderService().equals(evt.getProvider())) + recentMessagesForProvider.add(msc.getContactAddress()); + } + + List newContactSources + = new LinkedList(); + for(EventObject obj : res) + { + if(obj instanceof ChatRoomMessageDeliveredEvent + || obj instanceof ChatRoomMessageReceivedEvent) + { + MessageSourceContact msc + = new MessageSourceContact(obj, MessageSourceService.this); + + if(recentMessagesForProvider.contains(msc.getContactAddress())) + continue; + + recentMessages.add(msc); + newContactSources.add(msc); + + } + } + + // sort + Collections.sort(recentMessages); + + // and now fire events to update ui + if(recentQuery != null) + { + for(MessageSourceContact msc : newContactSources) + { + recentQuery.addQueryResult(msc); + } + } + } + + @Override + public void providerStatusMessageChanged(PropertyChangeEvent evt) + {} + + @Override + public void localUserPresenceChanged( + LocalUserChatRoomPresenceChangeEvent evt) + { + if(recentQuery == null) + return; + + MessageSourceContact srcContact = null; + for(MessageSourceContact msg : getRecentMessages()) + { + if(msg.getRoom() != null + && msg.getRoom().equals(evt.getChatRoom())) + { + srcContact = msg; + break; + } + } + + if(srcContact == null) + return; + + String eventType = evt.getEventType(); + + if (LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_JOINED.equals(eventType)) + { + srcContact.setStatus(ChatRoomPresenceStatus.CHAT_ROOM_ONLINE); + recentQuery.fireContactChanged(srcContact); + } + else if ((LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_LEFT.equals(eventType) + || LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_KICKED.equals(eventType) + || LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_DROPPED.equals(eventType)) + ) + { + srcContact.setStatus(ChatRoomPresenceStatus.CHAT_ROOM_OFFLINE); + recentQuery.fireContactChanged(srcContact); + } + } + + /** + * Handles new events. + * + * @param obj the event object + * @param provider the provider + * @param id the id of the source of the event + */ + private void handle(EventObject obj, + ProtocolProviderService provider, + String id) + { + // check if provider - contact exist update message content + for(MessageSourceContact msc : recentMessages) + { + if(msc.getProtocolProviderService().equals(provider) + && msc.getContactAddress().equals(id)) + { + // update + msc.update(obj); + + if(recentQuery != null) + recentQuery.fireContactChanged(msc); + + return; + } + } + + // if missing create source contact + // and update recent messages, trim and sort + MessageSourceContact newSourceContact = + new MessageSourceContact(obj, MessageSourceService.this); + recentMessages.add(newSourceContact); + + Collections.sort(recentMessages); + + // trim + List removedItems = null; + if(recentMessages.size() > numberOfMessages) + { + removedItems = recentMessages.subList( + numberOfMessages, recentMessages.size()); + + recentMessages = recentMessages.subList(0, numberOfMessages); + } + + // save + saveRecentMessagesToHistory(); + + // no query nothing to fire + if(recentQuery == null) + return; + + // now fire + if(removedItems != null) + { + for(MessageSourceContact msc : removedItems) + { + recentQuery.fireContactRemoved(msc); + } + } + + recentQuery.fireContactReceived(newSourceContact); + } + + @Override + public void messageReceived(MessageReceivedEvent evt) + { + handle( + evt, + evt.getSourceContact().getProtocolProvider(), + evt.getSourceContact().getAddress()); + } + + @Override + public void messageDelivered(MessageDeliveredEvent evt) + { + handle( + evt, + evt.getDestinationContact().getProtocolProvider(), + evt.getDestinationContact().getAddress()); + } + + /** + * Not used. + * @param evt the MessageFailedEvent containing the ID of the + */ + @Override + public void messageDeliveryFailed(MessageDeliveryFailedEvent evt) + {} + + @Override + public void messageReceived(ChatRoomMessageReceivedEvent evt) + { + handle( + evt, + evt.getSourceChatRoom().getParentProvider(), + evt.getSourceChatRoom().getIdentifier()); + } + + @Override + public void messageDelivered(ChatRoomMessageDeliveredEvent evt) + { + handle( + evt, + evt.getSourceChatRoom().getParentProvider(), + evt.getSourceChatRoom().getIdentifier()); + } + + /** + * Not used. + * @param evt the ChatroomMessageDeliveryFailedEvent containing + */ + @Override + public void messageDeliveryFailed(ChatRoomMessageDeliveryFailedEvent evt) + {} + + @Override + public void messageReceived(AdHocChatRoomMessageReceivedEvent evt) + { + // TODO + } + + @Override + public void messageDelivered(AdHocChatRoomMessageDeliveredEvent evt) + { + // TODO + } + + /** + * Not used. + * @param evt the AdHocChatroomMessageDeliveryFailedEvent + */ + @Override + public void messageDeliveryFailed(AdHocChatRoomMessageDeliveryFailedEvent evt) + {} + + /** + * The contact query implementation. + */ + private class MessageHistoryContactQuery + extends AsyncContactQuery + { + MessageHistoryContactQuery(int contactCount) + { + super(MessageSourceService.this, + Pattern.compile("", + Pattern.CASE_INSENSITIVE | Pattern.LITERAL), + false); + } + + @Override + public void run() + { + for(MessageSourceContact rm : getRecentMessages()) + { + addQueryResult(rm); + } + } + + /** + * Notifies the ContactQueryListeners registered with this + * ContactQuery that a new SourceContact has been + * received. + * + * @param contact the SourceContact which has been received and + * which the registered ContactQueryListeners are to be notified + * about + */ + public void fireContactReceived(SourceContact contact) + { + fireContactReceived(contact, false); + } + + /** + * Notifies the ContactQueryListeners registered with this + * ContactQuery that a SourceContact has been + * changed. + * + * @param contact the SourceContact which has been changed and + * which the registered ContactQueryListeners are to be notified + * about + */ + public void fireContactChanged(SourceContact contact) + { + super.fireContactChanged(contact); + } + + /** + * Notifies the ContactQueryListeners registered with this + * ContactQuery that a SourceContact has been + * removed. + * + * @param contact the SourceContact which has been removed and + * which the registered ContactQueryListeners are to be notified + * about + */ + public void fireContactRemoved(SourceContact contact) + { + super.fireContactRemoved(contact); + } + + /** + * Adds a specific SourceContact to the list of + * SourceContacts to be returned by this ContactQuery in + * response to {@link #getQueryResults()}. + * + * @param sourceContact the SourceContact to be added to the + * queryResults of this ContactQuery + * @return true if the queryResults of this + * ContactQuery has changed in response to the call + */ + public boolean addQueryResult(SourceContact sourceContact) + { + return super.addQueryResult(sourceContact); + } + } +} diff --git a/src/net/java/sip/communicator/impl/msghistory/msghistory.manifest.mf b/src/net/java/sip/communicator/impl/msghistory/msghistory.manifest.mf index e2ce2296b..b1f996a5f 100644 --- a/src/net/java/sip/communicator/impl/msghistory/msghistory.manifest.mf +++ b/src/net/java/sip/communicator/impl/msghistory/msghistory.manifest.mf @@ -14,7 +14,8 @@ Import-Package: org.osgi.framework, net.java.sip.communicator.service.contactlist, net.java.sip.communicator.service.contactsource, net.java.sip.communicator.service.history.records, - net.java.sip.communicator.util, + net.java.sip.communicator.service.muc, + net.java.sip.communicator.util, org.jitsi.util, net.java.sip.communicator.util.account, net.java.sip.communicator.service.protocol, net.java.sip.communicator.service.protocol.icqconstants, diff --git a/src/net/java/sip/communicator/service/contactsource/ContactSourceService.java b/src/net/java/sip/communicator/service/contactsource/ContactSourceService.java index 37100305f..0ef7d41b4 100644 --- a/src/net/java/sip/communicator/service/contactsource/ContactSourceService.java +++ b/src/net/java/sip/communicator/service/contactsource/ContactSourceService.java @@ -35,6 +35,11 @@ public interface ContactSourceService */ public static final int CHAT_ROOM_TYPE = 3; + /** + * Type of a recent messages source. + */ + public static final int RECENT_MESSAGES_TYPE = 4; + /** * Returns the type of this contact source. * diff --git a/src/net/java/sip/communicator/service/msghistory/MessageHistoryService.java b/src/net/java/sip/communicator/service/msghistory/MessageHistoryService.java index 8ca3f579d..218a04a7a 100644 --- a/src/net/java/sip/communicator/service/msghistory/MessageHistoryService.java +++ b/src/net/java/sip/communicator/service/msghistory/MessageHistoryService.java @@ -29,6 +29,14 @@ public interface MessageHistoryService = "net.java.sip.communicator.service.msghistory." + "IS_MESSAGE_HISTORY_ENABLED"; + /** + * Name of the property that indicates whether the recent messages is + * enabled. + */ + public static final String PNAME_IS_RECENT_MESSAGES_DISABLED + = "net.java.sip.communicator.service.msghistory." + + "IS_RECENT_MESSAGES_DISABLED"; + /** * Name of the property that indicates whether the logging of messages is * enabled. diff --git a/src/net/java/sip/communicator/service/muc/MUCService.java b/src/net/java/sip/communicator/service/muc/MUCService.java index b2b157371..c2a302552 100644 --- a/src/net/java/sip/communicator/service/muc/MUCService.java +++ b/src/net/java/sip/communicator/service/muc/MUCService.java @@ -278,6 +278,17 @@ public static OperationSetMultiUserChat getMultiUserChatOpSet( : null; } + /** + * Finds the ChatRoomWrapper instance associated with the + * chat room. + * @param chatRoomID the id of the chat room. + * @param pps the provider of the chat room. + * @return the ChatRoomWrapper instance. + */ + public abstract ChatRoomWrapper findChatRoomWrapperFromChatRoomID( + String chatRoomID, + ProtocolProviderService pps); + /** * Goes through the locally stored chat rooms list and for each * {@link ChatRoomWrapper} tries to find the corresponding server stored