Completed
Push — master ( 74c577...c1ce98 )
by Kirill
06:10
created

GitterSync::getRoom()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
/**
3
 * This file is part of GitterBot package.
4
 *
5
 * @author Serafim <[email protected]>
6
 * @date 24.09.2015 15:27
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
namespace Interfaces\Console\Commands;
12
13
use Carbon\Carbon;
14
use Core\Mappers\UserMapper;
15
use Domains\Message;
16
use Domains\User;
17
use Gitter\Client;
18
use Gitter\Support\ApiIterator;
19
use Illuminate\Console\Command;
20
use Illuminate\Contracts\Config\Repository;
21
use Illuminate\Contracts\Container\Container;
22
use Illuminate\Database\Connection;
23
use InvalidArgumentException;
24
use Ramsey\Uuid\Uuid;
25
use Serafim\Evacuator\Evacuator;
26
27
/**
28
 * Class GitterSync
29
 * @package Interfaces\Console\Commands
30
 */
31
class GitterSync extends Command
32
{
33
    /**
34
     * The name and signature of the console command.
35
     * @var string
36
     */
37
    protected $signature = 'gitter:sync {room}';
38
39
40
    /**
41
     * The console command description.
42
     * @var string
43
     */
44
    protected $description = 'Fill users karma from all messages of target room.';
45
46
    /**
47
     * Execute the console command.
48
     *
49
     * @param Repository $config
50
     * @param Container $container
51
     * @param Connection $db
52
     *
53
     * @return mixed
54
     * @throws \Throwable
55
     */
56
    public function handle(Repository $config, Container $container, Connection $db)
57
    {
58
        $config->set('gitter.output', false);
59
60
61
        // Sync users
62 View Code Duplication
        $db->transaction(function () use ($container) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
63
            $this->comment('Start users synchronize at ' . ($start = Carbon::now()));
64
            $container->call([$this, 'importUsers']);
65
            $this->comment('Ends ' . Carbon::now()->diffForHumans($start));
66
        });
67
68
69
        $this->output->newLine();
70
71
72
        // Sync messages
73 View Code Duplication
        $db->transaction(function () use ($container) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
74
            $this->comment('Start messages synchronize at ' . ($start = Carbon::now()));
75
            $container->call([$this, 'importMessages']);
76
            $this->comment('Ends ' . Carbon::now()->diffForHumans($start));
77
        });
78
79
80
        $this->output->newLine();
81
    }
82
83
    /**
84
     * @param Client $client
85
     * @param Connection $db
86
     * @throws \Throwable
87
     */
88
    public function importMessages(Client $client, Connection $db)
89
    {
90
        $limit          = 100;
91
        $lastMessageId  = null;
92
        $room           = $this->getRoom($client);
93
        $rootTimeZone   = new \DateTimeZone('UTC');
94
95
96
        $messages = new ApiIterator(function ($page) use ($client, $room, $limit, &$lastMessageId) {
0 ignored issues
show
Unused Code introduced by
The parameter $page is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
97
            $query = ['limit' => $limit];
98
99
            if ($lastMessageId !== null) {
100
                $query['beforeId'] = $lastMessageId;
101
            }
102
103
            $result = $this->rescue(function () use ($room, $query, $client) {
104
                return $client->http->getMessages($room->id, $query)->wait();
105
            });
106
107
            if (count($result) > 0) {
108
                $lastMessageId = $result[0]->id;
109
            }
110
111
            return $result;
112
        });
113
114
115
        foreach ($messages as $i => $message) {
116
            $data = [
117
                'id'            => Uuid::uuid4()->toString(),
118
                'gitter_id'     => $message->id,
119
                'room_id'       => $room->id,
120
                'text'          => $message->text,
121
                'text_rendered' => $message->html,
122
                'user_id'       => $message->fromUser->id,
123
                'created_at'    => new Carbon($message->sent, $rootTimeZone),
124
                'updated_at'    => new Carbon($message->sent, $rootTimeZone),
125
            ];
126
127
            if (property_exists($message, 'editedAt') && $message->editedAt) {
128
                $data['updated_at'] = new Carbon($message->editedAt, $rootTimeZone);
129
            }
130
131
            /*
132
             ! Bug: Only 1 item inserting by 1 sql query
133
             ! @see: https://github.com/gitterHQ/gitter/issues/1184
134
             !
135
             ! TODO This operations (delete + insert) will be very slow. Optimize later %)
136
             */
137
            try {
138
139
                $db->transaction(function () use ($message, $data, $db) {
140
                    $db->table('messages')->where('gitter_id', $data['gitter_id'])->delete();
141
                    $db->table('messages')->insert($data);
142
143
                    $db->table('urls')->where('message_id', $data['id'])->delete();
144
                    $db->table('urls')->insert(
145
                        $this->getUrlsFromMessage($data, (array)$message->urls)
146
                    );
147
148
                    $db->table('mentions')->where('message_id', $data['id'])->delete();
149
                    $db->table('mentions')->insert(
150
                        $this->getMentionsFromMessage($data, (array)$message->mentions)
151
                    );
152
153
                });
154
155
                $this->info(
156
                    '#' . $i . ' ' .
157
                    $data['created_at'] . ' ' .
158
                    mb_substr(str_replace("\n", '', $data['text']), 0, 32) . '... '
159
160
                );
161
162
            } catch (\Throwable $e) {
163
164
                $this->error($e->getMessage() . "\n" . $e->getTraceAsString());
165
166
            }
167
        }
168
    }
169
170
    /**
171
     * This command will returns an object of the room
172
     *
173
     * @param Client $client
174
     * @return mixed
175
     * @throws \Throwable
176
     */
177
    private function getRoom(Client $client)
178
    {
179
        return $this->rescue(function () use ($client) {
180
            return $client->http->getRoomById($this->argument('room'))->wait();
0 ignored issues
show
Bug introduced by
It seems like $this->argument('room') targeting Illuminate\Console\Command::argument() can also be of type array; however, Gitter\Bus\HttpBus::getRoomById() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
181
        });
182
    }
183
184
    /**
185
     * This method must be wrap ALL request actions
186
     *
187
     * @param \Closure $request
188
     * @return mixed
189
     * @throws \Throwable
190
     */
191
    private function rescue(\Closure $request)
192
    {
193
        return (new Evacuator($request))
194
            ->retry(Evacuator::INFINITY_RETRIES)
195
            ->catch(function (\Throwable $e) {
196
                $this->error($e->getMessage() . "\n" . '// retry again');
197
                sleep(1);
198
            })
199
            ->invoke();
200
    }
201
202
    /**
203
     * This method run an users export
204
     *
205
     * @param Client $client
206
     * @throws \Throwable
207
     */
208
    public function importUsers(Client $client)
209
    {
210
        $room = $this->getRoom($client);
211
212
213
        $users = new ApiIterator(function ($page) use ($client, $room) {
214
            return $this->rescue(function () use ($room, $page, $client) {
215
216
                return $client->http->getRoomUsers($room->id, ['limit' => 30, 'skip' => 30 * $page])->wait();
217
218
            });
219
        });
220
221
222
        foreach ($users as $i => $user) {
223
            $user = UserMapper::fromGitterObject($user);
224
            $this->comment('#' . $i . ' @' . $user->login);
225
        }
226
    }
227
228
    /**
229
     * @param Message $message
230
     * @throws InvalidArgumentException
231
     */
232
    protected function onMessage(Message $message)
233
    {
234
        $collection = $this->karma->validate($message);
0 ignored issues
show
Bug introduced by
The property karma does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
235
236
        foreach ($collection as $state) {
237
            $user = $state->getUser();
238
239
            if ($state->isIncrement()) {
240
                $message->user->addKarmaTo($user, $message);
241
            }
242
243
            if ($state->isIncrement() || $state->isTimeout() || $state->isSelf()) {
244
                echo "\r" . '[' . $message->created_at . '] ' .
245
                    $state->getTranslation($user->karma_text) . "\n";
246
            }
247
        }
248
    }
249
250
    /**
251
     * @param array $message
252
     * @param array $mentions
253
     * @return array
254
     */
255
    private function getMentionsFromMessage(array $message, array $mentions)
256
    {
257
        $result = [];
258
259
        foreach ($mentions as $mention) {
260
            if (property_exists($mention, 'userId') && $mention->userId) {
261
                $result[] = [
262
                    'id'         => Uuid::uuid4()->toString(),
263
                    'message_id' => $message['id'],
264
                    'user_id'    => $message['user_id'],
265
                ];
266
            }
267
        }
268
269
        return $result;
270
    }
271
272
    /**
273
     * @param array $message
274
     * @param array $urls
275
     * @return array
276
     */
277
    private function getUrlsFromMessage(array $message, array $urls)
278
    {
279
        $result = [];
280
281
        foreach ($urls as $url) {
282
            $result[] = [
283
                'id'         => Uuid::uuid4()->toString(),
284
                'message_id' => $message['id'],
285
                'url'        => $url->url,
286
            ];
287
        }
288
289
        return $result;
290
    }
291
}
292