001package ball.upnp.ssdp; 002/*- 003 * ########################################################################## 004 * UPnP/SSDP Implementation Classes 005 * $Id: SSDPDiscoveryCache.java 7215 2021-01-03 18:39:51Z ball $ 006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-upnp/trunk/src/main/java/ball/upnp/ssdp/SSDPDiscoveryCache.java $ 007 * %% 008 * Copyright (C) 2013 - 2021 Allen D. Ball 009 * %% 010 * Licensed under the Apache License, Version 2.0 (the "License"); 011 * you may not use this file except in compliance with the License. 012 * You may obtain a copy of the License at 013 * 014 * http://www.apache.org/licenses/LICENSE-2.0 015 * 016 * Unless required by applicable law or agreed to in writing, software 017 * distributed under the License is distributed on an "AS IS" BASIS, 018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 019 * See the License for the specific language governing permissions and 020 * limitations under the License. 021 * ########################################################################## 022 */ 023import java.net.DatagramSocket; 024import java.net.URI; 025import java.util.List; 026import java.util.Objects; 027import java.util.concurrent.ConcurrentSkipListMap; 028import java.util.concurrent.ScheduledFuture; 029import java.util.concurrent.TimeUnit; 030import java.util.regex.Pattern; 031import lombok.NoArgsConstructor; 032import lombok.ToString; 033import org.apache.http.Header; 034 035import static java.util.concurrent.TimeUnit.MILLISECONDS; 036import static java.util.concurrent.TimeUnit.MINUTES; 037import static java.util.concurrent.TimeUnit.SECONDS; 038 039/** 040 * SSDP discovery cache implementation. 041 * 042 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 043 * @version $Revision: 7215 $ 044 */ 045@NoArgsConstructor 046public class SSDPDiscoveryCache 047 extends ConcurrentSkipListMap<URI,SSDPMessage> 048 implements SSDPDiscoveryService.Listener { 049 private static final long serialVersionUID = 2743071044637511801L; 050 051 /** @serial */ private ScheduledFuture<?> expirer = null; 052 /** @serial */ private ScheduledFuture<?> msearch = null; 053 /** @serial */ private final List<SSDPDiscoveryService.Listener> listeners = 054 List.of(new NOTIFY(), new MSEARCH()); 055 056 @Override 057 public void register(SSDPDiscoveryService service) { 058 if (expirer == null) { 059 expirer = 060 service.scheduleAtFixedRate(() -> expirer(service), 061 0, 60, SECONDS); 062 } 063 064 if (msearch == null) { 065 msearch = 066 service.scheduleAtFixedRate(() -> msearch(service), 067 0, 300, SECONDS); 068 } 069 070 listeners.stream().forEach(t -> service.addListener(t)); 071 } 072 073 @Override 074 public void unregister(SSDPDiscoveryService service) { 075 ScheduledFuture<?> expirer = this.expirer; 076 077 if (expirer != null) { 078 expirer.cancel(true); 079 } 080 081 ScheduledFuture<?> msearch = this.msearch; 082 083 if (msearch != null) { 084 msearch.cancel(true); 085 } 086 } 087 088 @Override 089 public void sendEvent(SSDPDiscoveryService service, 090 DatagramSocket socket, SSDPMessage message) { 091 } 092 093 @Override 094 public void receiveEvent(SSDPDiscoveryService service, 095 DatagramSocket socket, SSDPMessage message) { 096 } 097 098 private void expirer(SSDPDiscoveryService service) { 099 long now = now(); 100 boolean pending = 101 values().stream() 102 .mapToLong(t -> MINUTES.convert(t.getExpiration() - now, MILLISECONDS)) 103 .anyMatch(t -> t <= expirer.getDelay(MINUTES)); 104 boolean expired = values().removeIf(t -> t.getExpiration() < now); 105 106 if (expired || pending) { 107 service.submit(() -> msearch(service)); 108 } 109 } 110 111 private void msearch(SSDPDiscoveryService service) { 112 service.msearch(15, SSDPMessage.SSDP_ALL); 113 } 114 115 private long now() { return System.currentTimeMillis(); } 116 117 private void update(URI usn, SSDPMessage message) { 118 if (usn != null) { 119 long time = now(); 120 121 if (message.getExpiration() > time) { 122 put(usn, message); 123 } 124 } 125 } 126 127 @ToString 128 private class NOTIFY extends SSDPDiscoveryService.RequestHandler { 129 public NOTIFY() { super(SSDPRequest.Method.NOTIFY); } 130 131 @Override 132 public void run(SSDPDiscoveryService service, 133 DatagramSocket socket, SSDPRequest request) { 134 String nts = request.getHeaderValue(SSDPMessage.NTS); 135 136 if (Objects.equals(SSDPMessage.SSDP_ALIVE, nts)) { 137 update(request.getUSN(), request); 138 } else if (Objects.equals(SSDPMessage.SSDP_UPDATE, nts)) { 139 /* update(request.getUSN(), request); */ 140 } else if (Objects.equals(SSDPMessage.SSDP_BYEBYE, nts)) { 141 remove(request.getUSN()); 142 } 143 } 144 } 145 146 @NoArgsConstructor @ToString 147 private class MSEARCH extends SSDPDiscoveryService.ResponseHandler { 148 @Override 149 public void run(SSDPDiscoveryService service, 150 DatagramSocket socket, SSDPResponse response) { 151 update(response.getUSN(), response); 152 } 153 } 154}