Passed
Push — master ( 370f15...d1697d )
by Marcel
02:54
created

DataloadService::deleteData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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