Completed
Pull Request — master (#92)
by
unknown
31:15
created

NotesService   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 295
Duplicated Lines 5.42 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 40
c 5
b 0
f 0
lcom 1
cbo 2
dl 16
loc 295
rs 8.2608

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A get() 0 4 1
A create() 0 12 1
B update() 0 40 4
A delete() 0 5 1
B getAll() 5 21 5
A getTags() 5 9 3
A favorite() 0 15 4
A getFolderForUser() 0 9 2
A generateFileName() 0 20 4
A gatherNoteFiles() 0 14 4
A isNote() 0 11 3
C getFileById() 6 23 7

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like NotesService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use NotesService, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Nextcloud - Notes
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Bernhard Posselt <[email protected]>
9
 * @copyright Bernhard Posselt 2012, 2014
10
 */
11
12
namespace OCA\Notes\Service;
13
14
use OCP\Files\FileInfo;
15
use OCP\IL10N;
16
use OCP\Files\IRootFolder;
17
use OCP\Files\Folder;
18
19
use OCA\Notes\Db\Note;
20
21
/**
22
 * Class NotesService
23
 *
24
 * @package OCA\Notes\Service
25
 */
26
class NotesService {
27
28
    private $l10n;
29
    private $root;
30
31
    /**
32
     * @param IRootFolder $root
33
     * @param IL10N $l10n
34
     */
35
    public function __construct (IRootFolder $root, IL10N $l10n) {
36
        $this->root = $root;
37
        $this->l10n = $l10n;
38
    }
39
40
41
    /**
42
     * @param string $userId
43
     * @return array with all notes in the current directory
44
     */
45
    public function getAll ($userId){
46
        $notesFolder = $this->getFolderForUser($userId);
47
        $notes = $this->gatherNoteFiles($notesFolder);
48
        $filesById = [];
49
        foreach($notes as $note) {
50
            $filesById[$note->getId()] = $note;
51
        }
52
        $tagger = \OC::$server->getTagManager()->load('files');
53 View Code Duplication
        if($tagger===null) {
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...
54
            $tags = [];
55
        } else {
56
            $tags = $tagger->getTagsForObjects(array_keys($filesById));
57
        }
58
59
        $notes = [];
60
        foreach($filesById as $id=>$file) {
61
            $notes[] = Note::fromFile($file, $notesFolder, array_key_exists($id, $tags) ? $tags[$id] : []);
62
        }
63
64
        return $notes;
65
    }
66
67
68
    /**
69
     * Used to get a single note by id
70
     * @param int $id the id of the note to get
71
     * @param string $userId
72
     * @throws NoteDoesNotExistException if note does not exist
73
     * @return Note
74
     */
75
    public function get ($id, $userId) {
76
        $folder = $this->getFolderForUser($userId);
77
        return Note::fromFile($this->getFileById($folder, $id), $folder, $this->getTags($id));
78
    }
79
80
    private function getTags ($id) {
81
        $tagger = \OC::$server->getTagManager()->load('files');
82 View Code Duplication
        if($tagger===null) {
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...
83
            $tags = [];
84
        } else {
85
            $tags = $tagger->getTagsForObjects([$id]);
86
        }
87
        return array_key_exists($id, $tags) ? $tags[$id] : [];
88
    }
89
90
    /**
91
     * Creates a note and returns the empty note
92
     * @param string $userId
93
     * @see update for setting note content
94
     * @return Note the newly created note
95
     */
96
    public function create ($userId) {
97
        $title = $this->l10n->t('New note');
98
        $folder = $this->getFolderForUser($userId);
99
100
        // check new note exists already and we need to number it
101
        // pass -1 because no file has id -1 and that will ensure
102
        // to only return filenames that dont yet exist
103
        $path = $this->generateFileName($folder, $title, "txt", -1);
104
        $file = $folder->newFile($path);
105
106
        return Note::fromFile($file, $folder);
107
    }
108
109
110
    /**
111
     * Updates a note. Be sure to check the returned note since the title is
112
     * dynamically generated and filename conflicts are resolved
113
     * @param int $id the id of the note used to update
114
     * @param string $content the content which will be written into the note
115
     * the title is generated from the first line of the content
116
     * @param int $mtime time of the note modification (optional)
117
     * @throws NoteDoesNotExistException if note does not exist
118
     * @return \OCA\Notes\Db\Note the updated note
119
     */
120
    public function update ($id, $content, $userId, $mtime=0) {
121
        $notesFolder = $this->getFolderForUser($userId);
122
        $file = $this->getFileById($notesFolder, $id);
123
        $folder = $file->getParent();
124
125
        // generate content from the first line of the title
126
        $splitContent = preg_split("/\R/", $content, 2);
127
        $title = $splitContent[0];
128
129
        if(!$title) {
130
            $title = $this->l10n->t('New note');
131
        }
132
133
        // prevent directory traversal
134
        $title = str_replace(array('/', '\\'), '',  $title);
135
        // remove hash and space characters from the beginning of the filename
136
        // in case of markdown
137
        $title = ltrim($title, ' #');
138
        // using a maximum of 100 chars should be enough
139
        $title = mb_substr($title, 0, 100, "UTF-8");
140
141
        // generate filename if there were collisions
142
        $currentFilePath = $file->getPath();
143
        $basePath = pathinfo($file->getPath(), PATHINFO_DIRNAME);
144
        $fileExtension = pathinfo($file->getName(), PATHINFO_EXTENSION);
145
        $newFilePath = $basePath . '/' . $this->generateFileName($folder, $title, $fileExtension, $id);
146
147
        // if the current path is not the new path, the file has to be renamed
148
        if($currentFilePath !== $newFilePath) {
149
            $file->move($newFilePath);
150
        }
151
152
        $file->putContent($content);
153
154
        if($mtime) {
155
            $file->touch($mtime);
156
        }
157
158
        return Note::fromFile($file, $notesFolder, $this->getTags($id));
159
    }
160
161
162
    /**
163
     * Set or unset a note as favorite.
164
     * @param int $id the id of the note used to update
165
     * @param boolean $favorite whether the note should be a favorite or not
166
     * @throws NoteDoesNotExistException if note does not exist
167
     * @return boolean the new favorite state of the note
168
     */
169
    public function favorite ($id, $favorite, $userId){
170
        $folder = $this->getFolderForUser($userId);
171
        $file = $this->getFileById($folder, $id);
172
        if(!$this->isNote($file)) {
173
            throw new NoteDoesNotExistException();
174
        }
175
        $tagger = \OC::$server->getTagManager()->load('files');
176
        if($favorite)
177
            $tagger->addToFavorites($id);
178
        else
179
            $tagger->removeFromFavorites($id);
180
181
        $tags = $tagger->getTagsForObjects([$id]);
182
        return array_key_exists($id, $tags) && in_array(\OC\Tags::TAG_FAVORITE, $tags[$id]);
183
    }
184
185
186
    /**
187
     * Deletes a note
188
     * @param int $id the id of the note which should be deleted
189
     * @param string $userId
190
     * @throws NoteDoesNotExistException if note does not
191
     * exist
192
     */
193
    public function delete ($id, $userId) {
194
        $folder = $this->getFolderForUser($userId);
195
        $file = $this->getFileById($folder, $id);
196
        $file->delete();
197
    }
198
199
200
    /**
201
     * @param Folder $folder
202
     * @param int $id
203
     * @throws NoteDoesNotExistException
204
     * @return \OCP\Files\File
205
     */
206
    private function getFileById ($folder, $id) {
207
        $file = $folder->getById($id);
208
209
        echo PHP_EOL;
210
        echo 'This is NotesService->getFileById() for folder '.$folder->getInternalPath().' with requested File ID '.$id.PHP_EOL;
211
        $files = $folder->getDirectoryListing();
212
        if(is_array($files)) {
213
            echo count($files).' result(s) from Folder->getDirectoryListing():'.PHP_EOL;
214 View Code Duplication
            foreach($files as $f) {
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...
215
                echo $f->getPath().' (ID '.$f->getId().')'.PHP_EOL;
216
            }
217
        }
218
        echo count($file).' result(s) from Folder->getById('.$id.'):'.PHP_EOL;
219 View Code Duplication
        foreach($file as $f) {
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...
220
             echo $f->getPath().' (ID '.$f->getId().')'.PHP_EOL;
221
        }
222
223
        if(count($file) <= 0 || !$this->isNote($file[0])) {
224
            echo '=> NoteDoesNotExistException'.PHP_EOL;
225
            throw new NoteDoesNotExistException('ID='.$id . (count($file) ? $file[0]->getPath().' is no note' : ' not found'));
226
        } else echo '=> OK'.PHP_EOL;
227
        return $file[0];
228
    }
229
230
231
    /**
232
     * @param string $userId the user id
233
     * @return Folder
234
     */
235
    private function getFolderForUser ($userId) {
236
        $path = '/' . $userId . '/files/Notes';
237
        if ($this->root->nodeExists($path)) {
238
            $folder = $this->root->get($path);
239
        } else {
240
            $folder = $this->root->newFolder($path);
241
        }
242
        return $folder;
243
    }
244
245
246
    /**
247
     * get path of file and the title.txt and check if they are the same
248
     * file. If not the title needs to be renamed
249
     *
250
     * @param Folder $folder a folder to the notes directory
251
     * @param string $title the filename which should be used
252
     * @param string $extension the extension which should be used
253
     * @param int $id the id of the note for which the title should be generated
254
     * used to see if the file itself has the title and not a different file for
255
     * checking for filename collisions
256
     * @return string the resolved filename to prevent overwriting different
257
     * files with the same title
258
     */
259
    private function generateFileName (Folder $folder, $title, $extension, $id) {
260
        $path = $title . '.' . $extension;
261
262
        // if file does not exist, that name has not been taken. Similar we don't
263
        // need to handle file collisions if it is the filename did not change
264
        if (!$folder->nodeExists($path) || $folder->get($path)->getId() === $id) {
265
            return $path;
266
        } else {
267
            // increments name (2) to name (3)
268
            $match = preg_match('/\((?P<id>\d+)\)$/', $title, $matches);
269
            if($match) {
270
                $newId = ((int) $matches['id']) + 1;
271
                $newTitle = preg_replace('/(.*)\s\((\d+)\)$/',
272
                    '$1 (' . $newId . ')', $title);
273
            } else {
274
                $newTitle = $title . ' (2)';
275
            }
276
            return $this->generateFileName($folder, $newTitle, $extension, $id);
277
        }
278
    }
279
280
281
	/**
282
	 * gather note files in given directory and all subdirectories
283
	 * @param Folder $folder
284
	 * @return array
285
	 */
286
	private function gatherNoteFiles ($folder) {
287
		$notes = [];
288
		$nodes = $folder->getDirectoryListing();
289
		foreach($nodes as $node) {
290
			if($node->getType() === FileInfo::TYPE_FOLDER) {
291
				$notes = array_merge($notes, $this->gatherNoteFiles($node));
292
				continue;
293
			}
294
			if($this->isNote($node)) {
295
				$notes[] = $node;
296
			}
297
		}
298
		return $notes;
299
	}
300
301
302
    /**
303
     * test if file is a note
304
     *
305
     * @param \OCP\Files\File $file
306
     * @return bool
307
     */
308
    private function isNote($file) {
309
        $allowedExtensions = ['txt', 'org', 'markdown', 'md', 'note'];
310
311
        if($file->getType() !== 'file') return false;
312
        if(!in_array(
313
            pathinfo($file->getName(), PATHINFO_EXTENSION),
314
            $allowedExtensions
315
        )) return false;
316
317
        return true;
318
    }
319
320
}
321