From 83411ad1277f8d4dfbc95aff2f15900150e84db3 Mon Sep 17 00:00:00 2001 From: 64850858 <648540858@qq.com> Date: Mon, 7 Jun 2021 15:11:53 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E4=BF=A1=E6=81=AF=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=9C=80=E8=BF=91=E6=B3=A8=E5=86=8C=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E5=92=8C=E6=9C=80=E8=BF=91=E5=BF=83=E8=B7=B3=E6=97=B6=E9=97=B4?= =?UTF-8?q?=EF=BC=8C=E5=BF=83=E8=B7=B3=E8=B6=85=E6=97=B6=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E5=8F=98=E4=B8=BA=E5=8F=AF=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/mysql.sql | 4 +- .../genersoft/iot/vmp/VManageBootstrap.java | 1 + .../iot/vmp/common/VideoManagerConstants.java | 4 +- .../com/genersoft/iot/vmp/conf/SipConfig.java | 6 +++ .../iot/vmp/gb28181/bean/Device.java | 33 +++++++++---- .../iot/vmp/gb28181/event/EventPublisher.java | 5 +- .../vmp/gb28181/event/online/OnlineEvent.java | 13 +++--- .../event/online/OnlineEventListener.java | 44 +++++++++++------- .../request/impl/MessageRequestProcessor.java | 13 +++--- .../request/impl/NotifyRequestProcessor.java | 4 +- .../impl/RegisterRequestProcessor.java | 28 ++++++----- .../iot/vmp/storager/dao/DeviceMapper.java | 9 ++++ src/main/resources/all-application.yml | 4 +- src/main/resources/application-dev.yml | 2 +- src/main/resources/wvp.sqlite | Bin 106496 -> 114688 bytes web_src/src/components/DeviceList.vue | 30 ++++++------ 16 files changed, 130 insertions(+), 70 deletions(-) diff --git a/sql/mysql.sql b/sql/mysql.sql index 3f3b876d..71e651a1 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -12,9 +12,11 @@ create table device transport varchar(50) null, streamMode varchar(50) null, online varchar(50) null, - registerTimeMillis int null, + registerTime varchar(50) null, + keepaliveTime varchar(50) null, ip varchar(50) not null, port int not null, + expires int not null, hostAddress varchar(50) not null ); diff --git a/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java b/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java index 7b749901..2d926587 100644 --- a/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java +++ b/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java @@ -23,4 +23,5 @@ public class VManageBootstrap extends LogManager { VManageBootstrap.context = SpringApplication.run(VManageBootstrap.class, args); } + } diff --git a/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java b/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java index 8af9956c..6d8f45d9 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java +++ b/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java @@ -39,7 +39,9 @@ public class VideoManagerConstants { public static final String EVENT_ONLINE_REGISTER = "1"; public static final String EVENT_ONLINE_KEEPLIVE = "2"; - + + public static final String EVENT_ONLINE_MESSAGE = "3"; + public static final String EVENT_OUTLINE_UNREGISTER = "1"; public static final String EVENT_OUTLINE_TIMEOUT = "2"; diff --git a/src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java index 32352c71..4fc28bc0 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java @@ -31,6 +31,9 @@ public class SipConfig { @Value("${sip.ptz.speed:50}") Integer speed; + @Value("${sip.keepaliveTimeOut:180}") + Integer keepaliveTimeOut; + public String getMonitorIp() { return monitorIp; } @@ -63,4 +66,7 @@ public class SipConfig { return speed; } + public Integer getKeepaliveTimeOut() { + return keepaliveTimeOut; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java index f587e757..a9185e0c 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java @@ -66,19 +66,24 @@ public class Device { /** * 注册时间 */ - private Long registerTimeMillis; + private String registerTime; /** * 心跳时间 */ - private Long KeepaliveTimeMillis; + private String keepaliveTime; /** * 通道个数 */ private int channelCount; + /** + * 注册有效期 + */ + private int expires; + public String getDeviceId() { return deviceId; } @@ -175,19 +180,27 @@ public class Device { this.channelCount = channelCount; } - public Long getRegisterTimeMillis() { - return registerTimeMillis; + public String getRegisterTime() { + return registerTime; } - public void setRegisterTimeMillis(Long registerTimeMillis) { - this.registerTimeMillis = registerTimeMillis; + public void setRegisterTime(String registerTime) { + this.registerTime = registerTime; } - public Long getKeepaliveTimeMillis() { - return KeepaliveTimeMillis; + public String getKeepaliveTime() { + return keepaliveTime; } - public void setKeepaliveTimeMillis(Long keepaliveTimeMillis) { - KeepaliveTimeMillis = keepaliveTimeMillis; + public void setKeepaliveTime(String keepaliveTime) { + this.keepaliveTime = keepaliveTime; + } + + public int getExpires() { + return expires; + } + + public void setExpires(int expires) { + this.expires = expires; } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java index 58b03dd0..04229621 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.gb28181.event; +import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.event.platformKeepaliveExpire.PlatformKeepaliveExpireEvent; import com.genersoft.iot.vmp.gb28181.event.platformNotRegister.PlatformNotRegisterEvent; import org.springframework.beans.factory.annotation.Autowired; @@ -22,9 +23,9 @@ public class EventPublisher { @Autowired private ApplicationEventPublisher applicationEventPublisher; - public void onlineEventPublish(String deviceId, String from) { + public void onlineEventPublish(Device device, String from) { OnlineEvent onEvent = new OnlineEvent(this); - onEvent.setDeviceId(deviceId); + onEvent.setDevice(device); onEvent.setFrom(from); applicationEventPublisher.publishEvent(onEvent); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEvent.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEvent.java index e077cb98..1e019569 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEvent.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEvent.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.gb28181.event.online; +import com.genersoft.iot.vmp.gb28181.bean.Device; import org.springframework.context.ApplicationEvent; /** @@ -18,18 +19,18 @@ public class OnlineEvent extends ApplicationEvent { super(source); } - private String deviceId; + private Device device; private String from; - public String getDeviceId() { - return deviceId; + public Device getDevice() { + return device; } - public void setDeviceId(String deviceId) { - this.deviceId = deviceId; + public void setDevice(Device device) { + this.device = device; } - + public String getFrom() { return from; } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEventListener.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEventListener.java index b9bb5b12..acdf2722 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEventListener.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/online/OnlineEventListener.java @@ -1,5 +1,7 @@ package com.genersoft.iot.vmp.gb28181.event.online; +import com.genersoft.iot.vmp.conf.SipConfig; +import com.genersoft.iot.vmp.gb28181.bean.Device; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -10,6 +12,9 @@ import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import com.genersoft.iot.vmp.utils.redis.RedisUtil; +import java.text.SimpleDateFormat; +import java.util.Date; + /** * @Description: 在线事件监听器,监听到离线后,修改设备离在线状态。 设备在线有两个来源: * 1、设备主动注销,发送注销指令,{@link com.genersoft.iot.vmp.gb28181.transmit.request.impl.RegisterRequestProcessor} @@ -28,39 +33,46 @@ public class OnlineEventListener implements ApplicationListener { @Autowired private RedisUtil redis; + @Autowired + private SipConfig sipConfig; + + private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + @Override public void onApplicationEvent(OnlineEvent event) { if (logger.isDebugEnabled()) { - logger.debug("设备上线事件触发,deviceId:" + event.getDeviceId() + ",from:" + event.getFrom()); + logger.debug("设备上线事件触发,deviceId:" + event.getDevice().getDeviceId() + ",from:" + event.getFrom()); } - - String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + event.getDeviceId(); - boolean needUpdateStorager = false; + Device device = event.getDevice(); + String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + event.getDevice().getDeviceId(); switch (event.getFrom()) { // 注册时触发的在线事件,先在redis中增加超时超时监听 case VideoManagerConstants.EVENT_ONLINE_REGISTER: - // TODO 超时时间暂时写死为180秒 - redis.set(key, event.getDeviceId(), 180); - needUpdateStorager = true; + // 超时时间 + redis.set(key, event.getDevice().getDeviceId(), sipConfig.getKeepaliveTimeOut()); + device.setRegisterTime(format.format(new Date(System.currentTimeMillis()))); break; - // 设备主动发送心跳触发的离线事件 + // 设备主动发送心跳触发的在线事件 case VideoManagerConstants.EVENT_ONLINE_KEEPLIVE: boolean exist = redis.hasKey(key); // 先判断是否还存在,当设备先心跳超时后又发送心跳时,redis没有监听,需要增加 if (!exist) { - needUpdateStorager = true; - redis.set(key, event.getDeviceId(), 180); + redis.set(key, event.getDevice().getDeviceId(), sipConfig.getKeepaliveTimeOut()); } else { - redis.expire(key, 180); + redis.expire(key, sipConfig.getKeepaliveTimeOut()); } + device.setKeepaliveTime(format.format(new Date(System.currentTimeMillis()))); + break; + // 设备主动发送消息触发的在线事件 + case VideoManagerConstants.EVENT_ONLINE_MESSAGE: + break; } - - if (needUpdateStorager) { - // 处理离线监听 - storager.online(event.getDeviceId()); - } + + device.setOnline(1); + // 处理上线监听 + storager.updateDevice(device); } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java index 114ffd45..1842efe2 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.request.impl; import java.io.ByteArrayInputStream; import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.*; import javax.sip.address.SipURI; @@ -226,7 +227,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { String name = rootElement.getName(); Element deviceIdElement = rootElement.element("DeviceID"); String deviceId = deviceIdElement.getText(); - + Device device = storager.queryVideoDevice(deviceId); if (name.equalsIgnoreCase("Query")) { // 区分是Response——查询响应,还是Query——查询请求 logger.info("接收到DeviceStatus查询消息"); FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME); @@ -259,7 +260,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { deferredResultHolder.invokeResult(msg); if (offLineDetector.isOnline(deviceId)) { - publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE); + publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_MESSAGE); } else { } } @@ -452,6 +453,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { String requestName = rootElement.getName(); Element deviceIdElement = rootElement.element("DeviceID"); String deviceId = deviceIdElement.getTextTrim().toString(); + Device device = storager.queryVideoDevice(deviceId); if (requestName.equals("Query")) { logger.info("接收到DeviceInfo查询消息"); FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME); @@ -468,7 +470,6 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { } } else { logger.debug("接收到DeviceInfo应答消息"); - Device device = storager.queryVideoDevice(deviceId); if (device == null) { return; } @@ -489,7 +490,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { // 回复200 OK responseAck(evt); if (offLineDetector.isOnline(deviceId)) { - publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE); + publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_MESSAGE); } } } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) { @@ -669,7 +670,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { // 回复200 OK responseAck(evt); if (offLineDetector.isOnline(deviceId)) { - publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE); + publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_MESSAGE); } } } @@ -776,7 +777,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { // 回复200 OK responseAck(evt); if (offLineDetector.isOnline(deviceId)) { - publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE); + publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE); } else { } }else { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/NotifyRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/NotifyRequestProcessor.java index 897b6ab8..2d669eb2 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/NotifyRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/NotifyRequestProcessor.java @@ -216,13 +216,13 @@ public class NotifyRequestProcessor extends SIPRequestAbstractProcessor { Element rootElement = getRootElement(evt); Element deviceIdElement = rootElement.element("DeviceID"); String deviceId = deviceIdElement.getText(); + Device device = storager.queryVideoDevice(deviceId); Element deviceListElement = rootElement.element("DeviceList"); if (deviceListElement == null) { return; } Iterator deviceListIterator = deviceListElement.elementIterator(); if (deviceListIterator != null) { - Device device = storager.queryVideoDevice(deviceId); if (device == null) { return; } @@ -324,7 +324,7 @@ public class NotifyRequestProcessor extends SIPRequestAbstractProcessor { // 回复200 OK response200Ok(evt); if (offLineDetector.isOnline(deviceId)) { - publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE); + publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_MESSAGE); } } } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java index 574f9c75..64be7eff 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java @@ -2,7 +2,10 @@ package com.genersoft.iot.vmp.gb28181.transmit.request.impl; import java.security.NoSuchAlgorithmException; import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; import java.util.Calendar; +import java.util.Date; import java.util.Locale; import javax.sip.InvalidArgumentException; @@ -70,7 +73,11 @@ public class RegisterRequestProcessor extends SIPRequestAbstractProcessor { boolean passwordCorrect = false; // 注册标志 0:未携带授权头或者密码错误 1:注册成功 2:注销成功 int registerFlag = 0; - Device device = null; + FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME); + AddressImpl address = (AddressImpl) fromHeader.getAddress(); + SipUri uri = (SipUri) address.getURI(); + String deviceId = uri.getUser(); + Device device = storager.queryVideoDevice(deviceId); AuthorizationHeader authorhead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME); // 校验密码是否正确 if (authorhead != null) { @@ -103,13 +110,17 @@ public class RegisterRequestProcessor extends SIPRequestAbstractProcessor { response.addHeader(dateHeader); ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(Expires.NAME); + if (expiresHeader == null) { + response = getMessageFactory().createResponse(Response.BAD_REQUEST, request); + getServerTransaction(evt).sendResponse(response); + return; + } // 添加Contact头 response.addHeader(request.getHeader(ContactHeader.NAME)); // 添加Expires头 response.addHeader(request.getExpires()); // 获取到通信地址等信息 - FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME); ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME); String received = viaHeader.getReceived(); int rPort = viaHeader.getRPort(); @@ -119,10 +130,7 @@ public class RegisterRequestProcessor extends SIPRequestAbstractProcessor { rPort = viaHeader.getPort(); } // - AddressImpl address = (AddressImpl) fromHeader.getAddress(); - SipUri uri = (SipUri) address.getURI(); - String deviceId = uri.getUser(); - device = storager.queryVideoDevice(deviceId); + if (device == null) { device = new Device(); device.setStreamMode("UDP"); @@ -132,11 +140,12 @@ public class RegisterRequestProcessor extends SIPRequestAbstractProcessor { device.setPort(rPort); device.setHostAddress(received.concat(":").concat(String.valueOf(rPort))); // 注销成功 - if (expiresHeader != null && expiresHeader.getExpires() == 0) { + if (expiresHeader.getExpires() == 0) { registerFlag = 2; } // 注册成功 else { + device.setExpires(expiresHeader.getExpires()); registerFlag = 1; // 判断TCP还是UDP boolean isTcp = false; @@ -154,10 +163,7 @@ public class RegisterRequestProcessor extends SIPRequestAbstractProcessor { // 下发catelog查询目录 if (registerFlag == 1 ) { logger.info("[{}] 注册成功! deviceId:" + device.getDeviceId(), requestAddress); - device.setRegisterTimeMillis(System.currentTimeMillis()); - storager.updateDevice(device); - publisher.onlineEventPublish(device.getDeviceId(), VideoManagerConstants.EVENT_ONLINE_REGISTER); - + publisher.onlineEventPublish(device, VideoManagerConstants.EVENT_ONLINE_REGISTER); // 重新注册更新设备和通道,以免设备替换或更新后信息无法更新 handler.onRegister(device); } else if (registerFlag == 2) { diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java index bcc33de2..d597f35b 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java @@ -27,6 +27,9 @@ public interface DeviceMapper { "ip," + "port," + "hostAddress," + + "expires," + + "registerTime," + + "keepaliveTime," + "online" + ") VALUES (" + "#{deviceId}," + @@ -39,6 +42,9 @@ public interface DeviceMapper { "#{ip}," + "#{port}," + "#{hostAddress}," + + "#{expires}," + + "#{registerTime}," + + "#{keepaliveTime}," + "#{online}" + ")") int add(Device device); @@ -56,6 +62,9 @@ public interface DeviceMapper { ", port=${port}" + ", hostAddress='${hostAddress}'" + ", online=${online}" + + ", registerTime='${registerTime}'" + + ", keepaliveTime='${keepaliveTime}'" + + ", expires=${expires}" + "WHERE deviceId='${deviceId}'"+ " "}) int update(Device device); diff --git a/src/main/resources/all-application.yml b/src/main/resources/all-application.yml index 10667be4..6923d970 100644 --- a/src/main/resources/all-application.yml +++ b/src/main/resources/all-application.yml @@ -63,8 +63,10 @@ sip: domain: 4401020049 # [可选] id: 44010200492000000001 - # [可选] 默认设备认证密码,后续扩展使用设备单独密码 + # [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验 password: admin123 + # [可选] 心跳超时时间, 建议设置为心跳周期的三倍 + keepaliveTimeOut: 180 #zlm服务器配置 media: diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index b494769f..b70bce41 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -39,7 +39,7 @@ sip: domain: 4401020049 # [可选] id: 44010200492000000001 - # [可选] 默认设备认证密码,后续扩展使用设备单独密码 + # [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验 password: admin123 #zlm服务器配置 diff --git a/src/main/resources/wvp.sqlite b/src/main/resources/wvp.sqlite index 5def77b03700840fd27e178cf26a62a79c671261..3131c0f05b1486b5b922a97f05cdb959af05c0b2 100644 GIT binary patch delta 309 zcmZoTz}C>fK0#XWI|BoQ3=}glFfh%ZsAJ6fok7pPc4NvC{>>Z<{>X1;F<>#+%;E4u zep3U>10goH8V3G#+b&GIFr+88At0Y!qYKZkWaRpP7Y$fw695 zV-@4{b@_~8%z{is)7c9cW_@;X zNlC_5>*?uvjOW>uI73_`LZ-XsGX}^hWv8YVB<5t6rG{kYrb1+NxHy$kD+)4;Ql|q= zmEuWBEz3+!^-NLHB j$*8{FzLN2v+GYiX2mBK!sIxKxog}?kP~koQ!~j+RdLC6s delta 257 zcmZo@U~f3UHbGkOGXn#I6c9rJQ~yLAW7f|MdiFOrrYzxSYJI9lUc09>FF2Z>h2n(!^NqTSulNL5u@<*>|#dq=?`icd8gkhX5^W^E}t=snV --> - + - - - - + + - + - +