znote 1.0.1__tar.gz → 1.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {znote-1.0.1/znote.egg-info → znote-1.2.3}/PKG-INFO +44 -1
- {znote-1.0.1 → znote-1.2.3}/README.md +43 -0
- {znote-1.0.1 → znote-1.2.3}/znote/app/display.py +72 -1
- {znote-1.0.1 → znote-1.2.3}/znote/client.py +210 -2
- znote-1.2.3/znote/core/client/fetch.py +83 -0
- znote-1.2.3/znote/core/client/metafile.py +12 -0
- {znote-1.0.1 → znote-1.2.3}/znote/core/client/settings.py +14 -6
- {znote-1.0.1 → znote-1.2.3}/znote/core/server/settings.py +13 -8
- {znote-1.0.1 → znote-1.2.3}/znote/model/models.py +17 -1
- {znote-1.0.1 → znote-1.2.3}/znote/server.py +164 -1
- {znote-1.0.1 → znote-1.2.3/znote.egg-info}/PKG-INFO +44 -1
- {znote-1.0.1 → znote-1.2.3}/znote.egg-info/SOURCES.txt +2 -0
- {znote-1.0.1 → znote-1.2.3}/LICENSE +0 -0
- {znote-1.0.1 → znote-1.2.3}/setup.cfg +0 -0
- {znote-1.0.1 → znote-1.2.3}/znote/__init__.py +0 -0
- {znote-1.0.1 → znote-1.2.3}/znote/app/input.py +0 -0
- {znote-1.0.1 → znote-1.2.3}/znote/core/auth.py +0 -0
- {znote-1.0.1 → znote-1.2.3}/znote/core/client/connect.py +0 -0
- {znote-1.0.1 → znote-1.2.3}/znote/core/client/token.py +0 -0
- {znote-1.0.1 → znote-1.2.3}/znote/core/config.py +0 -0
- {znote-1.0.1 → znote-1.2.3}/znote/core/editor.py +0 -0
- {znote-1.0.1 → znote-1.2.3}/znote/core/gen.py +0 -0
- {znote-1.0.1 → znote-1.2.3}/znote/core/pack.py +0 -0
- {znote-1.0.1 → znote-1.2.3}/znote/core/server/note.py +0 -0
- {znote-1.0.1 → znote-1.2.3}/znote/core/workspace.py +0 -0
- {znote-1.0.1 → znote-1.2.3}/znote/main.py +0 -0
- {znote-1.0.1 → znote-1.2.3}/znote.egg-info/dependency_links.txt +0 -0
- {znote-1.0.1 → znote-1.2.3}/znote.egg-info/entry_points.txt +0 -0
- {znote-1.0.1 → znote-1.2.3}/znote.egg-info/requires.txt +0 -0
- {znote-1.0.1 → znote-1.2.3}/znote.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: znote
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.3
|
|
4
4
|
Summary: Vault de prise de note
|
|
5
5
|
Author:
|
|
6
6
|
License: mit
|
|
@@ -40,6 +40,7 @@ znote est une application de prise de notes en ligne de commande fonctionnant su
|
|
|
40
40
|
- **Éditeur Intégré** : Un éditeur de texte simple est intégré directement dans le terminal pour une expérience fluide.
|
|
41
41
|
- **Workspaces (Espaces de travail)** : Organisez vos notes dans des espaces de travail distincts.
|
|
42
42
|
- **Synchronisation de Fichiers** : Envoyez (`push`) un fichier local pour créer une note, ou téléchargez (`pull`) une note dans un fichier.
|
|
43
|
+
- **Channels de Diffusion** : Partagez et synchronisez des fichiers sur plusieurs postes via des "channels".
|
|
43
44
|
- **Protection par Mot de Passe** : Protégez l'accès à des notes spécifiques avec un mot de passe.
|
|
44
45
|
- **Recherche** : Retrouvez du contenu rapidement grâce à une fonction de recherche dans toutes vos notes.
|
|
45
46
|
- **Stockage en base** : Pour l'instant les notes sont stockées en clair dans la base. A voir pour rajouter le support du chiffrement côté serveur.
|
|
@@ -92,6 +93,18 @@ Le serveur tournera en arrière-plan, en attente de connexions.
|
|
|
92
93
|
| `--username <nom>` | Spécifie le nom d'utilisateur pour la création. | `... --username admin` |
|
|
93
94
|
| `--password <mdp>` | Spécifie le mot de passe pour la création. | `... --password "un_mot_de_passe_solide"` |
|
|
94
95
|
|
|
96
|
+
### Détail du fichier de config
|
|
97
|
+
|
|
98
|
+
```ini
|
|
99
|
+
server:
|
|
100
|
+
verbose: true #Afficher les logs
|
|
101
|
+
host: 127.0.0.1 #Ip d'écoute
|
|
102
|
+
port: 40017 #Port d'écoute
|
|
103
|
+
key_size: 2048 #Taille de la clé RSA
|
|
104
|
+
key_otp: JFSWY3DPEHPK3PXP #Clé OTP pour le démarrage
|
|
105
|
+
data_dir: #Chemin vers la base SQLite
|
|
106
|
+
working_dir: #Emplacement des fichiers de l'application
|
|
107
|
+
```
|
|
95
108
|
|
|
96
109
|
## Côté client
|
|
97
110
|
|
|
@@ -141,3 +154,33 @@ La syntaxe générale est `znote <commande> [arguments...]`.
|
|
|
141
154
|
| `workspace <nom_ws>` | Change le workspace actif. | `znote workspace "Projets"` |
|
|
142
155
|
| `workspace <nom_ws> --create` | Crée un nouveau workspace. | `znote workspace "Archives" --create` |
|
|
143
156
|
| `workspace <nom_ws> --remove` | Supprime un workspace. | `znote workspace "Archives" --remove` |
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
### Commandes des Channels
|
|
160
|
+
|
|
161
|
+
La fonctionnalité de "channel" permet de synchroniser des fichiers entre plusieurs postes.
|
|
162
|
+
|
|
163
|
+
| Commande | Description | Exemple d’utilisation |
|
|
164
|
+
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
165
|
+
| `channel` | Liste tous les channels disponibles sur le serveur. | `znote channel` |
|
|
166
|
+
| `channel <nom> --detail` | Affiche les informations détaillées d'un channel (hash, date de modification...). | `znote channel mon-canal --detail` |
|
|
167
|
+
| `channel <nom> --tree` | Affiche l'arborescence et la liste des fichiers contenus dans le channel. | `znote channel mon-canal --tree` |
|
|
168
|
+
| `publish <channel> <fichier>` | Publie un fichier sur un channel. Si le fichier existe déjà , il est mis à jour. | `znote publish mon-canal ./rapport.pdf` |
|
|
169
|
+
| `publish <channel> --purge` | Supprime les fichiers du channel qui ne sont pas présents dans la publication actuelle. | `znote publish mon-canal --purge` |
|
|
170
|
+
| `fetch <channel> (<path>)` | Récupère (télécharge) tous les fichiers d'un channel dans le répertoire local. | `znote fetch mon-canal` |
|
|
171
|
+
| `fetch <channel> --force` | Force le téléchargement et l'écrasement des fichiers locaux même s'ils existent déjà . | `znote fetch mon-canal --force` |
|
|
172
|
+
| `fetch <channel> --purge` | Supprime les fichiers locaux qui ne sont pas présent dans le channel. | `znote fetch mon-canal --purge` |
|
|
173
|
+
| `diff <channel>` | Compare le répertoire local avec le contenu du channel et affiche les différences (fichiers nouveaux, modifiés ou à supprimer). | `znote diff mon-canal` |
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
### Détail du fichier de config
|
|
177
|
+
|
|
178
|
+
```ini
|
|
179
|
+
client:
|
|
180
|
+
verbose: true #Afficher les logs
|
|
181
|
+
host: 127.0.0.1 #Ip du serveur
|
|
182
|
+
port: 40017 #Port du serveur
|
|
183
|
+
key_size: 2048 #Taille de la clé RSA
|
|
184
|
+
key_otp: JFSWY3DPEHPK3PXP #Clé OTP pour le démarrage
|
|
185
|
+
working_dir: #Emplacement des fichiers de l'application
|
|
186
|
+
```
|
|
@@ -11,6 +11,7 @@ znote est une application de prise de notes en ligne de commande fonctionnant su
|
|
|
11
11
|
- **Éditeur Intégré** : Un éditeur de texte simple est intégré directement dans le terminal pour une expérience fluide.
|
|
12
12
|
- **Workspaces (Espaces de travail)** : Organisez vos notes dans des espaces de travail distincts.
|
|
13
13
|
- **Synchronisation de Fichiers** : Envoyez (`push`) un fichier local pour créer une note, ou téléchargez (`pull`) une note dans un fichier.
|
|
14
|
+
- **Channels de Diffusion** : Partagez et synchronisez des fichiers sur plusieurs postes via des "channels".
|
|
14
15
|
- **Protection par Mot de Passe** : Protégez l'accès à des notes spécifiques avec un mot de passe.
|
|
15
16
|
- **Recherche** : Retrouvez du contenu rapidement grâce à une fonction de recherche dans toutes vos notes.
|
|
16
17
|
- **Stockage en base** : Pour l'instant les notes sont stockées en clair dans la base. A voir pour rajouter le support du chiffrement côté serveur.
|
|
@@ -63,6 +64,18 @@ Le serveur tournera en arrière-plan, en attente de connexions.
|
|
|
63
64
|
| `--username <nom>` | Spécifie le nom d'utilisateur pour la création. | `... --username admin` |
|
|
64
65
|
| `--password <mdp>` | Spécifie le mot de passe pour la création. | `... --password "un_mot_de_passe_solide"` |
|
|
65
66
|
|
|
67
|
+
### Détail du fichier de config
|
|
68
|
+
|
|
69
|
+
```ini
|
|
70
|
+
server:
|
|
71
|
+
verbose: true #Afficher les logs
|
|
72
|
+
host: 127.0.0.1 #Ip d'écoute
|
|
73
|
+
port: 40017 #Port d'écoute
|
|
74
|
+
key_size: 2048 #Taille de la clé RSA
|
|
75
|
+
key_otp: JFSWY3DPEHPK3PXP #Clé OTP pour le démarrage
|
|
76
|
+
data_dir: #Chemin vers la base SQLite
|
|
77
|
+
working_dir: #Emplacement des fichiers de l'application
|
|
78
|
+
```
|
|
66
79
|
|
|
67
80
|
## Côté client
|
|
68
81
|
|
|
@@ -112,3 +125,33 @@ La syntaxe générale est `znote <commande> [arguments...]`.
|
|
|
112
125
|
| `workspace <nom_ws>` | Change le workspace actif. | `znote workspace "Projets"` |
|
|
113
126
|
| `workspace <nom_ws> --create` | Crée un nouveau workspace. | `znote workspace "Archives" --create` |
|
|
114
127
|
| `workspace <nom_ws> --remove` | Supprime un workspace. | `znote workspace "Archives" --remove` |
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
### Commandes des Channels
|
|
131
|
+
|
|
132
|
+
La fonctionnalité de "channel" permet de synchroniser des fichiers entre plusieurs postes.
|
|
133
|
+
|
|
134
|
+
| Commande | Description | Exemple d’utilisation |
|
|
135
|
+
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
136
|
+
| `channel` | Liste tous les channels disponibles sur le serveur. | `znote channel` |
|
|
137
|
+
| `channel <nom> --detail` | Affiche les informations détaillées d'un channel (hash, date de modification...). | `znote channel mon-canal --detail` |
|
|
138
|
+
| `channel <nom> --tree` | Affiche l'arborescence et la liste des fichiers contenus dans le channel. | `znote channel mon-canal --tree` |
|
|
139
|
+
| `publish <channel> <fichier>` | Publie un fichier sur un channel. Si le fichier existe déjà, il est mis à jour. | `znote publish mon-canal ./rapport.pdf` |
|
|
140
|
+
| `publish <channel> --purge` | Supprime les fichiers du channel qui ne sont pas présents dans la publication actuelle. | `znote publish mon-canal --purge` |
|
|
141
|
+
| `fetch <channel> (<path>)` | Récupère (télécharge) tous les fichiers d'un channel dans le répertoire local. | `znote fetch mon-canal` |
|
|
142
|
+
| `fetch <channel> --force` | Force le téléchargement et l'écrasement des fichiers locaux même s'ils existent déjà. | `znote fetch mon-canal --force` |
|
|
143
|
+
| `fetch <channel> --purge` | Supprime les fichiers locaux qui ne sont pas présent dans le channel. | `znote fetch mon-canal --purge` |
|
|
144
|
+
| `diff <channel>` | Compare le répertoire local avec le contenu du channel et affiche les différences (fichiers nouveaux, modifiés ou à supprimer). | `znote diff mon-canal` |
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
### Détail du fichier de config
|
|
148
|
+
|
|
149
|
+
```ini
|
|
150
|
+
client:
|
|
151
|
+
verbose: true #Afficher les logs
|
|
152
|
+
host: 127.0.0.1 #Ip du serveur
|
|
153
|
+
port: 40017 #Port du serveur
|
|
154
|
+
key_size: 2048 #Taille de la clé RSA
|
|
155
|
+
key_otp: JFSWY3DPEHPK3PXP #Clé OTP pour le démarrage
|
|
156
|
+
working_dir: #Emplacement des fichiers de l'application
|
|
157
|
+
```
|
|
@@ -90,4 +90,75 @@ def RestoreScreen():
|
|
|
90
90
|
|
|
91
91
|
def CleanScreen():
|
|
92
92
|
sys.stdout.write("\033[2J") # Effacer l'écran
|
|
93
|
-
sys.stdout.flush()
|
|
93
|
+
sys.stdout.flush()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
#Affichage arborescence en mode tree
|
|
97
|
+
@impmagic.loader(
|
|
98
|
+
{'module': 'collections', 'submodule': ['defaultdict']},
|
|
99
|
+
)
|
|
100
|
+
def build_tree(paths):
|
|
101
|
+
tree = lambda: defaultdict(tree)
|
|
102
|
+
root = tree()
|
|
103
|
+
|
|
104
|
+
for path in paths:
|
|
105
|
+
parts = path.strip(".\\/").replace("/", "\\").split("\\")
|
|
106
|
+
current = root
|
|
107
|
+
for i, part in enumerate(parts):
|
|
108
|
+
if i == len(parts) - 1:
|
|
109
|
+
current[part] = "FILE"
|
|
110
|
+
else:
|
|
111
|
+
current = current[part]
|
|
112
|
+
return root
|
|
113
|
+
|
|
114
|
+
# Tree builder for dict with statuses
|
|
115
|
+
@impmagic.loader(
|
|
116
|
+
{'module': 'collections', 'submodule': ['defaultdict']},
|
|
117
|
+
)
|
|
118
|
+
def build_tree_from_dict(status_map):
|
|
119
|
+
tree = lambda: defaultdict(tree)
|
|
120
|
+
root = tree()
|
|
121
|
+
status_lookup = {}
|
|
122
|
+
|
|
123
|
+
for path, status in status_map.items():
|
|
124
|
+
parts = path.strip(".\\/").replace("/", "\\").split("\\")
|
|
125
|
+
current = root
|
|
126
|
+
for i, part in enumerate(parts):
|
|
127
|
+
if i == len(parts) - 1:
|
|
128
|
+
current[part] = status # store status as a leaf value
|
|
129
|
+
else:
|
|
130
|
+
current = current[part]
|
|
131
|
+
return root
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
STATUS_COLOR = {
|
|
135
|
+
"OK": "cyan", # green
|
|
136
|
+
"UPDATE": "light_yellow", # yellow
|
|
137
|
+
"DELETE": "gold_3b", # red
|
|
138
|
+
"NEW": "magenta_3a", # cyan
|
|
139
|
+
"FOLDER": "grey_53", # blue for directories
|
|
140
|
+
"RESET": "\033[0m",
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# Unified print function
|
|
144
|
+
@impmagic.loader(
|
|
145
|
+
{'module': 'zpp_color', 'submodule': ['fg', 'attr']},
|
|
146
|
+
)
|
|
147
|
+
def print_tree(d, prefix=""):
|
|
148
|
+
keys = sorted(d.keys())
|
|
149
|
+
for i, key in enumerate(keys):
|
|
150
|
+
is_last = i == len(keys) - 1
|
|
151
|
+
connector = "└── " if is_last else "├── "
|
|
152
|
+
value = d[key]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
if isinstance(value, dict):
|
|
156
|
+
# Directory
|
|
157
|
+
color = STATUS_COLOR["FOLDER"]
|
|
158
|
+
print(f"{prefix}{connector}{fg(color)}{key}{attr(0)}")
|
|
159
|
+
new_prefix = prefix + (" " if is_last else "│ ")
|
|
160
|
+
print_tree(value, new_prefix)
|
|
161
|
+
else:
|
|
162
|
+
# File with status
|
|
163
|
+
color = STATUS_COLOR.get(value, "cyan")
|
|
164
|
+
print(f"{prefix}{connector}{fg(color)}{key}{attr(0)}")
|
|
@@ -181,6 +181,33 @@ class Client():
|
|
|
181
181
|
return ""
|
|
182
182
|
|
|
183
183
|
|
|
184
|
+
@impmagic.loader(
|
|
185
|
+
{'module': '__main__'},
|
|
186
|
+
{'module': 'hashlib'},
|
|
187
|
+
{'module': 'app.display', 'submodule': ['logs', 'notify']},
|
|
188
|
+
)
|
|
189
|
+
def publish_file(self, file, expanded_path, channel_name):
|
|
190
|
+
logs(f"Envoi de {os.path.relpath(file, expanded_path)}")
|
|
191
|
+
with open(file, 'rb') as f:
|
|
192
|
+
content = f.read()
|
|
193
|
+
content_hash = hashlib.sha256(content).hexdigest()
|
|
194
|
+
|
|
195
|
+
command = {
|
|
196
|
+
'method': 'channel',
|
|
197
|
+
'action': 'publish',
|
|
198
|
+
'channel_name': channel_name,
|
|
199
|
+
'name': os.path.relpath(file, expanded_path),
|
|
200
|
+
'content': content,
|
|
201
|
+
'hash': content_hash,
|
|
202
|
+
'token': __main__.settings.token
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
message = self.call_server(command)
|
|
206
|
+
if message['method']=="notify":
|
|
207
|
+
if 'message' in message and 'status_code' in message:
|
|
208
|
+
notify(message['message'], message['status_code'])
|
|
209
|
+
|
|
210
|
+
|
|
184
211
|
#Envoie une commande au serveur et attends une réponse
|
|
185
212
|
@impmagic.loader(
|
|
186
213
|
{'module': '__main__'},
|
|
@@ -225,6 +252,7 @@ class Client():
|
|
|
225
252
|
{'module': 'json'},
|
|
226
253
|
{'module': 'os'},
|
|
227
254
|
{'module': 'hashlib'},
|
|
255
|
+
{'module': 'glob', 'submodule': ['glob']},
|
|
228
256
|
{'module': 'core.editor', 'submodule': ['Editor']},
|
|
229
257
|
{'module': 'app.display', 'submodule': ['logs', 'notify', 'print_nxs', 'SaveScreen', 'RestoreScreen']},
|
|
230
258
|
{'module': 'core.pack', 'submodule': ['pack']},
|
|
@@ -397,13 +425,90 @@ class Client():
|
|
|
397
425
|
'token': __main__.settings.token
|
|
398
426
|
}
|
|
399
427
|
|
|
428
|
+
elif instruction=="channel":
|
|
429
|
+
command = {
|
|
430
|
+
'method': 'channel',
|
|
431
|
+
'action': 'list',
|
|
432
|
+
'token': __main__.settings.token
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if len(parameter)>1:
|
|
436
|
+
if argument.detail:
|
|
437
|
+
command['action'] = 'info'
|
|
438
|
+
|
|
439
|
+
elif argument.tree:
|
|
440
|
+
command['action'] = 'tree'
|
|
441
|
+
|
|
442
|
+
command['channel_name'] = parameter[1]
|
|
443
|
+
|
|
444
|
+
elif instruction=="fetch":
|
|
445
|
+
if len(parameter)>1:
|
|
446
|
+
command = {
|
|
447
|
+
'method': 'channel',
|
|
448
|
+
'action': 'fetch',
|
|
449
|
+
'channel_name': parameter[1],
|
|
450
|
+
'token': __main__.settings.token
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if len(parameter)>2:
|
|
454
|
+
command['path'] = parameter[2]
|
|
455
|
+
|
|
456
|
+
if argument.force:
|
|
457
|
+
command['force'] = True
|
|
458
|
+
else:
|
|
459
|
+
command['force'] = False
|
|
460
|
+
|
|
461
|
+
if argument.purge:
|
|
462
|
+
command['purge'] = True
|
|
463
|
+
else:
|
|
464
|
+
command['purge'] = False
|
|
465
|
+
|
|
466
|
+
elif instruction=="diff":
|
|
467
|
+
if len(parameter)>1:
|
|
468
|
+
command = {
|
|
469
|
+
'method': 'channel',
|
|
470
|
+
'action': 'diff',
|
|
471
|
+
'channel_name': parameter[1],
|
|
472
|
+
'token': __main__.settings.token
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if len(parameter)>2:
|
|
476
|
+
command['path'] = parameter[2]
|
|
477
|
+
|
|
478
|
+
elif instruction=="publish":
|
|
479
|
+
channel_name = parameter[1]
|
|
480
|
+
path = parameter[2]
|
|
481
|
+
|
|
482
|
+
expanded_path = os.path.expanduser(path)
|
|
483
|
+
|
|
484
|
+
files = glob(f"{expanded_path}/**/*", recursive=True)
|
|
485
|
+
#files = [os.path.relpath(p, expanded_path) for p in results]
|
|
486
|
+
push_file = []
|
|
487
|
+
|
|
488
|
+
for file in files:
|
|
489
|
+
if os.path.isfile(file):
|
|
490
|
+
push_file.append(os.path.relpath(file, expanded_path))
|
|
491
|
+
self.publish_file(file, expanded_path, channel_name)
|
|
492
|
+
|
|
493
|
+
if argument.purge:
|
|
494
|
+
logs("Purge du channel", "info")
|
|
495
|
+
command = {
|
|
496
|
+
'method': 'channel',
|
|
497
|
+
'action': 'purge',
|
|
498
|
+
'channel_name': parameter[1],
|
|
499
|
+
'list_files': push_file,
|
|
500
|
+
'token': __main__.settings.token
|
|
501
|
+
}
|
|
502
|
+
|
|
400
503
|
if command:
|
|
401
504
|
self.send_and_response(command)
|
|
402
505
|
|
|
403
506
|
|
|
404
507
|
@impmagic.loader(
|
|
405
508
|
{'module': '__main__'},
|
|
406
|
-
{'module': '
|
|
509
|
+
{'module': 'core.client.fetch', 'submodule': ['remove_unlisted_files', 'build_diff_tree']},
|
|
510
|
+
{'module': 'core.client.metafile', 'submodule': ['read_metafile', 'write_metafile']},
|
|
511
|
+
{'module': 'app.display', 'submodule': ['logs', 'notify', 'print_nxs', 'build_tree', 'print_tree']},
|
|
407
512
|
{'module': 'core.pack', 'submodule': ['pack']},
|
|
408
513
|
{'module': 'core.client.token', 'submodule': ['set_token']},
|
|
409
514
|
{'module': 'app.input', 'submodule': ['getpass']},
|
|
@@ -458,7 +563,6 @@ class Client():
|
|
|
458
563
|
for note in message['content']:
|
|
459
564
|
print(f" - {note}")
|
|
460
565
|
|
|
461
|
-
|
|
462
566
|
elif message['method']=="notify":
|
|
463
567
|
if 'message' in message and 'status_code' in message:
|
|
464
568
|
notify(message['message'], message['status_code'])
|
|
@@ -470,7 +574,104 @@ class Client():
|
|
|
470
574
|
payload['password_hash'] = hashlib.sha256(password.encode()).hexdigest()
|
|
471
575
|
|
|
472
576
|
self.send_and_response(payload)
|
|
577
|
+
|
|
578
|
+
elif message['method']=="channel":
|
|
579
|
+
if 'action' in message and message['action']=="fetch":
|
|
580
|
+
if 'tree' in message:
|
|
581
|
+
list_files = []
|
|
582
|
+
for file in message['tree']:
|
|
583
|
+
#Spécifie le chemin de destination
|
|
584
|
+
if 'path' in message:
|
|
585
|
+
file_path = os.path.join(message['path'], file['name'])
|
|
586
|
+
else:
|
|
587
|
+
file_path = file['name']
|
|
473
588
|
|
|
589
|
+
list_files.append(file['name'])
|
|
590
|
+
|
|
591
|
+
#Forcer la mise à jour du contenu
|
|
592
|
+
if 'force' in message:
|
|
593
|
+
force = message['force']
|
|
594
|
+
else:
|
|
595
|
+
force = False
|
|
596
|
+
|
|
597
|
+
#Supprimer les fichiers non présent dans le channel
|
|
598
|
+
if 'purge' in message:
|
|
599
|
+
purge = message['purge']
|
|
600
|
+
else:
|
|
601
|
+
purge = False
|
|
602
|
+
|
|
603
|
+
if not os.path.exists(os.path.dirname(file_path)):
|
|
604
|
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
|
605
|
+
|
|
606
|
+
content_hash = None
|
|
607
|
+
if os.path.exists(file_path):
|
|
608
|
+
with open(file_path, 'rb') as f:
|
|
609
|
+
content_hash = hashlib.sha256(f.read()).hexdigest()
|
|
610
|
+
|
|
611
|
+
if content_hash and content_hash==file['content_hash'] and not force:
|
|
612
|
+
pass
|
|
613
|
+
else:
|
|
614
|
+
with open(file_path, 'wb') as f:
|
|
615
|
+
f.write(file['content'])
|
|
616
|
+
if content_hash:
|
|
617
|
+
logs(f"{file['name']} mis à jour", "success", force=True)
|
|
618
|
+
else:
|
|
619
|
+
logs(f"{file['name']} récupéré", "success", force=True)
|
|
620
|
+
|
|
621
|
+
if purge:
|
|
622
|
+
if 'path' in message:
|
|
623
|
+
file_path = message['path']
|
|
624
|
+
else:
|
|
625
|
+
file_path = "."
|
|
626
|
+
expanded_path = os.path.expanduser(file_path)
|
|
627
|
+
list_files = [p.replace('\\', '/') for p in list_files]
|
|
628
|
+
|
|
629
|
+
remove_unlisted_files(file_path, list_files)
|
|
630
|
+
|
|
631
|
+
"""
|
|
632
|
+
if 'channel_hash' in message:
|
|
633
|
+
if 'path' in message:
|
|
634
|
+
file_path = os.path.join(message['path'], '.znote')
|
|
635
|
+
else:
|
|
636
|
+
file_path = ".znote"
|
|
637
|
+
|
|
638
|
+
content = {
|
|
639
|
+
"channel_name": message['channel_name'],
|
|
640
|
+
"channel_hash": message['channel_hash'],
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
write_metafile(content, file_path)
|
|
644
|
+
"""
|
|
645
|
+
|
|
646
|
+
elif 'action' in message and message['action']=="diff":
|
|
647
|
+
if 'tree' in message:
|
|
648
|
+
if 'path' in message:
|
|
649
|
+
file_path = message['path']
|
|
650
|
+
else:
|
|
651
|
+
file_path = "."
|
|
652
|
+
print(file_path)
|
|
653
|
+
diff_output = build_diff_tree(file_path, message["tree"])
|
|
654
|
+
print_tree(diff_output)
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
elif 'action' in message and message['action']=="list":
|
|
658
|
+
if 'channel_list' in message:
|
|
659
|
+
for channel_name in message['channel_list']:
|
|
660
|
+
print_nxs(" - ", color='dark_gray',nojump=True)
|
|
661
|
+
print_nxs(channel_name)
|
|
662
|
+
|
|
663
|
+
elif 'action' in message and message['action']=="info":
|
|
664
|
+
if 'channel_info' in message:
|
|
665
|
+
for element in ['name', 'description', 'hash', 'updated_at', 'total_files']:
|
|
666
|
+
if element in message['channel_info'] and message['channel_info'][element]:
|
|
667
|
+
print_nxs(f"{element}: ", color='dark_gray',nojump=True)
|
|
668
|
+
print_nxs(message['channel_info'][element])
|
|
669
|
+
|
|
670
|
+
elif 'action' in message and message['action']=="tree":
|
|
671
|
+
if 'list_files' in message:
|
|
672
|
+
tree = build_tree(message['list_files'])
|
|
673
|
+
print_tree(tree)
|
|
674
|
+
|
|
474
675
|
else:
|
|
475
676
|
print(message)
|
|
476
677
|
|
|
@@ -526,6 +727,10 @@ def run_client():
|
|
|
526
727
|
parse.set_argument(longname="create", description="Création d'un workspace", default=False, category="workspace")
|
|
527
728
|
parse.set_argument(longname="remove", description="Suppression d'un workspace", default=False, category="workspace")
|
|
528
729
|
parse.set_argument(longname="info", description="Afficher les informations de connexion", default=False)
|
|
730
|
+
parse.set_argument(longname="purge", description="Purger le channel durant le publication ou durant le fetch", default=False, category="channel")
|
|
731
|
+
parse.set_argument(longname="force", description="Forcer le changement avec fetch", default=False, category="channel")
|
|
732
|
+
parse.set_argument(longname="detail", description="Afficher les informations du channel", default=False, category="channel")
|
|
733
|
+
parse.set_argument(longname="tree", description="Afficher l'arborescence du channel", default=False, category="channel")
|
|
529
734
|
#parse.set_parameter("command", description="commande à lancer")
|
|
530
735
|
parse.set_parameter("login", "Connexion à un serveur de note")
|
|
531
736
|
parse.set_parameter("logout", "Déconnexion d'un serveur de note")
|
|
@@ -541,6 +746,9 @@ def run_client():
|
|
|
541
746
|
parse.set_parameter("info", "Afficher les informations d'une note")
|
|
542
747
|
parse.set_parameter("find", "Rechercher un contenu dans les notes")
|
|
543
748
|
parse.set_parameter("protect", "Protéger une note par mot de passe")
|
|
749
|
+
parse.set_parameter("publish", "Publier du contenu sur un channel")
|
|
750
|
+
parse.set_parameter("fetch", "Récupérer le contenu sur un channel")
|
|
751
|
+
parse.set_parameter("channel", "Afficher les informations sur les channels")
|
|
544
752
|
parse.disable_check()
|
|
545
753
|
parameter, argument = parse.load()
|
|
546
754
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import impmagic
|
|
2
|
+
|
|
3
|
+
@impmagic.loader(
|
|
4
|
+
{'module': 'os'},
|
|
5
|
+
{'module': 'glob'},
|
|
6
|
+
{'module': 'app.display', 'submodule': ['logs']},
|
|
7
|
+
)
|
|
8
|
+
def remove_unlisted_files(base_dir, paths_to_keep):
|
|
9
|
+
# Normaliser les chemins (en absolu)
|
|
10
|
+
paths_to_keep_abs = {os.path.abspath(os.path.join(base_dir, p)) for p in paths_to_keep}
|
|
11
|
+
|
|
12
|
+
# Lister tous les fichiers dans base_dir récursivement
|
|
13
|
+
all_files = glob.glob(os.path.join(base_dir, '**'), recursive=True)
|
|
14
|
+
all_files = [f for f in all_files if os.path.isfile(f)]
|
|
15
|
+
all_files = [p.replace('\\', '/') for p in all_files]
|
|
16
|
+
|
|
17
|
+
# Supprimer ceux qui ne sont pas dans paths_to_keep
|
|
18
|
+
for file_path in all_files:
|
|
19
|
+
abs_path = os.path.abspath(file_path)
|
|
20
|
+
if abs_path not in paths_to_keep_abs:
|
|
21
|
+
logs(f"{os.path.expanduser(file_path)} supprimé", force=True)
|
|
22
|
+
os.remove(abs_path)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
import os
|
|
27
|
+
import hashlib
|
|
28
|
+
from collections import defaultdict
|
|
29
|
+
from app.display import build_tree_from_dict
|
|
30
|
+
|
|
31
|
+
def build_diff_tree(base_path, remote_tree):
|
|
32
|
+
local_files = {}
|
|
33
|
+
for root, _, files in os.walk(base_path):
|
|
34
|
+
for file in files:
|
|
35
|
+
full_path = os.path.join(root, file)
|
|
36
|
+
rel_path = os.path.relpath(full_path, base_path).replace('\\', '/')
|
|
37
|
+
with open(full_path, 'rb') as f:
|
|
38
|
+
local_files[rel_path] = hashlib.sha256(f.read()).hexdigest()
|
|
39
|
+
|
|
40
|
+
remote_map = {f["name"].replace('\\', '/'): f["content_hash"] for f in remote_tree}
|
|
41
|
+
all_paths = set(local_files) | set(remote_map)
|
|
42
|
+
|
|
43
|
+
diff = {}
|
|
44
|
+
for path in sorted(all_paths):
|
|
45
|
+
if path in local_files and path in remote_map:
|
|
46
|
+
if local_files[path] == remote_map[path]:
|
|
47
|
+
diff[path] = "OK"
|
|
48
|
+
else:
|
|
49
|
+
diff[path] = "UPDATE"
|
|
50
|
+
elif path in local_files:
|
|
51
|
+
diff[path] = "DELETE"
|
|
52
|
+
else:
|
|
53
|
+
diff[path] = "NEW"
|
|
54
|
+
|
|
55
|
+
return build_tree_from_dict(diff)
|
|
56
|
+
|
|
57
|
+
def build_tree_view(diff_map):
|
|
58
|
+
tree = defaultdict(list)
|
|
59
|
+
for path, status in diff_map.items():
|
|
60
|
+
parts = path.split('/')
|
|
61
|
+
for i in range(1, len(parts)):
|
|
62
|
+
folder = '/'.join(parts[:i])
|
|
63
|
+
tree[folder].append(None)
|
|
64
|
+
tree[path] = status
|
|
65
|
+
|
|
66
|
+
printed = set()
|
|
67
|
+
lines = []
|
|
68
|
+
|
|
69
|
+
def print_branch(prefix, path_parts):
|
|
70
|
+
full_path = '/'.join(path_parts)
|
|
71
|
+
entries = {k for k in tree if k.startswith(full_path + '/') and k.count('/') == len(path_parts)}
|
|
72
|
+
files = sorted(e for e in entries if tree[e] != None)
|
|
73
|
+
folders = sorted(e for e in entries if tree[e] == None)
|
|
74
|
+
for i, item in enumerate(folders + files):
|
|
75
|
+
connector = '└── ' if i == len(folders + files) - 1 else '├── '
|
|
76
|
+
label = item.split('/')[-1]
|
|
77
|
+
status = f" [{tree[item]}]" if tree[item] else ''
|
|
78
|
+
lines.append(f"{prefix}{connector}{label}{status}")
|
|
79
|
+
if tree[item] is None:
|
|
80
|
+
print_branch(prefix + (' ' if i == len(folders + files) - 1 else '│ '), item.split('/'))
|
|
81
|
+
|
|
82
|
+
print_branch("", [])
|
|
83
|
+
return '\n'.join(lines)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
|
|
3
|
+
def read_metafile(path):
|
|
4
|
+
if os.path.exists(path):
|
|
5
|
+
with open(path, 'r') as file:
|
|
6
|
+
content = yaml.safe_load(file)
|
|
7
|
+
return content
|
|
8
|
+
return {}
|
|
9
|
+
|
|
10
|
+
def write_metafile(content, path):
|
|
11
|
+
with open(path, 'w') as file:
|
|
12
|
+
yaml.dump(content, file)
|
|
@@ -14,12 +14,12 @@ class Settings():
|
|
|
14
14
|
{'module': 'core.client.token','submodule': ['get_token']}
|
|
15
15
|
)
|
|
16
16
|
def __init__(self, config_file):
|
|
17
|
-
self.config_dir = os.path.abspath(os.path.expanduser('~/.config/znote'))
|
|
18
|
-
|
|
19
|
-
self.token_file = os.path.join(self.config_dir, ".token")
|
|
20
|
-
|
|
21
17
|
if not config_file:
|
|
22
|
-
|
|
18
|
+
config_dir = os.path.abspath(os.path.expanduser('~/.config/znote'))
|
|
19
|
+
config_file = os.path.join(config_dir, "config.yml")
|
|
20
|
+
|
|
21
|
+
if not os.path.exists(os.path.dirname(config_file)):
|
|
22
|
+
os.makedirs(os.path.dirname(config_file), exist_ok=True)
|
|
23
23
|
|
|
24
24
|
self.config = Config(f"{config_file}")
|
|
25
25
|
|
|
@@ -28,8 +28,12 @@ class Settings():
|
|
|
28
28
|
self.host = self.config.get('client.host', default="127.0.0.1", auto_set=True)
|
|
29
29
|
self.port = self.config.get('client.port', default=40017, auto_set=True)
|
|
30
30
|
self.key_size = self.config.get('client.key_size', default=2048, auto_set=True)
|
|
31
|
-
self.key_otp = self.config.get('client.key_otp', default='JBSWY3DPEHPK3PXP', auto_set=True)
|
|
31
|
+
self.key_otp = self.config.get('client.key_otp', default='JBSWY3DPEHPK3PXP', auto_set=True)
|
|
32
|
+
self.config_dir = self.config.get('server.working_dir', default=os.path.abspath(os.path.expanduser('~/.config/znote')), auto_set=True)
|
|
33
|
+
|
|
32
34
|
self.totp = pyotp.TOTP(self.key_otp)
|
|
35
|
+
|
|
36
|
+
self.token_file = os.path.join(self.config_dir, ".token")
|
|
33
37
|
|
|
34
38
|
self.PubServ = None
|
|
35
39
|
self.keyC = None
|
|
@@ -123,6 +127,7 @@ class Settings():
|
|
|
123
127
|
port = input(f"{fg('dark_gray')}port [{fg('cyan')}{self.port}{fg('dark_gray')}]: ")
|
|
124
128
|
key_size = input(f"{fg('dark_gray')}key_size [{fg('cyan')}{self.key_size}{fg('dark_gray')}]: ")
|
|
125
129
|
key_otp = input(f"{fg('dark_gray')}key_otp [{fg('cyan')}{self.key_otp}{fg('dark_gray')}]: ")
|
|
130
|
+
working_dir = input(f"{fg('dark_gray')}working_dir [{fg('cyan')}{self.working_dir}{fg('dark_gray')}]: ")
|
|
126
131
|
verbose = input(f"{fg('dark_gray')}verbose [{fg('cyan')}{self.verbose}{fg('dark_gray')}]: ")
|
|
127
132
|
|
|
128
133
|
if host:
|
|
@@ -137,6 +142,9 @@ class Settings():
|
|
|
137
142
|
if key_otp:
|
|
138
143
|
self.config.set('server.key_otp', key_otp)
|
|
139
144
|
|
|
145
|
+
if working_dir:
|
|
146
|
+
self.config.set('server.working_dir', working_dir)
|
|
147
|
+
|
|
140
148
|
if verbose:
|
|
141
149
|
self.config.set('server.verbose', verbose)
|
|
142
150
|
|
|
@@ -10,14 +10,12 @@ class Settings:
|
|
|
10
10
|
{'module': 'core.config', 'submodule': ['Config']},
|
|
11
11
|
)
|
|
12
12
|
def __init__(self, config_file=None):
|
|
13
|
-
self.config_dir = os.path.abspath(os.path.expanduser('~/.config/znote'))
|
|
14
|
-
|
|
15
|
-
if not os.path.exists(self.config_dir):
|
|
16
|
-
os.makedirs(self.config_dir, exist_ok=True)
|
|
17
|
-
|
|
18
13
|
if not config_file:
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
config_dir = os.path.abspath(os.path.expanduser('~/.config/znote'))
|
|
15
|
+
config_file = os.path.join(config_dir, "config.yml")
|
|
16
|
+
|
|
17
|
+
if not os.path.exists(os.path.dirname(config_file)):
|
|
18
|
+
os.makedirs(os.path.dirname(config_file), exist_ok=True)
|
|
21
19
|
|
|
22
20
|
self.config = Config(config_file)
|
|
23
21
|
|
|
@@ -27,6 +25,7 @@ class Settings:
|
|
|
27
25
|
self.port = self.config.get('server.port', default=40017, auto_set=True)
|
|
28
26
|
self.key_size = self.config.get('server.key_size', default=2048, auto_set=True)
|
|
29
27
|
self.key_otp = self.config.get('server.key_otp', default='JBSWY3DPEHPK3PXP', auto_set=True)
|
|
28
|
+
self.config_dir = self.config.get('server.working_dir', default=os.path.abspath(os.path.expanduser('~/.config/znote')), auto_set=True)
|
|
30
29
|
|
|
31
30
|
#Initialisation de l'OTP pour la connexion initiale
|
|
32
31
|
if not re.match(r'^[A-Z0-9]{16}$', self.key_otp):
|
|
@@ -36,7 +35,9 @@ class Settings:
|
|
|
36
35
|
|
|
37
36
|
self.data_dir = self.config.get('server.data_dir', default=None, auto_set=False)
|
|
38
37
|
|
|
39
|
-
if
|
|
38
|
+
if self.data_dir:
|
|
39
|
+
self.data_dir = os.path.join(self.config_dir, self.data_dir)
|
|
40
|
+
else:
|
|
40
41
|
self.data_dir = os.path.join(self.config_dir, "znote.db")
|
|
41
42
|
|
|
42
43
|
|
|
@@ -127,6 +128,7 @@ class Settings:
|
|
|
127
128
|
key_size = input(f"{fg('dark_gray')}key_size [{fg('cyan')}{self.key_size}{fg('dark_gray')}]: ")
|
|
128
129
|
key_otp = input(f"{fg('dark_gray')}key_otp [{fg('cyan')}{self.key_otp}{fg('dark_gray')}]: ")
|
|
129
130
|
data_dir = input(f"{fg('dark_gray')}data_dir [{fg('cyan')}{self.data_dir}{fg('dark_gray')}]: ")
|
|
131
|
+
working_dir = input(f"{fg('dark_gray')}working_dir [{fg('cyan')}{self.working_dir}{fg('dark_gray')}]: ")
|
|
130
132
|
verbose = input(f"{fg('dark_gray')}verbose [{fg('cyan')}{self.verbose}{fg('dark_gray')}]: ")
|
|
131
133
|
|
|
132
134
|
if host:
|
|
@@ -144,5 +146,8 @@ class Settings:
|
|
|
144
146
|
if data_dir:
|
|
145
147
|
self.config.set('server.data_dir', data_dir)
|
|
146
148
|
|
|
149
|
+
if working_dir:
|
|
150
|
+
self.config.set('server.working_dir', working_dir)
|
|
151
|
+
|
|
147
152
|
if verbose:
|
|
148
153
|
self.config.set('server.verbose', verbose)
|
|
@@ -22,4 +22,20 @@ def Table(db, type_db="sqlite"):
|
|
|
22
22
|
editor_id = Required(int)
|
|
23
23
|
protected = Optional(str)
|
|
24
24
|
shared_with = Optional(str) # Liste d’IDs ou noms d’utilisateurs
|
|
25
|
-
workspace = Required(str)
|
|
25
|
+
workspace = Required(str)
|
|
26
|
+
|
|
27
|
+
class Channel(db.Entity):
|
|
28
|
+
id = PrimaryKey(int, auto=True)
|
|
29
|
+
name = Required(str, unique=True) # Nom du canal
|
|
30
|
+
description = Optional(str)
|
|
31
|
+
channel_hash = Required(str) # Dernier hash publié
|
|
32
|
+
updated_at = Required(datetime)
|
|
33
|
+
creator_id = Required(int) # ID de l'utilisateur qui a créé le canal
|
|
34
|
+
|
|
35
|
+
class PubChannel(db.Entity):
|
|
36
|
+
id = PrimaryKey(int, auto=True)
|
|
37
|
+
channel_id = Required(int) # ID du canal (référence manuelle)
|
|
38
|
+
name = Required(str)
|
|
39
|
+
content = Required(str)
|
|
40
|
+
content_hash = Required(str)
|
|
41
|
+
updated_at = Required(datetime)
|
|
@@ -4,7 +4,13 @@ import impmagic
|
|
|
4
4
|
import threading
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
@impmagic.loader(
|
|
8
|
+
{'module': 'uuid'},
|
|
9
|
+
{'module': 'hashlib'},
|
|
10
|
+
)
|
|
11
|
+
def generate_unique_hash() -> str:
|
|
12
|
+
unique_id = uuid.uuid4().hex
|
|
13
|
+
return hashlib.sha256(unique_id.encode('utf-8')).hexdigest()
|
|
8
14
|
|
|
9
15
|
|
|
10
16
|
#Supprime le nom d'utilisateur de la liste lorsqu'il se déconnecte
|
|
@@ -187,6 +193,7 @@ class ThreadClient(threading.Thread):
|
|
|
187
193
|
{'module': 'json'},
|
|
188
194
|
{'module': 'hashlib'},
|
|
189
195
|
{'module': 'traceback'},
|
|
196
|
+
{'module': 'base64'},
|
|
190
197
|
{'module': 'core.pack', 'submodule': ['pack', 'unpack']},
|
|
191
198
|
{'module': 'app.display', 'submodule': ['logs']},
|
|
192
199
|
{'module': 'datetime', 'submodule': ['datetime']},
|
|
@@ -548,6 +555,162 @@ class ThreadClient(threading.Thread):
|
|
|
548
555
|
payload = {'method': 'find', 'content': note_list}
|
|
549
556
|
else:
|
|
550
557
|
payload = {'method': 'notify', 'message': "Aucune note trouvée", 'status_code': "error"}
|
|
558
|
+
|
|
559
|
+
elif message['method']=="channel":
|
|
560
|
+
with pony.db_session:
|
|
561
|
+
if 'action' in message:
|
|
562
|
+
if message['action']=="publish":
|
|
563
|
+
for deps in ['channel_name', 'name', 'content', 'hash']:
|
|
564
|
+
if deps not in message:
|
|
565
|
+
logs(f"[{self.get_name_user()}] - {deps} manquant dans la demande", "error")
|
|
566
|
+
payload = {'method': 'notify', 'message': f'{deps} manquant dans la demande', 'status_code': 'error'}
|
|
567
|
+
break
|
|
568
|
+
else:
|
|
569
|
+
#Convertis en base64 pour stockage en base
|
|
570
|
+
message['content'] = base64.b64encode(message['content']).decode("utf-8")
|
|
571
|
+
|
|
572
|
+
channel = __main__.settings.orm.Channel.get(name=message['channel_name'])
|
|
573
|
+
if channel:
|
|
574
|
+
channel_id = channel.id
|
|
575
|
+
else:
|
|
576
|
+
logs(f"[{self.get_name_user()}] - Création du channel {message['channel_name']}", "success")
|
|
577
|
+
__main__.settings.orm.Channel(name=message['channel_name'], channel_hash=generate_unique_hash(), updated_at=datetime.now(), creator_id=self.user_id)
|
|
578
|
+
pony.commit()
|
|
579
|
+
|
|
580
|
+
channel = __main__.settings.orm.Channel.get(name=message['channel_name'])
|
|
581
|
+
channel_id = channel.id
|
|
582
|
+
|
|
583
|
+
pub_content = __main__.settings.orm.PubChannel.get(channel_id=channel_id , name=message['name'])
|
|
584
|
+
|
|
585
|
+
if pub_content:
|
|
586
|
+
if pub_content.content_hash!=message['hash']:
|
|
587
|
+
pub_content.content = message['content']
|
|
588
|
+
pub_content.content_hash = message['hash']
|
|
589
|
+
pub_content.updated_at=datetime.now()
|
|
590
|
+
|
|
591
|
+
channel.updated_at=datetime.now()
|
|
592
|
+
channel.channel_hash=generate_unique_hash()
|
|
593
|
+
|
|
594
|
+
logs(f"[{self.get_name_user()}] - channel: {message['channel_name']} - {message['name']} mis à jour", "success")
|
|
595
|
+
payload = {'method': 'notify', 'message': f"{message['name']} mis à jour", 'status_code': 'success'}
|
|
596
|
+
else:
|
|
597
|
+
logs(f"[{self.get_name_user()}] - channel: {message['channel_name']} - {message['name']} à jour", "info")
|
|
598
|
+
payload = {'method': 'notify', 'message': f"{message['name']} à jour", 'status_code': 'info'}
|
|
599
|
+
|
|
600
|
+
else:
|
|
601
|
+
publish_file = __main__.settings.orm.PubChannel(channel_id=channel_id , name=message['name'], content=message['content'], content_hash=message['hash'], updated_at=datetime.now())
|
|
602
|
+
|
|
603
|
+
channel.updated_at=datetime.now()
|
|
604
|
+
channel.channel_hash=generate_unique_hash()
|
|
605
|
+
|
|
606
|
+
logs(f"[{self.get_name_user()}] - channel: {message['channel_name']} - {message['name']} publié", "success")
|
|
607
|
+
payload = {'method': 'notify', 'message': f"{message['name']} publié", 'status_code': 'success'}
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
pony.commit()
|
|
612
|
+
|
|
613
|
+
elif message['action']=="purge":
|
|
614
|
+
if 'list_files' in message:
|
|
615
|
+
logs(f"[{self.get_name_user()}] - channel: {message['channel_name']} - purge du channel", "info")
|
|
616
|
+
|
|
617
|
+
channel = __main__.settings.orm.Channel.get(name=message['channel_name'])
|
|
618
|
+
channel_id = channel.id
|
|
619
|
+
|
|
620
|
+
query = __main__.settings.orm.PubChannel.select(lambda p: p.channel_id == channel_id)
|
|
621
|
+
result = query[:]
|
|
622
|
+
|
|
623
|
+
for file in result:
|
|
624
|
+
if file.name not in message['list_files']:
|
|
625
|
+
logs(f"[{self.get_name_user()}] - channel: {message['channel_name']} - {file.name} obsolète", "info")
|
|
626
|
+
file.delete()
|
|
627
|
+
|
|
628
|
+
pony.commit()
|
|
629
|
+
|
|
630
|
+
payload = {'method': 'notify', 'message': f"channel {message['channel_name']} purgé", 'status_code': 'success'}
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
elif message['action']=="fetch_hash":
|
|
634
|
+
channel = __main__.settings.orm.Channel.get(name=message['channel_name'])
|
|
635
|
+
if channel:
|
|
636
|
+
payload = {'method': 'channel', 'action': 'fetch_hash', 'channel_hash': channel.channel_hash}
|
|
637
|
+
else:
|
|
638
|
+
payload = {'method': 'notify', 'message': f"channel {message['channel_name']} introuvable", 'status_code': 'error'}
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
elif message['action']=="fetch" or message['action']=="diff":
|
|
642
|
+
channel = __main__.settings.orm.Channel.get(name=message['channel_name'])
|
|
643
|
+
if channel:
|
|
644
|
+
channel_id = channel.id
|
|
645
|
+
query = __main__.settings.orm.PubChannel.select(lambda p: p.channel_id == channel_id)
|
|
646
|
+
result = query[:]
|
|
647
|
+
|
|
648
|
+
tree = []
|
|
649
|
+
|
|
650
|
+
for file in result:
|
|
651
|
+
tree.append({
|
|
652
|
+
"name": file.name,
|
|
653
|
+
"content": base64.b64decode(file.content.encode("utf-8")),
|
|
654
|
+
"content_hash": file.content_hash,
|
|
655
|
+
})
|
|
656
|
+
|
|
657
|
+
payload = {'method': 'channel', 'action': message['action'], 'tree': tree, 'channel_hash': channel.channel_hash, 'channel_name': message['channel_name']}
|
|
658
|
+
|
|
659
|
+
for element in ['path', 'force', 'purge']:
|
|
660
|
+
if element in message:
|
|
661
|
+
payload[element] = message[element]
|
|
662
|
+
|
|
663
|
+
else:
|
|
664
|
+
payload = {'method': 'notify', 'message': f"channel {message['channel_name']} introuvable", 'status_code': 'error'}
|
|
665
|
+
|
|
666
|
+
elif message['action']=="list":
|
|
667
|
+
query = __main__.settings.orm.Channel.select()
|
|
668
|
+
result = query[:]
|
|
669
|
+
|
|
670
|
+
channel_list = []
|
|
671
|
+
for chan in result:
|
|
672
|
+
channel_list.append(chan.name)
|
|
673
|
+
|
|
674
|
+
payload = {'method': 'channel', 'action': 'list', 'channel_list': channel_list}
|
|
675
|
+
|
|
676
|
+
elif message['action']=="info":
|
|
677
|
+
if "channel_name" in message:
|
|
678
|
+
channel = __main__.settings.orm.Channel.get(name=message['channel_name'])
|
|
679
|
+
if channel:
|
|
680
|
+
channel_info = {
|
|
681
|
+
"name": channel.name,
|
|
682
|
+
"description": channel.description,
|
|
683
|
+
"hash": channel.channel_hash,
|
|
684
|
+
"updated_at": channel.updated_at
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
channel_id = channel.id
|
|
688
|
+
query = __main__.settings.orm.PubChannel.select(lambda p: p.channel_id == channel_id)
|
|
689
|
+
result = query[:]
|
|
690
|
+
channel_info["total_files"] = len(result)
|
|
691
|
+
|
|
692
|
+
payload = {'method': 'channel', 'action': 'info', 'channel_info': channel_info}
|
|
693
|
+
|
|
694
|
+
else:
|
|
695
|
+
payload = {'method': 'notify', 'message': f"channel {message['channel_name']} introuvable", 'status_code': 'error'}
|
|
696
|
+
|
|
697
|
+
elif message['action']=="tree":
|
|
698
|
+
if "channel_name" in message:
|
|
699
|
+
channel = __main__.settings.orm.Channel.get(name=message['channel_name'])
|
|
700
|
+
if channel:
|
|
701
|
+
channel_id = channel.id
|
|
702
|
+
query = __main__.settings.orm.PubChannel.select(lambda p: p.channel_id == channel_id)
|
|
703
|
+
result = query[:]
|
|
704
|
+
|
|
705
|
+
list_files = []
|
|
706
|
+
for file in result:
|
|
707
|
+
list_files.append(file.name)
|
|
708
|
+
|
|
709
|
+
payload = {'method': 'channel', 'action': 'tree', 'list_files': list_files}
|
|
710
|
+
|
|
711
|
+
else:
|
|
712
|
+
payload = {'method': 'notify', 'message': f"channel {message['channel_name']} introuvable", 'status_code': 'error'}
|
|
713
|
+
|
|
551
714
|
|
|
552
715
|
else:
|
|
553
716
|
logs(f"[{self.get_name_user()}] - Commande reçu >> {message['method']}", "info")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: znote
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.3
|
|
4
4
|
Summary: Vault de prise de note
|
|
5
5
|
Author:
|
|
6
6
|
License: mit
|
|
@@ -40,6 +40,7 @@ znote est une application de prise de notes en ligne de commande fonctionnant su
|
|
|
40
40
|
- **Éditeur Intégré** : Un éditeur de texte simple est intégré directement dans le terminal pour une expérience fluide.
|
|
41
41
|
- **Workspaces (Espaces de travail)** : Organisez vos notes dans des espaces de travail distincts.
|
|
42
42
|
- **Synchronisation de Fichiers** : Envoyez (`push`) un fichier local pour créer une note, ou téléchargez (`pull`) une note dans un fichier.
|
|
43
|
+
- **Channels de Diffusion** : Partagez et synchronisez des fichiers sur plusieurs postes via des "channels".
|
|
43
44
|
- **Protection par Mot de Passe** : Protégez l'accès à des notes spécifiques avec un mot de passe.
|
|
44
45
|
- **Recherche** : Retrouvez du contenu rapidement grâce à une fonction de recherche dans toutes vos notes.
|
|
45
46
|
- **Stockage en base** : Pour l'instant les notes sont stockées en clair dans la base. A voir pour rajouter le support du chiffrement côté serveur.
|
|
@@ -92,6 +93,18 @@ Le serveur tournera en arrière-plan, en attente de connexions.
|
|
|
92
93
|
| `--username <nom>` | Spécifie le nom d'utilisateur pour la création. | `... --username admin` |
|
|
93
94
|
| `--password <mdp>` | Spécifie le mot de passe pour la création. | `... --password "un_mot_de_passe_solide"` |
|
|
94
95
|
|
|
96
|
+
### Détail du fichier de config
|
|
97
|
+
|
|
98
|
+
```ini
|
|
99
|
+
server:
|
|
100
|
+
verbose: true #Afficher les logs
|
|
101
|
+
host: 127.0.0.1 #Ip d'écoute
|
|
102
|
+
port: 40017 #Port d'écoute
|
|
103
|
+
key_size: 2048 #Taille de la clé RSA
|
|
104
|
+
key_otp: JFSWY3DPEHPK3PXP #Clé OTP pour le démarrage
|
|
105
|
+
data_dir: #Chemin vers la base SQLite
|
|
106
|
+
working_dir: #Emplacement des fichiers de l'application
|
|
107
|
+
```
|
|
95
108
|
|
|
96
109
|
## Côté client
|
|
97
110
|
|
|
@@ -141,3 +154,33 @@ La syntaxe générale est `znote <commande> [arguments...]`.
|
|
|
141
154
|
| `workspace <nom_ws>` | Change le workspace actif. | `znote workspace "Projets"` |
|
|
142
155
|
| `workspace <nom_ws> --create` | Crée un nouveau workspace. | `znote workspace "Archives" --create` |
|
|
143
156
|
| `workspace <nom_ws> --remove` | Supprime un workspace. | `znote workspace "Archives" --remove` |
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
### Commandes des Channels
|
|
160
|
+
|
|
161
|
+
La fonctionnalité de "channel" permet de synchroniser des fichiers entre plusieurs postes.
|
|
162
|
+
|
|
163
|
+
| Commande | Description | Exemple d’utilisation |
|
|
164
|
+
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
165
|
+
| `channel` | Liste tous les channels disponibles sur le serveur. | `znote channel` |
|
|
166
|
+
| `channel <nom> --detail` | Affiche les informations détaillées d'un channel (hash, date de modification...). | `znote channel mon-canal --detail` |
|
|
167
|
+
| `channel <nom> --tree` | Affiche l'arborescence et la liste des fichiers contenus dans le channel. | `znote channel mon-canal --tree` |
|
|
168
|
+
| `publish <channel> <fichier>` | Publie un fichier sur un channel. Si le fichier existe déjà , il est mis à jour. | `znote publish mon-canal ./rapport.pdf` |
|
|
169
|
+
| `publish <channel> --purge` | Supprime les fichiers du channel qui ne sont pas présents dans la publication actuelle. | `znote publish mon-canal --purge` |
|
|
170
|
+
| `fetch <channel> (<path>)` | Récupère (télécharge) tous les fichiers d'un channel dans le répertoire local. | `znote fetch mon-canal` |
|
|
171
|
+
| `fetch <channel> --force` | Force le téléchargement et l'écrasement des fichiers locaux même s'ils existent déjà . | `znote fetch mon-canal --force` |
|
|
172
|
+
| `fetch <channel> --purge` | Supprime les fichiers locaux qui ne sont pas présent dans le channel. | `znote fetch mon-canal --purge` |
|
|
173
|
+
| `diff <channel>` | Compare le répertoire local avec le contenu du channel et affiche les différences (fichiers nouveaux, modifiés ou à supprimer). | `znote diff mon-canal` |
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
### Détail du fichier de config
|
|
177
|
+
|
|
178
|
+
```ini
|
|
179
|
+
client:
|
|
180
|
+
verbose: true #Afficher les logs
|
|
181
|
+
host: 127.0.0.1 #Ip du serveur
|
|
182
|
+
port: 40017 #Port du serveur
|
|
183
|
+
key_size: 2048 #Taille de la clé RSA
|
|
184
|
+
key_otp: JFSWY3DPEHPK3PXP #Clé OTP pour le démarrage
|
|
185
|
+
working_dir: #Emplacement des fichiers de l'application
|
|
186
|
+
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|