1 /*
2     SPDX-FileCopyrightText: 2020 Alexander Trufanov <trufanovan@gmail.com>
3     SPDX-License-Identifier: GPL-2.0-or-later
4 */
5 
6 #include "nodeoperations.h"
7 
8 #include <QThread>
9 
10 namespace kt
11 {
getChild(FNode * root,const QString & name,bool is_dir)12 FNode *NodeOperations::getChild(FNode *root, const QString &name, bool is_dir)
13 {
14     FNode *child = root->first_child;
15     while (child && (child->name != name || child->is_dir != is_dir)) {
16         child = child->next;
17     }
18     return child;
19 }
20 
addChild(FNode * root,const QString & name,bool is_dir)21 FNode *NodeOperations::addChild(FNode *root, const QString &name, bool is_dir)
22 {
23     FNode *n = new FNode();
24     n->parent = root;
25     n->name = name;
26     n->is_dir = is_dir;
27     if (!root->first_child) {
28         root->first_child = n;
29     } else {
30         FNode *last_child = root->first_child;
31         while (last_child->next)
32             last_child = last_child->next;
33         last_child->next = n;
34         n->prev = last_child;
35     }
36     return n;
37 }
38 
removeNode(FNode * n)39 void NodeOperations::removeNode(FNode *n)
40 {
41     while (n->first_child) {
42         removeNode(n->first_child);
43     }
44 
45     if (n->parent) {
46         if (n->parent->first_child == n) {
47             n->parent->first_child = n->next;
48         }
49     }
50 
51     if (n->prev) {
52         n->prev->next = n->next;
53     }
54 
55     if (n->next) {
56         n->next->prev = n->prev;
57     }
58 
59     free(n);
60 }
61 
makePath(FNode * root,const QString & fname,bool is_dir)62 FNode *NodeOperations::makePath(FNode *root, const QString &fname, bool is_dir)
63 {
64     int idx = fname.indexOf(QLatin1Char('/'));
65     FNode *existing;
66 
67     if (idx == -1) {
68         existing = getChild(root, fname, is_dir);
69         if (existing)
70             return existing;
71         return addChild(root, fname, is_dir);
72     } else {
73         existing = getChild(root, fname.left(idx), true);
74         if (!existing)
75             existing = addChild(root, fname.left(idx), true);
76         return makePath(existing, fname.right(fname.size() - 1 - idx), is_dir);
77     }
78 }
79 
findChild(FNode * root,const QString & fname,bool is_dir)80 FNode *NodeOperations::findChild(FNode *root, const QString &fname, bool is_dir)
81 {
82     int idx = fname.indexOf(QLatin1Char('/'));
83     if (idx == -1) {
84         return getChild(root, fname, is_dir);
85     } else {
86         FNode *n = getChild(root, fname.left(idx), true);
87         if (n)
88             n = findChild(n, fname.right(fname.size() - 1 - idx), is_dir);
89         return n;
90     }
91 }
92 
fillFromDir(FNode * root,const QDir & dir)93 void NodeOperations::fillFromDir(FNode *root, const QDir &dir)
94 {
95     if (QThread::currentThread()->isInterruptionRequested()) {
96         return;
97     }
98 
99     // QStringLists must be const to suppress "warning: c++11 range-loop might detach Qt container"
100     const QStringList sl_f = dir.entryList(QDir::NoDotAndDotDot | QDir::Files | QDir::Hidden);
101     for (const QString &s : sl_f) {
102         addChild(root, s, false);
103     }
104 
105     const QStringList sl_d = dir.entryList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Hidden);
106     QDir next_dir;
107     for (const QString &s : sl_d) {
108         FNode *d = addChild(root, s, true);
109         next_dir.setPath(dir.path() + QLatin1String("/") + s);
110         fillFromDir(d, next_dir);
111     }
112 }
113 
subtractTreesOnFiles(FNode * tree1,FNode * tree2)114 void NodeOperations::subtractTreesOnFiles(FNode *tree1, FNode *tree2)
115 {
116     if (QThread::currentThread()->isInterruptionRequested()) {
117         return;
118     }
119 
120     FNode *c = tree2->first_child;
121     while (c) {
122         FNode *f = getChild(tree1, c->name, c->is_dir);
123         if (f) {
124             if (c->is_dir)
125                 subtractTreesOnFiles(f, c);
126             else
127                 removeNode(f);
128         }
129         c = c->next;
130     }
131 }
132 
pruneEmptyFolders(FNode * start_folder)133 void NodeOperations::pruneEmptyFolders(FNode *start_folder)
134 {
135     FNode *c = start_folder->first_child;
136     while (c) {
137         if (c->is_dir)
138             pruneEmptyFolders(c);
139         c = c->next;
140     }
141 
142     if (!start_folder->first_child) {
143         removeNode(start_folder);
144     }
145 }
146 
pruneEmptyFolders(FNode * tree1,FNode * tree2)147 void NodeOperations::pruneEmptyFolders(FNode *tree1, FNode *tree2)
148 {
149     if (QThread::currentThread()->isInterruptionRequested()) {
150         return;
151     }
152 
153     FNode *c = tree2->first_child;
154     while (c) {
155         if (c->is_dir) {
156             FNode *f = getChild(tree1, c->name, c->is_dir);
157             if (f) {
158                 pruneEmptyFolders(f, c);
159             }
160         }
161         c = c->next;
162     }
163 
164     if (!tree2->first_child) {
165         pruneEmptyFolders(tree1);
166     }
167 }
168 
printTree(FNode * root,const QString & path,QSet<QString> & set)169 void NodeOperations::printTree(FNode *root, const QString &path, QSet<QString> &set)
170 {
171     if (QThread::currentThread()->isInterruptionRequested()) {
172         return;
173     }
174 
175     QString new_path;
176     if (!root->name.isEmpty()) {
177         new_path = path + QLatin1String("/") + root->name;
178         set += new_path;
179     }
180 
181     FNode *c = root->first_child;
182 
183     while (c) {
184         if (c->is_dir) {
185             printTree(c, new_path, set);
186         } else {
187             set += new_path + QLatin1String("/") + c->name;
188         }
189         c = c->next;
190     }
191 }
192 
printTree(FNode * root,QSet<QString> & set)193 void NodeOperations::printTree(FNode *root, QSet<QString> &set)
194 {
195     QString path;
196     printTree(root, path, set);
197 }
198 
199 }
200