Passed
Push — master ( 2093cb...8afb9a )
by Sam
02:27
created

Client::getFeedbackTokens()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 15
rs 9.4285
cc 3
eloc 8
nc 4
nop 0
1
<?php
2
3
namespace FastAPNS;
4
5
class Client {
6
  const FASTAPNS_BATCH_SIZE_DEFAULT = 1700;
7
  const FASTAPNS_BATCH_SUCCESS = 1;
8
  const FASTAPNS_BATCH_NEEDS_REWIND = 2;
9
10
  /**
11
   * @var ClientStreamSocket
12
   */
13
  private $client_stream_socket;
14
15
  private $batch_size;
16
17
  private $batches;
18
19
  private $badTokens;
20
21
  public function __construct($stream_socket_client, $batch_size = Client::FASTAPNS_BATCH_SIZE_DEFAULT) {
22
    $this->client_stream_socket = $stream_socket_client;
23
    $this->batch_size = $batch_size;
24
  }
25
26
  public function getBadTokens() {
27
    return $this->badTokens;
28
  }
29
30
  public function send($payload, $tokens, $expiry = 0) {
31
    if (!$this->client_stream_socket->isConnected()) {
32
      $this->client_stream_socket->connect();
33
    }
34
35
    $payload = is_array($payload) ? json_encode($payload) : $payload;
36
37
    $this->batches = array();
38
    $this->badTokens = array();
39
40
    $batch = array();
41
    $currentBatchIndex = 0;
42
43
    $total = count($tokens);
44
45
    for ($i = 0; $i < $total; $i += 1) {
46
      $batch[] = $tokens[$i];
47
48
      if (count($batch) === $this->batch_size || $i === $total - 1) {
49
        $this->batches[] = $batch;
50
51
        $this->processBatches($payload, $expiry, $total);
52
53
        $batch = array();
54
        $currentBatchIndex += 1;
55
      }
56
    }
57
  }
58
59
  private function processBatches($payload, $expiry, $total) {
60
    $maxBatchOffset = count($this->batches) - 1;
61
    $batchOffset = $maxBatchOffset;
62
    $tokenOffset = 0;
63
64
    while ($batchOffset <= $maxBatchOffset) {
65
      $tokenOffset = $this->sendBatch($payload, $expiry, $total, $batchOffset, $tokenOffset);
66
67
      if ($tokenOffset < count($this->batches[$batchOffset])) {
68
        $rewind = $this->rewind($batchOffset * $this->batch_size + $tokenOffset);
69
70
        $batchOffset = floor($rewind / $this->batch_size);
71
        $tokenOffset = $rewind - ($batchOffset * $this->batch_size);
72
73
        continue;
74
      }
75
76
      $batchOffset += 1;
77
      $tokenOffset = 0;
78
    }
79
  }
80
81
  private function sendBatch($payload, $expiry, $total, $batchOffset, $tokenOffset) {
82
    $batch = $this->batches[$batchOffset];
83
    $batchSize = count($batch);
84
    $confirm = $batchOffset == ceil($total / $this->batch_size) - 1;
85
86
    while ($tokenOffset < $batchSize) {
87
      $token = $batch[$tokenOffset];
88
89
      $notification_bytes = chr(1);
90
      $notification_bytes .= pack('N', ($batchOffset * $this->batch_size) + $tokenOffset);
91
      $notification_bytes .= pack('N', $expiry);
92
      $notification_bytes .= chr(0) . chr(32);
93
      $notification_bytes .= pack('H*', $token);
94
      $notification_bytes .= chr(0) . chr(mb_strlen($payload));
95
      $notification_bytes .= $payload;
96
97
      $result = $this->client_stream_socket->write($notification_bytes);
98
99
      if ($result == ClientStreamSocket::FASTAPNS_STATUS_WRITABLE) {
100
        continue;
101
      }
102
103
      if ($result == ClientStreamSocket::FASTAPNS_STATUS_READABLE) {
104
        return $tokenOffset;
105
      }
106
107
      if ($confirm && $tokenOffset == $batchSize - 1) {
108
        $result = $this->client_stream_socket->status(TRUE);
109
110
        if ($result == ClientStreamSocket::FASTAPNS_STATUS_READABLE) {
111
          return $tokenOffset;
112
        }
113
      }
114
115
      $tokenOffset += 1;
116
    }
117
118
    return $tokenOffset;
119
  }
120
121
  private function rewind($currentPointer) {
122
    $rewind = $currentPointer;
123
124
    $socket = $this->client_stream_socket;
125
126
    $error = $socket->read();
127
128
    if (!empty($error)) {
129
      $rewind = $error['identifier'];
130
131
      if ($error['status'] === 8) {
132
        $batchIndex = (int) floor($rewind / $this->batch_size);
133
        $tokenIndex = $rewind % $this->batch_size;
134
135
        $this->badTokens[] = $this->batches[$batchIndex][$tokenIndex];
136
137
        $rewind += 1;
138
      } else if ($error['status'] === 10) {
139
        $socket->reconnect();
140
      } else {
141
        throw new \Exception('Unrecoverable error sending notification: please check your payload.');
142
      }
143
    } else {
144
      $socket->reconnect();
145
    }
146
147
    return $rewind;
148
  }
149
150
  public function getFeedbackTokens() {
151
    if (!$this->client_stream_socket->isConnected()) {
152
      $this->client_stream_socket->connect();
153
    }
154
155
    $feedback = $this->client_stream_socket->readFeedback();
156
157
    $tokens = array();
158
159
    foreach ($feedback as $item) {
160
      $tokens[] = $item['devtoken'];
161
    }
162
163
    return $tokens;
164
  }
165
}
166