Passed
Push — master ( 1d1c0d...40ae07 )
by Marcel
02:46
created

DataloadController::updateData()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 27
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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