.AMCtoOwncloud.py

Script Python - Rémi G., 08/30/2017 01:20 pm

Download (11.9 kB)

 
1
#!/usr/bin/python3
2
#
3
# Nautilus script that sends AMC annotated sheets to Owncloud
4
# Copyright (C) 2017 Rémi GROLLEAU
5
#
6
# This program is free software: you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation, either version 3 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
19
20
import os
21
import csv
22
import re
23
import owncloud
24
import getpass
25
import requests # for owncloud behind SSO only
26
import lxml.html # for owncloud behind SSO only
27
28
29
### Parameters to tweak
30
31
32
CSV_FILE_PATH = '/home/username/students.csv'
33
OWNCLOUD_FOLDER = 'Interro Maths/'
34
35
OWNCLOUD_ADDRESS = 'http://ncloud.zaclys.com/'
36
OWNCLOUD_USERNAME = 'MyUserName'
37
38
39
### class and functions
40
41
42
class Student:
43
    """Student object"""
44
    
45
    def __init__(self,  name='', surname='', group='', number = '',
46
                 email='', owncloud='', quiz=''):
47
        """Define a Student and stores all the corresponding attributes.
48
        
49
        - name
50
        - surname
51
        - group (or class)
52
        - identification number
53
        - email
54
        - owncloud username
55
        - quiz path
56
        """
57
        self.name = name
58
        self.surname = surname
59
        self.group = group
60
        self.number = number
61
        self.email = email
62
        self.owncloud = owncloud
63
        self.quiz = quiz
64
65
66
    def print(self):
67
        """Display Student attributes."""
68
        print(' {0:15} {1:15} {2:5} {3:4} {4:40} {5:40} {6}'.format(
69
                                                             self.surname,
70
                                                             self.name,
71
                                                             self.group,
72
                                                             self.number,
73
                                                             self.email,
74
                                                             self.owncloud,
75
                                                             self.quiz))
76
77
78
def get_files_paths_from_nautilus():
79
    """Get paths of files selected in Nautilus file manager.
80
    
81
    Look also into selected folders (but not in subfolders).
82
    Returns a list of file paths that are not symbolic links.
83
    """ 
84
    # Retrieve paths selected in Nautilus
85
    if 'NAUTILUS_SCRIPT_SELECTED_FILE_PATHS' in os.environ:
86
        selection = os.environ['NAUTILUS_SCRIPT_SELECTED_FILE_PATHS']
87
88
    # Parse selection and folders and keep files which are not sym link
89
    list_of_files = []
90
    for path in selection.splitlines():
91
        # path is a file: keep if not sym link
92
        if os.path.isfile(path): 
93
            if not os.path.islink(path):
94
                list_of_files.append(path) 
95
        # path is a folder: parse it and keep if not sym link
96
        else: 
97
            for f in os.listdir(path):
98
                newpath = os.path.join(path,f)
99
                if os.path.isfile(newpath) and not os.path.islink(newpath):
100
                    list_of_files.append(newpath)
101
102
    print( '{0} files selected:'.format( len(list_of_files) ) )
103
    print( ' '+'\n '.join( list_of_files ) )
104
    return list_of_files
105
106
107
def get_students_from_csv(csv_file_path, verbose=False):
108
    """Extract student information from a CSV file.
109
    
110
    Returns a dictionary of all students (key = number, value = student object)
111
    """
112
    dict_of_students = {}
113
    with open(csv_file_path, newline='') as csv_file:
114
        tableau = csv.DictReader(csv_file, delimiter=':')
115
        for row in tableau: 
116
            student = Student(  name = row["name"], 
117
                                surname = row["surname"],
118
                                group = row["group"],
119
                                number = row["number"],
120
                                email = row["email"],
121
                                owncloud = row["owncloud"])
122
            dict_of_students[student.number] = student
123
    
124
    print( '\nFound {} students in {}.'.format(len(dict_of_students),
125
                                           csv_file_path
126
                                          ))
127
    if verbose:
128
        for student in dict_of_students.values():
129
            student.print()
130
    return dict_of_students
131
    
132
133
def associate_quiz_to_student(list_of_quiz, dict_of_students, verbose=False):
134
    """Associate each quiz to a student.
135
    
136
    Update quiz attribute of each student with the corresponding file path.
137
    Returns a list of students that matched quizzes.
138
    """ 
139
    matched_students = []
140
    unmatched_quiz = []
141
    
142
    for quiz_path in list_of_quiz:
143
        # extract the first number in the file name
144
        quiz_name = os.path.basename(quiz_path)
145
        regular_expression = re.compile('[0-9]+')
146
        student_number = re.search(regular_expression,quiz_name).group()
147
        try:
148
            # update the quiz attribute with file path
149
            dict_of_students[student_number].quiz = quiz_path
150
            matched_students.append(dict_of_students[student_number])
151
        except:
152
            # store unmatched files
153
            unmatched_quiz.append(quiz_path)                
154
    
155
    print('\n{}/{} quizzes matched'.format(len(matched_students),
156
                                        len(list_of_quiz)))
157
    if verbose:
158
        for student in matched_students:
159
            print(' {:15} {:15} n°{:3} {}'.format(student.surname,
160
                                                  student.name,
161
                                                  student.number,
162
                                                  student.quiz))
163
    if len(matched_students) != len(list_of_quiz):
164
        print('Unmatched file(s):')
165
        for quiz_path in unmatched_quiz:
166
            print(' {}'.format( quiz_path ))
167
        cancel = input('Do you want to continue? (y/n) ')
168
        if cancel.lower() == 'n':
169
            quit('\nScript cancelled !')
170
    return matched_students
171
172
173
def connect_owncloud(address, username, password=None):
174
    """Prompt for a password, connect to Owncloud.
175
    
176
    Return a Client object.
177
    """
178
    oc = owncloud.Client(address)
179
    if password == None:
180
        password = getpass.getpass('\nEnter Owncloud password: ')
181
    print('\nConnecting to Owncloud... ', end="")
182
    try:
183
        oc.login(username, password)
184
    except Exception as e:
185
        print('Error logging in! {}'.format(e))
186
        retry = input('Try again? (y/n) ')
187
        if retry.lower() == 'y':
188
            oc = connect_owncloud(address, username, password=None)
189
        else:
190
            quit()
191
            
192
    print('Connected !')
193
    return oc
194
    
195
196
def connect_owncloud_behind_sso(owncloud_address, username, password=None):
197
    """Prompt for a password, connect to Owncloud behind SSO (single sign on).
198
    
199
    Return a Client object.
200
    """
201
    # Get the hidden form fields needed to log in (CSRF token)
202
    s = requests.session()
203
    sso_address = s.get(owncloud_address).url # follow redirection
204
    login = s.get(sso_address)
205
    login_html = lxml.html.fromstring(login.text)
206
    hidden_inputs = login_html.xpath(r'//form//input[@type="hidden"]')
207
    form = {x.attrib["name"]: x.attrib["value"] for x in hidden_inputs}
208
    
209
    # Fill out the form with username and password
210
    if password == None:
211
        password = getpass.getpass('\nEnter Owncloud password: ')
212
        form['username'] = username
213
        form['password'] = password
214
        response = s.post(sso_address, data=form)
215
        print(response.url) # to check if it worked
216
        print('\nConnecting to Owncloud... ', end="")
217
        oc = owncloud.Client(owncloud_address)
218
        oc._session = s
219
        
220
    # Connect
221
    try:        
222
        oc._update_capabilities()
223
        print('Ok !')
224
    except Exception as e:
225
        print('Error logging in! {}'.format(e))
226
        retry = input('Try again? (y/n) ')
227
        if retry.lower() == 'y':
228
            oc = connect_owncloud_behind_sso(   owncloud_address,
229
                                                login, password=None)
230
        else:
231
            quit()
232
    
233
    # Hack, why does it work? https://github.com/owncloud/pyocclient/issues/204
234
    oc._session = requests.session()
235
    oc._session.verify = oc._verify_certs
236
    oc._session.auth = (username, password)
237
238
    return oc
239
240
241
def upload_and_share_quiz(owncloud_client, list_of_students, folder_base,
242
                          quiz_name = None):
243
    """ Create and share students remote folders if necessary and upload quizzes
244
    
245
    Create remote folders for each students (if not already there):
246
    "/folder_base/Group/Surname - Name (Number) - Interros Maths/"
247
    
248
    Upload students quizzes:
249
    "User input - Surname Name (Number).ext"
250
    
251
    Share folders with the corresponding students (if not already done).   
252
    """
253
    # Create remote folders if necessary 
254
    print('\nUploading files...')
255
    try:
256
        owncloud_client.mkdir(folder_base)
257
        print('Created folder ' + folder_base)
258
    except:
259
        pass
260
    for student in list_of_students: 
261
        folder_group = student.group + '/'
262
        folder_student = ( student.surname 
263
                        + ' '  + student.name 
264
                        + ' (' + student.number + ')'
265
                        + ' - Interros Maths/')
266
        remote_folder = folder_base + folder_group + folder_student
267
        try:
268
            owncloud_client.mkdir(folder_base + folder_group)
269
            print('Created folder ' + folder_base + folder_group)
270
        except:
271
            pass
272
        try:
273
            owncloud_client.mkdir(remote_folder)  
274
            print('Created folder ' + remote_folder)
275
        except:
276
            pass
277
        
278
        # Define remote quiz name et send
279
        if quiz_name == None:
280
            quiz_name = input('Enter quiz name: ')
281
        remote_quiz_name = ( quiz_name + ' - '
282
                            + student.surname 
283
                            + ' '  + student.name 
284
                            + ' (' + student.number + ')'
285
                            + '.'  + student.quiz.split(".")[-1]) # extension
286
        remote_quiz_path =  ( remote_folder
287
                            + remote_quiz_name)
288
        try:
289
            owncloud_client.put_file(remote_quiz_path, student.quiz)
290
            print('Sent file ' + remote_quiz_path)
291
        except:
292
            print("Can't send file " + remote_quiz_path)
293
            
294
        # Share folders if necessary
295
        is_shared = False
296
        for file_share in owncloud_client.get_shares(remote_folder):
297
            if file_share.get_share_with() == student.owncloud:
298
                is_shared = True
299
                break
300
        if is_shared == False:
301
            try:
302
                if '@' in student.owncloud: # remote user, bug in pyocclient 0.4
303
                    owncloud_client.share_file_with_user(   remote_folder, 
304
                                                            student.owncloud + '/', 
305
                                                            remote_user=True)
306
                else: # local user
307
                    owncloud_client.share_file_with_user(   remote_folder, 
308
                                                            student.owncloud)
309
                print( remote_folder + " shared with " + student.owncloud )
310
            except:
311
                print(  "Can't share folder " + remote_folder 
312
                        + " with " + student.owncloud)
313
314
### Script
315
316
list_of_quiz = get_files_paths_from_nautilus()
317
                
318
dict_of_students = get_students_from_csv(CSV_FILE_PATH, verbose=False)
319
320
list_of_students = associate_quiz_to_student(list_of_quiz, dict_of_students, verbose=True)
321
322
owncloud_client = connect_owncloud(OWNCLOUD_ADDRESS, OWNCLOUD_USERNAME)
323
#owncloud_client = connect_owncloud_behind_sso(OWNCLOUD_ADDRESS, OWNCLOUD_USERNAME)
324
325
upload_and_share_quiz(owncloud_client, list_of_students, OWNCLOUD_FOLDER)