Przeglądaj źródła

feat: support screenshot cache

Casper Dai 3 lat temu
rodzic
commit
e84551631c

+ 114 - 0
src/utils/screenshot.js

@@ -0,0 +1,114 @@
+import {
+  subscribe,
+  publish
+} from '@/utils/mqtt'
+
+const cache = new Map()
+let listening = false
+
+export function getAndCheck ({ productId, id }, cb, expired) {
+  if (!listening) {
+    subscribe('+/+/screenshot/reply', onMessage)
+    listening = true
+  }
+  let inst = cache.get(id)
+  if (inst) {
+    inst.cb = cb
+  } else {
+    cache.set(id, inst = createInst(productId, id, cb))
+  }
+  if (retry(inst, expired)) {
+    screenshot(id, true)
+  } else {
+    cb(inst)
+  }
+}
+
+function retry (inst, expired = 30000) {
+  if (!inst) {
+    return false
+  }
+  if (inst.waiting && inst.timestamp + 10000 >= Date.now()) {
+    return false
+  }
+  if (inst.base64 && inst.timestamp + expired >= Date.now()) {
+    return false
+  }
+  return true
+}
+
+export function stop (deviceId) {
+  const inst = cache.get(deviceId)
+  if (inst) {
+    inst.cb = null
+  }
+}
+
+export function screenshot (deviceId, silence) {
+  const inst = cache.get(deviceId)
+  if (inst) {
+    inst.waiting = true
+    inst.base64 = null
+    publish(`${inst.productId}/${inst.deviceId}/screenshot/ask`, JSON.stringify({ timestamp: inst.timestamp })).then(() => {
+      startTimer(inst)
+    }, () => {
+      inst.waiting = false
+      if (!silence) {
+        this.$message({
+          type: 'warning',
+          message: '正在连接,请稍后再试'
+        })
+      }
+    }).finally(() => {
+      emit(inst)
+    })
+  }
+}
+
+function createInst (productId, deviceId, cb) {
+  return {
+    productId,
+    deviceId,
+    cb,
+    timer: -1,
+    waiting: false,
+    timestamp: Date.now(),
+    base64: null
+  }
+}
+
+function startTimer (inst) {
+  clearTimeout(inst.timer)
+  inst.timer = setTimeout(() => {
+    inst.waiting = false
+    emit(inst)
+  }, 20000)
+}
+
+function emit (inst) {
+  inst.cb && inst.cb(inst)
+}
+
+function onMessage (topic, message) {
+  const result = /^(\d+)\/(\d+)\/screenshot\/reply$/.exec(topic)
+  if (result) {
+    const inst = cache.get(result[2])
+    if (inst) {
+      clearTimeout(inst.timer)
+      inst.waiting = false
+      inst.base64 = `data:image/jpeg;base64,${message.replace(/\s/g, '')}`
+      inst.timestamp = Date.now()
+      emit(inst)
+    }
+  }
+}
+
+export function reset (deviceId) {
+  const inst = cache.get(deviceId)
+  if (inst) {
+    clearTimeout(inst.timer)
+    inst.waiting = false
+    inst.base64 = null
+    emit(inst)
+  }
+}

+ 26 - 25
src/views/dashboard/components/Device.vue

@@ -12,10 +12,11 @@
         class="l-flex__fill"
         :text="name"
       />
-      <template v-if="isOnline">
+      <template v-if="isActivated && isOnline">
         <i
           v-if="isShotting"
           class="l-flex__none el-icon-loading"
+          @click.stop
         />
         <i
           v-else
@@ -86,10 +87,15 @@ import { getTimeline } from '@/api/calendar'
 import { parseTime } from '@/utils'
 import {
   listen,
-  unlisten,
-  publish
+  unlisten
 } from '@/utils/mqtt'
 import { getName } from '@/utils/cache'
+import {
+  getAndCheck,
+  screenshot,
+  reset,
+  stop
+} from '@/utils/screenshot'
 
 export default {
   name: 'DeviceCard',
@@ -141,7 +147,7 @@ export default {
       return `地址:${this.device.remark}`
     },
     styles () {
-      return this.isOnline && this.shot ? {
+      return this.isActivated && this.isOnline && this.shot ? {
         backgroundImage: `url("${this.shot}"`
       } : null
     },
@@ -149,20 +155,35 @@ export default {
       return this.next ? `下一场:${this.next.startDate} ${this.next.startTime} ${this.next.name}` : ''
     }
   },
-  mounted () {
+  created () {
     if (this.isActivated) {
       listen(this.onMessage)
       this.getTimeline()
+      if (this.isOnline) {
+        getAndCheck(this.device, this.onScreenshotUpdate)
+      } else {
+        reset(this.device.id)
+      }
     }
     this.$timer = -1
   },
   beforeDestroy () {
     if (this.isActivated) {
       unlisten(this.onMessage)
+      if (this.isOnline) {
+        stop(this.device.id)
+      }
     }
     clearTimeout(this.$timer)
   },
   methods: {
+    screenshot () {
+      screenshot(this.device.id)
+    },
+    onScreenshotUpdate ({ waiting, base64 }) {
+      this.isShotting = waiting
+      this.shot = waiting ? null : base64
+    },
     onClick () {
       this.$router.push({
         name: 'device-detail',
@@ -174,9 +195,6 @@ export default {
         const result = new RegExp(`${this.device.productId}/${this.device.id}/(.+)`).exec(topic)
         if (result) {
           switch (result[1]) {
-            case 'screenshot/reply':
-              this.onScreenshotReply(message)
-              break
             case 'calendar/update':
               this.onCalendarUpdate(message)
               break
@@ -186,21 +204,6 @@ export default {
         }
       }
     },
-    screenshot () {
-      publish(`${this.device.productId}/${this.device.id}/screenshot/ask`, JSON.stringify({ timestamp: Date.now() })).then(() => {
-        this.isShotting = true
-        this.shot = null
-      }, () => {
-        this.$message({
-          type: 'warning',
-          message: '正在连接,请稍后再试'
-        })
-      })
-    },
-    onScreenshotReply (message) {
-      this.isShotting = false
-      this.shot = `data:image/jpeg;base64,${message.replace(/\s/g, '')}`
-    },
     onCalendarUpdate (message) {
       clearTimeout(this.$timer)
       try {
@@ -296,12 +299,10 @@ export default {
 .o-device {
   display: inline-flex;
   flex-direction: column;
-  // height: 220px;
   color: $black;
   line-height: 1;
   border-radius: $radius;
   background-color: #fff;
-  background-size: contain;
 
   &__block + &__block {
     margin-top: $spacing;

+ 2 - 4
src/views/dashboard/index.vue

@@ -159,16 +159,14 @@ export default {
     subscribe([
       '+/+/online',
       '+/+/offline',
-      '+/+/calendar/update',
-      '+/+/screenshot/reply'
+      '+/+/calendar/update'
     ], this.onMessage)
   },
   beforeDestroy () {
     unsubscribe([
       '+/+/online',
       '+/+/offline',
-      '+/+/calendar/update',
-      '+/+/screenshot/reply'
+      '+/+/calendar/update'
     ], this.onMessage)
   },
   methods: {

+ 4 - 1
src/views/device/detail/components/DeviceRuntime.vue

@@ -5,7 +5,10 @@
       v-bind="$attrs"
     />
     <template v-if="$attrs.online">
-      <screen-shot class="c-info-grid__item" />
+      <screen-shot
+        class="c-info-grid__item"
+        v-bind="$attrs"
+      />
       <download class="c-info-grid__item" />
     </template>
   </div>

+ 20 - 19
src/views/device/detail/components/ScreenShot.vue

@@ -22,42 +22,43 @@
 
 <script>
 import {
-  addListener,
-  removeListener,
-  send
-} from '../monitor'
+  getAndCheck,
+  screenshot,
+  stop
+} from '@/utils/screenshot'
 
 export default {
   name: 'DeviceScreenShot',
+  props: {
+    device: {
+      type: Object,
+      default: null
+    }
+  },
   data () {
     return {
       asking: false,
       styles: null
     }
   },
+  activated () {
+    getAndCheck(this.device, this.onScreenshotUpdate, 10000)
+  },
   created () {
-    addListener('screenshot', this.onUpdate)
+    getAndCheck(this.device, this.onScreenshotUpdate, 10000)
   },
   beforeDestroy () {
-    removeListener('screenshot', this.onUpdate)
+    stop()
   },
   methods: {
-    onUpdate (screenshot, { loading }) {
-      this.asking = loading
-      this.styles = !loading && screenshot ? {
-        backgroundImage: `url("${screenshot}"`
+    onScreenshotUpdate ({ waiting, base64 }) {
+      this.asking = waiting
+      this.styles = !waiting && base64 ? {
+        backgroundImage: `url("${base64}"`
       } : null
     },
-    ask () {
-      return send('screenshot/ask')
-    },
     invoke () {
-      this.ask().catch(() => {
-        this.$message({
-          type: 'warning',
-          message: '正在连接,请稍后再试'
-        })
-      })
+      screenshot(this.device.id, true)
     }
   }
 }

+ 7 - 0
src/views/device/detail/index.vue

@@ -63,6 +63,7 @@
 
 <script>
 import { getDevice } from '@/api/device'
+import { reset } from '@/utils/screenshot'
 import {
   start,
   stop,
@@ -159,6 +160,9 @@ export default {
           this.device = data
           this.isActivated = data.activate === 2
           this.isOnline = this.isActivated && data.onlineStatus === 1
+          if (!this.isOnline) {
+            reset(this.deviceId)
+          }
           start(this.device)
           addListener('online', this.onUpdate)
         }
@@ -171,6 +175,9 @@ export default {
     onUpdate (online) {
       this.isActivated = true
       this.isOnline = online
+      if (!this.isOnline) {
+        reset(this.deviceId)
+      }
     }
   }
 }

+ 0 - 13
src/views/device/detail/monitor.js

@@ -24,12 +24,10 @@ export function start (device) {
     `${productId}/${deviceId}/online`,
     `${productId}/${deviceId}/offline`,
     `${productId}/${deviceId}/status/reply`,
-    `${productId}/${deviceId}/screenshot/reply`,
     `${productId}/${deviceId}/resource/progress`
   ], onMessage)
   createImmediateType('online', { parser: onlineParser })
   createLoadType('status', { defaults: '未知', parser: statusParser })
-  createLoadType('screenshot', { parser: screenshotParser })
   createLoopType('download', { parser: downloadParser })
   if (__SENSOR__) {
     updateSensors(createLoopType('sensor', { parser: sensorParser }))
@@ -42,7 +40,6 @@ export function stop () {
       `${productId}/${deviceId}/online`,
       `${productId}/${deviceId}/offline`,
       `${productId}/${deviceId}/status/reply`,
-      `${productId}/${deviceId}/screenshot/reply`,
       `${productId}/${deviceId}/resource/progress`
     ], onMessage)
     productId = null
@@ -99,8 +96,6 @@ function getTypeBySend (topic) {
   switch (topic) {
     case 'status/ask':
       return 'status'
-    case 'screenshot/ask':
-      return 'screenshot'
     default:
       return null
   }
@@ -113,8 +108,6 @@ function getType (topic) {
       return 'online'
     case 'status/reply':
       return 'status'
-    case 'screenshot/reply':
-      return 'screenshot'
     case 'resource/progress':
       return 'download'
     default:
@@ -183,7 +176,6 @@ function onlineParser (inst, message, topic) {
     return true
   }
   onUpdate(types.get('status'), '{}')
-  onUpdate(types.get('screenshot'))
   onUpdate(types.get('download'), 'clear')
   return false
 }
@@ -199,11 +191,6 @@ function statusParser (inst, message) {
   return (inst.value = status)
 }
 
-function screenshotParser (inst, message) {
-  inst.value = message ? `data:image/jpeg;base64,${message.replace(/\s/g, '')}` : null
-  return inst.value
-}
-
 function downloadParser (inst, message) {
   try {
     if (message === 'clear') {