-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnextcloud-status.py
executable file
·251 lines (208 loc) · 8.53 KB
/
nextcloud-status.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#!/usr/bin/python3
#
# This program aims to implement a nextcloud status request command-line tool that
# works as similar as possible to 'dropbox status'.
# Most of the time, it just prints 'Up to date' or 'Syncing..' to stdout.
#
# The parts for connecting and querying the nextcloud client socket are borrowed from
# the OwnCloud integration to Nautilus by Klaas Freitag, see:
# https://github.com/nextcloud/desktop/blob/master/shell_integration/nautilus/syncstate.py
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# If you want to implement other socket info/action requests, you can find a list at:
# https://github.com/nextcloud/desktop/blob/master/src/gui/socketapi/socketapi.h
import sys
python3 = sys.version_info[0] >= 3
import os
import urllib
if python3:
import urllib.parse
import socket
import tempfile
import time
import configparser
#from gi.repository import GObject, Nautilus
# Note: setappname.sh will search and replace 'ownCloud' on this file to update this line and other
# occurrences of the name
appname = 'Nextcloud'
### startup info silenced
#print("Initializing "+appname+"-client-nautilus extension")
#print("--> This version of "+appname+"-client-nautilus was changed by @fabianostermann")
#print("Using python version {}".format(sys.version_info))
# determine nextcloud path
nextcloud_path = "~/Nextcloud"
try:
config = configparser.ConfigParser()
config.read(os.path.expanduser("~/.config/Nextcloud/nextcloud.cfg"))
for key, value in config["Accounts"].items():
if "localpath" in key:
nextcloud_path = value
break
except:
print("Could not read config, falling back to default path ("+nextcloud_path+")..")
nextcloud_path = os.path.expanduser(nextcloud_path)
nextcloud_path = os.path.realpath(nextcloud_path)
def get_local_path(url):
if url[0:7] == 'file://':
url = url[7:]
unquote = urllib.parse.unquote if python3 else urllib.unquote
return unquote(url)
def get_runtime_dir():
"""Returns the value of $XDG_RUNTIME_DIR, a directory path.
If the value is not set, returns the same default as in Qt5
"""
try:
return os.environ['XDG_RUNTIME_DIR']
except KeyError:
fallback = os.path.join(tempfile.gettempdir(), 'runtime-' + os.environ['USER'])
return fallback
class SocketConnect:#(GObject.GObject):
def __init__(self):
#GObject.GObject.__init__(self)
self.connected = False
self.registered_paths = {}
self._watch_id = 0
self._sock = None
self._listeners = [self._update_registered_paths, self._get_version]
self._remainder = ''.encode() if python3 else ''
self.protocolVersion = '1.0'
self.nautilusVFSFile_table = {} # not needed in this object actually but shared
# all over the other objects.
# returns true when one should try again!
if self._connectToSocketServer():
self.timeout_add(5, self._connectToSocketServer)
#print("Connected!" if self.connected else "Did not connect.")
def reconnect(self):
self._sock.close()
self.connected = False
#GObject.source_remove(self._watch_id)
self.timeout_add(5, self._connectToSocketServer)
def timeout_add(time_secs, func):
time.sleep(time_secs)
func()
def sendCommand(self, cmd):
# print("Server command: " + cmd)
if self.connected:
try:
self._sock.send(cmd.encode() if python3 else cmd)
#print("Sended:", cmd)
except:
print("Sending failed.")
self.reconnect()
else:
print("Cannot send, not connected!")
def addListener(self, listener):
self._listeners.append(listener)
def _connectToSocketServer(self):
try:
self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock_file = os.path.join(get_runtime_dir(), appname, "socket")
try:
self._sock.connect(sock_file) # fails if sock_file doesn't exist
self.connected = True
#self._watch_id = GObject.io_add_watch(self._sock, GObject.IO_IN, self._handle_notify)
self.sendCommand('VERSION:\n')
self.sendCommand('GET_STRINGS:\n')
#print("Found socket at:", sock_file)
return False # Don't run again
except Exception as e:
#print("Could not connect to unix socket " + sock_file + ". " + str(e))
print("Nextcloud isn't running!")
exit()
except Exception as e: # Bad habbit
print("Connect could not be established, try again later.")
self._sock.close()
return True # Run again, if enabled via timeout_add()
# Reads data that becomes available.
# New responses can be accessed with get_available_responses().
# Returns false if no data was received within timeout
def read_socket_data_with_timeout(self, timeout):
self._sock.settimeout(timeout)
try:
self._remainder += self._sock.recv(1024)
except socket.timeout:
return False
else:
return True
finally:
self._sock.settimeout(None)
# Parses response lines out of collected data, returns list of strings
def get_available_responses(self):
end = self._remainder.rfind(b'\n')
if end == -1:
return []
data = self._remainder[:end]
self._remainder = self._remainder[end+1:]
data = data.decode() if python3 else data
return data.split('\n')
# Notify is the raw answer from the socket
def _handle_notify(self, source, condition):
# Blocking is ok since we're notified of available data
self._remainder += self._sock.recv(1024)
if len(self._remainder) == 0:
return False
for line in self.get_available_responses():
self.handle_server_response(line)
return True # Run again
def handle_server_response(self, line):
# print("Server response: " + line)
parts = line.split(':')
action = parts[0]
args = parts[1:]
for listener in self._listeners:
listener(action, args)
def _update_registered_paths(self, action, args):
if action == 'REGISTER_PATH':
self.registered_paths[args[0]] = 1
elif action == 'UNREGISTER_PATH':
del self.registered_paths[args[0]]
# Check if there are no paths left. If so, its usual
# that mirall went away. Try reconnecting.
if not self.registered_paths:
self.reconnect()
def _get_version(self, action, args):
if action == 'VERSION':
self.protocolVersion = args[1]
def translate_command(cmd):
"""
Translate command to human readable format.
Aim is to be as identical as possible to the outputs from 'dropbox status'.
"""
answers = { 'OK' : 'Up to date',
'SYNC' : 'Syncing..',
'NEW' : 'Syncing..',
'IGNORE' : 'WARNING..',
'ERROR' : 'ERROR..',
'OK+SWM' : 'Up to date',
'SYNC+SWM' : 'Syncing..',
'NEW+SWM' : 'Syncing..',
'IGNORE+SWM': 'WARNING..',
'ERROR+SWM' : 'ERROR..',
'NOP' : 'No operation (the nextcloud path could be wrong. Is '+nextcloud_path+' correct?)'
}
return answers[cmd]
def handle_commands(action, args):
#answer = args[0] # For debug only
#print("Action " + action + " -> got " + answer) # For debug only
if action == 'STATUS':
state = args[0]
print(translate_command(state))
#print("Exiting on purpose.")
exit()
socketConnect = SocketConnect()
socketConnect.addListener(handle_commands)
#print("handle notify..")
socketConnect.sendCommand("RETRIEVE_FOLDER_STATUS:"+nextcloud_path+"\n")
time.sleep(0.1)
socketConnect._handle_notify(None, None)
time.sleep(3)
print("No answer from socket.")