Passed
Push — master ( 4f8082...b2a043 )
by Marcel
02:16
created

DataloadService::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
c 0
b 0
f 0
nc 5
nop 5
dl 0
loc 27
rs 9.6
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 2021 Marcel Scherello
10
 */
11
12
namespace OCA\Analytics\Service;
13
14
use Exception;
15
use OCA\Analytics\Activity\ActivityManager;
16
use OCA\Analytics\Controller\DatasourceController;
17
use OCA\Analytics\Db\DataloadMapper;
18
use OCP\AppFramework\Http\NotFoundResponse;
19
use OCP\Files\NotFoundException;
20
use OCP\IL10N;
21
use Psr\Log\LoggerInterface;
22
23
class DataloadService
24
{
25
    private $logger;
26
    private $StorageService;
27
    private $DatasourceController;
28
    private $ActivityManager;
29
    private $ReportService;
30
    private $l10n;
31
    private $DataloadMapper;
32
33
    public function __construct(
34
        IL10N $l10n,
35
        LoggerInterface $logger,
36
        ActivityManager $ActivityManager,
37
        DatasourceController $DatasourceController,
38
        ReportService $ReportService,
39
        StorageService $StorageService,
40
        DataloadMapper $DataloadMapper
41
    )
42
    {
43
        $this->l10n = $l10n;
44
        $this->logger = $logger;
45
        $this->StorageService = $StorageService;
46
        $this->ActivityManager = $ActivityManager;
47
        $this->DatasourceController = $DatasourceController;
48
        $this->ReportService = $ReportService;
49
        $this->DataloadMapper = $DataloadMapper;
50
    }
51
52
    // Dataloads
53
    // Dataloads
54
    // Dataloads
55
56
    /**
57
     * create a new dataload
58
     *
59
     * @param $datasetId
60
     * @param $reportId
61
     * @param int $datasourceId
62
     * @return int
63
     * @throws \OCP\DB\Exception
64
     */
65
    public function create($datasetId, int $datasourceId)
66
    {
67
        return $this->DataloadMapper->create((int)$datasetId, $datasourceId);
68
    }
69
70
    /**
71
     * get all dataloads for a dataset or report
72
     *
73
     * @param int $datasetId
74
     * @param $reportId
75
     * @return array
76
     */
77
    public function read($datasetId)
78
    {
79
        return $this->DataloadMapper->read((int)$datasetId);
80
    }
81
82
    /**
83
     * update dataload
84
     *
85
     * @param int $dataloadId
86
     * @param $name
87
     * @param $option
88
     * @param $schedule
89
     * @return bool
90
     */
91
    public function update(int $dataloadId, $name, $option, $schedule)
92
    {
93
        return $this->DataloadMapper->update($dataloadId, $name, $option, $schedule);
94
    }
95
96
    /**
97
     * delete a dataload
98
     *
99
     * @param int $dataloadId
100
     * @return bool
101
     */
102
    public function delete(int $dataloadId)
103
    {
104
        return $this->DataloadMapper->delete($dataloadId);
105
    }
106
107
    /**
108
     * execute all dataloads depending on their schedule
109
     * Daily or Hourly
110
     *
111
     * @param $schedule
112
     * @return void
113
     * @throws Exception
114
     */
115
    public function executeBySchedule($schedule)
116
    {
117
        $schedules = $this->DataloadMapper->getDataloadBySchedule($schedule);
118
        foreach ($schedules as $dataload) {
119
            $this->execute($dataload['id']);
120
        }
121
    }
122
123
    /**
124
     * execute a dataload from datasource and store into dataset
125
     *
126
     * @param int $dataloadId
127
     * @return array
128
     * @throws Exception
129
     */
130
    public function execute(int $dataloadId)
131
    {
132
        $bulkSize = 500;
133
        $insert = $update = $error = 0;
134
        $bulkInsert = null;
135
136
        $dataloadMetadata = $this->DataloadMapper->getDataloadById($dataloadId);
137
        $option = json_decode($dataloadMetadata['option'], true);
138
        $result = $this->getDataFromDatasource($dataloadId);
139
        $datasetId = $result['datasetId'];
140
141
        if (isset($option['delete']) and $option['delete'] === 'true') {
142
            $bulkInsert = $this->StorageService->delete($datasetId, '*', '*', $dataloadMetadata['user_id']);
143
        }
144
145
        $this->DataloadMapper->beginTransaction();
146
147
        if ($result['error'] === 0) {
148
            $currentCount = 0;
149
            foreach ($result['data'] as $row) {
150
                if (count($row) === 2) {
151
                    // if datasource only delivers 2 colums, the value needs to be in the last one
152
                    $row[2] = $row[1];
153
                    $row[1] = null;
154
                }
155
                $action = $this->StorageService->update($datasetId, $row[0], $row[1], $row[2], $dataloadMetadata['user_id'], $bulkInsert);
156
                $insert = $insert + $action['insert'];
157
                $update = $update + $action['update'];
158
                $error = $error + $action['error'];
159
160
                if ($currentCount % $bulkSize === 0) {
161
                    $this->DataloadMapper->commit();
162
                    $this->DataloadMapper->beginTransaction();
163
                }
164
                if ($action['error'] === 0) $currentCount++;
165
            }
166
        }
167
        $this->DataloadMapper->commit();
168
169
        $result = [
170
            'insert' => $insert,
171
            'update' => $update,
172
            'error' => $error,
173
        ];
174
175
        if ($error === 0) $this->ActivityManager->triggerEvent($datasetId, ActivityManager::OBJECT_DATA, ActivityManager::SUBJECT_DATA_ADD_DATALOAD, $dataloadMetadata['user_id']);
176
        return $result;
177
    }
178
179
    /**
180
     * get the data from datasource
181
     * to be used in simulation or execution
182
     *
183
     * @param int $dataloadId
184
     * @return array|NotFoundResponse
185
     * @throws NotFoundResponse|NotFoundException
186
     */
187
    public function getDataFromDatasource(int $dataloadId)
188
    {
189
        $dataloadMetadata = $this->DataloadMapper->getDataloadById($dataloadId);
190
191
        if (!empty($dataloadMetadata)) {
192
            $dataloadMetadata['link'] = $dataloadMetadata['option']; //remap until datasource table is renamed link=>option
193
            $result = $this->DatasourceController->read((int)$dataloadMetadata['datasource'], $dataloadMetadata);
194
            $result['datasetId'] = $dataloadMetadata['dataset'];
195
            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...
196
        } else {
197
            return new NotFoundResponse();
198
        }
199
    }
200
201
    // Data Manipulation
202
    // Data Manipulation
203
    // Data Manipulation
204
205
    /**
206
     * update data from input form
207
     *
208
     * @NoAdminRequired
209
     * @param int $objectId
210
     * @param $dimension1
211
     * @param $dimension2
212
     * @param $value
213
     * @param bool $isDataset
214
     * @return array|false
215
     * @throws \OCP\DB\Exception
216
     */
217
    public function updateData(int $objectId, $dimension1, $dimension2, $value, bool $isDataset)
218
    {
219
        $dataset = $this->getDatasetId($objectId, $isDataset);
220
221
        if ($dataset != '') {
222
            $insert = $update = $errorMessage = 0;
223
            $action = array();
224
            $value = $this->floatvalue($value);
225
            if ($value === false) {
226
                $errorMessage = $this->l10n->t('3rd field must be a valid number');
227
            } else {
228
                $action = $this->StorageService->update($dataset, $dimension1, $dimension2, $value);
229
                $insert = $insert + $action['insert'];
230
                $update = $update + $action['update'];
231
            }
232
233
            $result = [
234
                'insert' => $insert,
235
                'update' => $update,
236
                'error' => $errorMessage,
237
                'validate' => $action['validate'],
238
            ];
239
240
            if ($errorMessage === 0) $this->ActivityManager->triggerEvent($dataset, ActivityManager::OBJECT_DATA, ActivityManager::SUBJECT_DATA_ADD);
241
            return $result;
242
        } else {
243
            return false;
244
        }
245
    }
246
247
    /**
248
     * Simulate delete data from input form
249
     *
250
     * @NoAdminRequired
251
     * @param int $objectId
252
     * @param $dimension1
253
     * @param $dimension2
254
     * @param bool $isDataset
255
     * @return array|false
256
     * @throws \OCP\DB\Exception
257
     */
258
    public function deleteDataSimulate(int $objectId, $dimension1, $dimension2, bool $isDataset)
259
    {
260
        $dataset = $this->getDatasetId($objectId, $isDataset);
261
        if ($dataset != '') {
262
            $result = $this->StorageService->deleteSimulate($dataset, $dimension1, $dimension2);
263
            return ['delete' => $result];
264
        } else {
265
            return false;
266
        }
267
    }
268
269
    /**
270
     * delete data from input form
271
     *
272
     * @NoAdminRequired
273
     * @param int $objectId
274
     * @param $dimension1
275
     * @param $dimension2
276
     * @param bool $isDataset
277
     * @return array|false
278
     * @throws \OCP\DB\Exception
279
     */
280
    public function deleteData(int $objectId, $dimension1, $dimension2, bool $isDataset)
281
    {
282
        $dataset = $this->getDatasetId($objectId, $isDataset);
283
        if ($dataset != '') {
284
            $result = $this->StorageService->delete($dataset, $dimension1, $dimension2);
285
            return ['delete' => $result];
286
        } else {
287
            return false;
288
        }
289
    }
290
291
    /**
292
     * Import clipboard data
293
     *
294
     * @NoAdminRequired
295
     * @param int $objectId
296
     * @param $import
297
     * @param bool $isDataset
298
     * @return array|false
299
     * @throws \OCP\DB\Exception
300
     */
301
    public function importClipboard($objectId, $import, bool $isDataset)
302
    {
303
        $dataset = $this->getDatasetId($objectId, $isDataset);
304
        if ($dataset != '') {
305
            $insert = $update = $errorMessage = $errorCounter = 0;
306
            $delimiter = '';
307
308
            if ($import === '') {
309
                $errorMessage = $this->l10n->t('No data');
310
            } else {
311
                $delimiter = $this->detectDelimiter($import);
312
                $rows = str_getcsv($import, "\n");
313
314
                foreach ($rows as &$row) {
315
                    $row = str_getcsv($row, $delimiter);
316
                    $numberOfColumns = count($row);
317
                    // last column needs to be a float
318
                    $row[2] = $this->floatvalue($row[$numberOfColumns - 1]);
319
                    if ($row[2] === false) {
320
                        $errorCounter++;
321
                    } else {
322
                        if ($numberOfColumns < 3) $row[1] = null;
323
                        $action = $this->StorageService->update($dataset, $row[0], $row[1], $row[2]);
324
                        $insert = $insert + $action['insert'];
325
                        $update = $update + $action['update'];
326
                    }
327
                    if ($errorCounter === 2) {
328
                        // first error is ignored; might be due to header row
329
                        $errorMessage = $this->l10n->t('Last field must be a valid number');
330
                        break;
331
                    }
332
                }
333
            }
334
335
            $result = [
336
                'insert' => $insert,
337
                'update' => $update,
338
                'delimiter' => $delimiter,
339
                'error' => $errorMessage,
340
            ];
341
342
            if ($errorMessage === 0) $this->ActivityManager->triggerEvent($dataset, ActivityManager::OBJECT_DATA, ActivityManager::SUBJECT_DATA_ADD_IMPORT);
343
            return $result;
344
        } else {
345
            return false;
346
        }
347
    }
348
349
    /**
350
     * Import data into dataset from an internal or external file
351
     *
352
     * @NoAdminRequired
353
     * @param int $objectId
354
     * @param $path
355
     * @param bool $isDataset
356
     * @return array|false
357
     * @throws \OCP\DB\Exception
358
     */
359
    public function importFile(int $objectId, $path, bool $isDataset)
360
    {
361
        $dataset = $this->getDatasetId($objectId, $isDataset);
362
        if ($dataset != '') {
363
            $insert = $update = 0;
364
            $reportMetadata['link'] = $path;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$reportMetadata was never initialized. Although not strictly required by PHP, it is generally a good practice to add $reportMetadata = array(); before regardless.
Loading history...
365
            $result = $this->DatasourceController->read(DatasourceController::DATASET_TYPE_FILE, $reportMetadata);
366
367
            if ($result['error'] === 0) {
368
                foreach ($result['data'] as &$row) {
369
                    $action = $this->StorageService->update($dataset, $row[0], $row[1], $row[2]);
370
                    $insert = $insert + $action['insert'];
371
                    $update = $update + $action['update'];
372
                }
373
            }
374
375
            $result = [
376
                'insert' => $insert,
377
                'update' => $update,
378
                'error' => $result['error'],
379
            ];
380
381
            if ($result['error'] === 0) $this->ActivityManager->triggerEvent($dataset, ActivityManager::OBJECT_DATA, ActivityManager::SUBJECT_DATA_ADD_IMPORT);
382
            return $result;
383
        } else {
384
            return false;
385
        }
386
    }
387
388
    private function getDatasetId($objectId, bool $isDataset)
389
    {
390
        if ($isDataset) {
391
            $dataset = $objectId;
392
        } else {
393
            $reportMetadata = $this->ReportService->read($objectId);
394
            $dataset = (int)$reportMetadata['dataset'];
395
        }
396
        return $dataset;
397
    }
398
399
    private function detectDelimiter($data): string
400
    {
401
        $delimiters = ["\t", ";", "|", ","];
402
        $data_2 = null;
403
        $delimiter = $delimiters[0];
404
        foreach ($delimiters as $d) {
405
            $firstRow = str_getcsv($data, "\n")[0];
406
            $data_1 = str_getcsv($firstRow, $d);
407
            if (sizeof($data_1) > sizeof($data_2)) {
408
                $delimiter = $d;
409
                $data_2 = $data_1;
410
            }
411
        }
412
        return $delimiter;
413
    }
414
415
    private function floatvalue($val)
416
    {
417
        $val = str_replace(",", ".", $val);
418
        $val = preg_replace('/\.(?=.*\.)/', '', $val);
419
        $val = preg_replace('/[^0-9-.]+/', '', $val);
420
        if (is_numeric($val)) {
421
            return number_format(floatval($val), 2, '.', '');
422
        } else {
423
            return false;
424
        }
425
    }
426
427
}