Passed
Push — master ( 96ed6e...d5b540 )
by Marcel
05:09 queued 02:42
created

DataloadController::executeBySchedule()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Analytics
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the LICENSE.md file.
7
 *
8
 * @author Marcel Scherello <[email protected]>
9
 * @copyright 2020 Marcel Scherello
10
 */
11
12
namespace OCA\Analytics\Controller;
13
14
use Exception;
15
use OCA\Analytics\Activity\ActivityManager;
16
use OCA\Analytics\Db\DataloadMapper;
17
use OCP\AppFramework\Controller;
18
use OCP\AppFramework\Http\DataResponse;
19
use OCP\AppFramework\Http\NotFoundResponse;
20
use OCP\Files\NotFoundException;
21
use OCP\IL10N;
22
use OCP\ILogger;
23
use OCP\IRequest;
24
25
class DataloadController extends Controller
26
{
27
    private $logger;
28
    private $StorageController;
29
    private $DataSourceController;
30
    private $userId;
31
    private $ActivityManager;
32
    private $DatasetController;
33
    private $l10n;
34
    private $DataloadMapper;
35
36
    public function __construct(
37
        string $AppName,
38
        IRequest $request,
39
        IL10N $l10n,
40
        $userId,
41
        ILogger $logger,
42
        ActivityManager $ActivityManager,
43
        DataSourceController $DataSourceController,
44
        DatasetController $DatasetController,
45
        StorageController $StorageController,
46
        DataloadMapper $DataloadMapper
47
    )
48
    {
49
        parent::__construct($AppName, $request);
50
        $this->l10n = $l10n;
51
        $this->userId = $userId;
52
        $this->logger = $logger;
53
        $this->StorageController = $StorageController;
54
        $this->ActivityManager = $ActivityManager;
55
        $this->DataSourceController = $DataSourceController;
56
        $this->DatasetController = $DatasetController;
57
        $this->DataloadMapper = $DataloadMapper;
58
    }
59
60
    // Dataloads
61
    // Dataloads
62
    // Dataloads
63
64
    /**
65
     * create a new dataload
66
     *
67
     * @NoAdminRequired
68
     * @param int $datasetId
69
     * @param int $datasourceId
70
     * @return DataResponse
71
     */
72
    public function create(int $datasetId, int $datasourceId)
73
    {
74
        return new DataResponse(['id' => $this->DataloadMapper->create($datasetId, $datasourceId)]);
75
    }
76
77
    /**
78
     * get all dataloads for a dataset
79
     *
80
     * @NoAdminRequired
81
     * @param int $datasetId
82
     * @return DataResponse
83
     */
84
    public function read(int $datasetId)
85
    {
86
        $result = array();
87
        $result['dataloads'] = $this->DataloadMapper->read($datasetId);
88
        return new DataResponse($result);
89
    }
90
91
    /**
92
     * update dataload
93
     *
94
     * @NoAdminRequired
95
     * @param int $dataloadId
96
     * @param $name
97
     * @param $option
98
     * @param $schedule
99
     * @return DataResponse
100
     */
101
    public function update(int $dataloadId, $name, $option, $schedule)
102
    {
103
        return new DataResponse(['update' => $this->DataloadMapper->update($dataloadId, $name, $option, $schedule)]);
104
    }
105
106
    /**
107
     * delete a dataload
108
     *
109
     * @NoAdminRequired
110
     * @param int $dataloadId
111
     * @return bool
112
     */
113
    public function delete(int $dataloadId)
114
    {
115
        return $this->DataloadMapper->delete($dataloadId);
116
    }
117
118
    /**
119
     * simulate a dataload and output its data
120
     *
121
     * @NoAdminRequired
122
     * @param int $dataloadId
123
     * @return DataResponse
124
     * @throws NotFoundException
125
     */
126
    public function simulate(int $dataloadId)
127
    {
128
        $result = $this->getDataFromDatasource($dataloadId);
129
        return new DataResponse($result);
130
    }
131
132
    /**
133
     * execute all dataloads depending on their schedule
134
     * daily or hourly
135
     *
136
     * @NoAdminRequired
137
     * @param $schedule
138
     * @return void
139
     * @throws Exception
140
     */
141
    public function executeBySchedule($schedule)
142
    {
143
        $schedules = $this->DataloadMapper->getDataloadBySchedule($schedule);
144
        //$this->logger->debug('DataLoadController 145: execute schedule '.$schedule);
145
        foreach ($schedules as $dataload) {
146
            //$this->logger->debug('DataLoadController 147: execute dataload '.$dataload['id']);
147
            $this->execute($dataload['id']);
148
        }
149
    }
150
151
    /**
152
     * execute a dataload from datasource and store into dataset
153
     *
154
     * @NoAdminRequired
155
     * @param int $dataloadId
156
     * @return DataResponse
157
     * @throws Exception
158
     */
159
    public function execute(int $dataloadId)
160
    {
161
        $dataloadMetadata = $this->DataloadMapper->getDataloadById($dataloadId);
162
        $result = $this->getDataFromDatasource($dataloadId);
163
        $insert = $update = 0;
164
        $datasetId = $result['datasetId'];
165
        $option = json_decode($dataloadMetadata['option'], true);
166
167
        if (isset($option['delete']) and $option['delete'] === 'true') {
168
            $this->StorageController->delete($datasetId, '*', '*');
169
        }
170
        if ($result['error'] === 0) {
171
            foreach ($result['data'] as &$row) {
172
                if (count($row) === 2) {
173
                    // if datasource only delivers 2 colums, the value needs to be in the last one
174
                    $row[2] = $row[1];
175
                    $row[1] = null;
176
                }
177
                $action = $this->StorageController->update($datasetId, $row[0], $row[1], $row[2], $dataloadMetadata['user_id']);
178
                $insert = $insert + $action['insert'];
179
                $update = $update + $action['update'];
180
            }
181
        }
182
183
        $result = [
184
            'insert' => $insert,
185
            'update' => $update,
186
            'error' => $result['error'],
187
        ];
188
189
        if ($result['error'] === 0) $this->ActivityManager->triggerEvent($datasetId, ActivityManager::OBJECT_DATA, ActivityManager::SUBJECT_DATA_ADD_DATALOAD, $dataloadMetadata['user_id']);
190
191
        return new DataResponse($result);
192
    }
193
194
    /**
195
     * get the data from datasource
196
     * to be used in simulation or execution
197
     *
198
     * @NoAdminRequired
199
     * @param int $dataloadId
200
     * @return array|NotFoundResponse
201
     * @throws NotFoundResponse|NotFoundException
202
     */
203
    private function getDataFromDatasource(int $dataloadId)
204
    {
205
        $dataloadMetadata = $this->DataloadMapper->getDataloadById($dataloadId);
206
        $datasetMetadata = $this->DatasetController->getOwnDataset($dataloadMetadata['dataset'], $dataloadMetadata['user_id']);
207
208
        if (!empty($datasetMetadata)) {
209
            $option = json_decode($dataloadMetadata['option'], true);
210
            $option['user_id'] = $dataloadMetadata['user_id'];
211
212
            //$this->logger->debug('DataLoadController 187: ' . $dataloadMetadata['option'] . '---' . json_encode($option));
213
            $result = $this->DataSourceController->read((int)$dataloadMetadata['datasource'], $option);
214
            $result['datasetId'] = $dataloadMetadata['dataset'];
215
216
            if (isset($option['timestamp']) and $option['timestamp'] === 'true') {
217
                // if datasource should be timestamped/snapshoted
218
                // shift values by one dimension and stores date in second column
219
                $result['data'] = array_map(function ($tag) {
220
                    $columns = count($tag);
221
                    return array($tag[$columns - 2], $tag[$columns - 2], $tag[$columns - 1]);
222
                }, $result['data']);
223
                $result['data'] = $this->replaceDimension($result['data'], 1, date("Y-m-d H:i:s"));
224
            }
225
226
            return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type OCP\Files\NotFoundException which is incompatible with the documented return type OCP\AppFramework\Http\NotFoundResponse|array.
Loading history...
227
        } else {
228
            return new NotFoundResponse();
229
        }
230
    }
231
232
    /**
233
     * replace all values of one dimension
234
     *
235
     * @NoAdminRequired
236
     * @param $Array
237
     * @param $Find
238
     * @param $Replace
239
     * @return array
240
     */
241
    private function replaceDimension($Array, $Find, $Replace)
242
    {
243
        if (is_array($Array)) {
244
            foreach ($Array as $Key => $Val) {
245
                if (is_array($Array[$Key])) {
246
                    $Array[$Key] = $this->replaceDimension($Array[$Key], $Find, $Replace);
247
                } else {
248
                    if ($Key === $Find) {
249
                        $Array[$Key] = $Replace;
250
                    }
251
                }
252
            }
253
        }
254
        return $Array;
255
    }
256
257
    // Data Manipulation
258
    // Data Manipulation
259
    // Data Manipulation
260
261
    /**
262
     * update data from input form
263
     *
264
     * @NoAdminRequired
265
     * @param int $datasetId
266
     * @param $dimension1
267
     * @param $dimension2
268
     * @param $value
269
     * @return DataResponse|NotFoundResponse
270
     * @throws Exception
271
     */
272
    public function updateData(int $datasetId, $dimension1, $dimension2, $value)
273
    {
274
        $datasetMetadata = $this->DatasetController->getOwnDataset($datasetId);
275
        if (!empty($datasetMetadata)) {
276
            $insert = $update = $errorMessage = 0;
277
            $action = array();
278
            $value = $this->floatvalue($value);
279
            if ($value === false) {
280
                $errorMessage = $this->l10n->t('3rd field must be a valid number');
281
            } else {
282
                $action = $this->StorageController->update($datasetId, $dimension1, $dimension2, $value);
283
                $insert = $insert + $action['insert'];
284
                $update = $update + $action['update'];
285
            }
286
287
            $result = [
288
                'insert' => $insert,
289
                'update' => $update,
290
                'error' => $errorMessage,
291
                'validate' => $action['validate'],
292
            ];
293
294
            //$this->logger->error('DataLoadController 88:'.$errorMessage);
295
            if ($errorMessage === 0) $this->ActivityManager->triggerEvent($datasetId, ActivityManager::OBJECT_DATA, ActivityManager::SUBJECT_DATA_ADD);
296
            return new DataResponse($result);
297
        } else {
298
            return new NotFoundResponse();
299
        }
300
    }
301
302
    /**
303
     * delete data from input form
304
     *
305
     * @NoAdminRequired
306
     * @param int $datasetId
307
     * @param $dimension1
308
     * @param $dimension2
309
     * @return DataResponse|NotFoundResponse
310
     */
311
    public function deleteData(int $datasetId, $dimension1, $dimension2)
312
    {
313
        $datasetMetadata = $this->DatasetController->getOwnDataset($datasetId);
314
        if (!empty($datasetMetadata)) {
315
            $result = $this->StorageController->delete($datasetId, $dimension1, $dimension2);
316
            return new DataResponse(['delete' => $result]);
317
        } else {
318
            return new NotFoundResponse();
319
        }
320
    }
321
322
    /**
323
     * Simulate delete data from input form
324
     *
325
     * @NoAdminRequired
326
     * @param int $datasetId
327
     * @param $dimension1
328
     * @param $dimension2
329
     * @return DataResponse|NotFoundResponse
330
     */
331
    public function deleteDataSimulate(int $datasetId, $dimension1, $dimension2)
332
    {
333
        $datasetMetadata = $this->DatasetController->getOwnDataset($datasetId);
334
        if (!empty($datasetMetadata)) {
335
            $result = $this->StorageController->deleteSimulate($datasetId, $dimension1, $dimension2);
336
            return new DataResponse(['delete' => $result]);
337
        } else {
338
            return new NotFoundResponse();
339
        }
340
    }
341
342
    /**
343
     * Import clipboard data
344
     *
345
     * @NoAdminRequired
346
     * @param int $datasetId
347
     * @param $import
348
     * @return DataResponse|NotFoundResponse
349
     * @throws Exception
350
     */
351
    public function importClipboard($datasetId, $import)
352
    {
353
        $datasetMetadata = $this->DatasetController->getOwnDataset($datasetId);
354
        if (!empty($datasetMetadata)) {
355
            $insert = $update = $errorMessage = $errorCounter = 0;
356
            $delimiter = $this->detectDelimiter($import);
357
            $rows = str_getcsv($import, "\n");
358
359
            foreach ($rows as &$row) {
360
                $row = str_getcsv($row, $delimiter);
361
                $row[2] = $this->floatvalue($row[2]);
362
                if ($row[2] === false) {
363
                    $errorCounter++;
364
                } else {
365
                    $action = $this->StorageController->update($datasetId, $row[0], $row[1], $row[2]);
366
                    $insert = $insert + $action['insert'];
367
                    $update = $update + $action['update'];
368
                }
369
                if ($errorCounter === 2) {
370
                    // first error is ignored; might be due to header row
371
                    $errorMessage = $this->l10n->t('3rd field must be a valid number');
372
                    break;
373
                }
374
            }
375
376
            $result = [
377
                'insert' => $insert,
378
                'update' => $update,
379
                'delimiter' => $delimiter,
380
                'error' => $errorMessage,
381
            ];
382
383
            if ($errorMessage === 0) $this->ActivityManager->triggerEvent($datasetId, ActivityManager::OBJECT_DATA, ActivityManager::SUBJECT_DATA_ADD_IMPORT);
384
            return new DataResponse($result);
385
        } else {
386
            return new NotFoundResponse();
387
        }
388
    }
389
390
    /**
391
     * Import data into dataset from an internal or external file
392
     *
393
     * @NoAdminRequired
394
     * @param int $datasetId
395
     * @param $path
396
     * @return DataResponse|NotFoundResponse
397
     * @throws NotFoundException
398
     * @throws Exception
399
     */
400
    public function importFile(int $datasetId, $path)
401
    {
402
        //$this->logger->debug('DataLoadController 378:' . $datasetId . $path);
403
        $datasetMetadata = $this->DatasetController->getOwnDataset($datasetId);
404
        if (!empty($datasetMetadata)) {
405
            $insert = $update = 0;
406
            $option = array();
407
            $option['user_id'] = $datasetMetadata['user_id'];
408
            $option['path'] = $path;
409
            $option['link'] = $datasetMetadata['link'];
410
            $result = $this->DataSourceController->read(DataSourceController::DATASET_TYPE_INTERNAL_FILE, $option);
411
412
            if ($result['error'] === 0) {
413
                foreach ($result['data'] as &$row) {
414
                    $action = $this->StorageController->update($datasetId, $row[0], $row[1], $row[2]);
415
                    $insert = $insert + $action['insert'];
416
                    $update = $update + $action['update'];
417
                }
418
            }
419
420
            $result = [
421
                'insert' => $insert,
422
                'update' => $update,
423
                'error' => $result['error'],
424
            ];
425
426
            if ($result['error'] === 0) $this->ActivityManager->triggerEvent($datasetId, ActivityManager::OBJECT_DATA, ActivityManager::SUBJECT_DATA_ADD_IMPORT);
427
            return new DataResponse($result);
428
        } else {
429
            return new NotFoundResponse();
430
        }
431
    }
432
433
    private function detectDelimiter($data)
434
    {
435
        $delimiters = ["\t", ";", "|", ","];
436
        $data_2 = null;
437
        $delimiter = $delimiters[0];
438
        foreach ($delimiters as $d) {
439
            $firstRow = str_getcsv($data, "\n")[0];
440
            $data_1 = str_getcsv($firstRow, $d);
441
            if (sizeof($data_1) > sizeof($data_2)) {
442
                $delimiter = $d;
443
                $data_2 = $data_1;
444
            }
445
        }
446
        return $delimiter;
447
    }
448
449
    private function floatvalue($val)
450
    {
451
        $val = str_replace(",", ".", $val);
452
        $val = preg_replace('/\.(?=.*\.)/', '', $val);
453
        $val = preg_replace('/[^0-9-.]+/', '', $val);
454
        if (is_numeric($val)) {
455
            return number_format(floatval($val), 2, '.', '');
456
        } else {
457
            return false;
458
        }
459
    }
460
}