~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to www/apps/fileservice/__init__.py

  • Committer: mattgiuca
  • Date: 2008-01-10 04:40:27 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:159
fileservice: Split growing module into three modules: top-level,
    listing and action. Listing and action are the well-separated two phases
    of the fileservice response.
    listing: Added a much more detailed report on the output format at the
    top.

Show diffs side-by-side

added added

removed removed

Lines of Context:
65
65
# response status. (404 File Not Found, 403 Forbidden, etc), and a header
66
66
# "X-IVLE-Return-Error: <errormessage>".
67
67
 
68
 
### Actions ###
69
 
 
70
 
# The most important argument is "action". This determines which action is
71
 
# taken. Note that action, and all other arguments, are ignored unless the
72
 
# request is a POST request. The other arguments depend upon the action.
73
 
# Note that paths are often specified as arguments. Paths that begin with a
74
 
# slash are taken relative to the user's home directory (the top-level
75
 
# directory seen when fileservice has no arguments or path). Paths without a
76
 
# slash are taken relative to the specified path.
77
 
 
78
 
# action=remove: Delete a file(s) or directory(s) (recursively).
79
 
#       path:   The path to the file or directory to delete. Can be specified
80
 
#               multiple times.
81
 
#
82
 
# action=move: Move or rename a file or directory.
83
 
#       from:   The path to the file or directory to be renamed.
84
 
#       to:     The path of the target filename. Error if the file already
85
 
#               exists.
86
 
#
87
 
# action=putfile: Upload a file to the student workspace.
88
 
#       path:   The path to the file to be written. If it exists, will
89
 
#               overwrite. Error if the target file is a directory.
90
 
#       data:   Bytes to be written to the file verbatim. May either be
91
 
#               a string variable or a file upload.
92
 
#
93
 
# Clipboard-based actions. Cut/copy/paste work in the same way as modern
94
 
# file browsers, by keeping a server-side clipboard of files that have been
95
 
# cut and copied. The clipboard is stored in the session data, so it persists
96
 
# across navigation, tabs and browser windows, but not across browser
97
 
# sessions.
98
 
99
 
# action=copy: Write file(s) to the session-based clipboard. Overrides any
100
 
#               existing clipboard data. Does not actually copy the file.
101
 
#               The files are physically copied when the clipboard is pasted.
102
 
#       path:   The path to the file or directory to copy. Can be specified
103
 
#               multiple times.
104
 
105
 
# action=cut: Write file(s) to the session-based clipboard. Overrides any
106
 
#               existing clipboard data. Does not actually move the file.
107
 
#               The files are physically moved when the clipboard is pasted.
108
 
#       path:   The path to the file or directory to cut. Can be specified
109
 
#               multiple times.
110
 
111
 
# action=paste: Copy or move the files stored in the clipboard. Clears the
112
 
#               clipboard. The files are copied or moved to a specified dir.
113
 
#       path:   The path to the DIRECTORY to paste the files to. Must not
114
 
#               be a file.
115
 
#
116
 
# Subversion actions.
117
 
# action=svnadd: Add an existing file(s) to version control.
118
 
#       path:   The path to the file to be added. Can be specified multiple
119
 
#               times.
120
 
#
121
 
# action=svnrevert: Revert a file(s) to its state as of the current revision
122
 
#               / undo local edits.
123
 
#       path:   The path to the file to be reverted. Can be specified multiple
124
 
#               times.
125
 
#
126
 
# action=svnupdate: Bring a file up to date with the head revision.
127
 
#       path:   The path to the file to be updated. Can be specified multiple
128
 
#               times.
129
 
#
130
 
# action=svncommit: Commit a file(s) or directory(s) to the repository.
131
 
#       path:   The path to the file or directory to be committed. Can be
132
 
#               specified multiple times. Directories are committed
133
 
#               recursively.
134
 
#       logmsg: Text of the log message. Optional. There is a default log
135
 
#               message if unspecified.
136
 
137
 
# TODO: Implement the following actions:
138
 
#   move, copy, cut, paste, svnadd, svnrevert, svnupdate, svncommit
 
68
# See action.py for a full description of the actions.
 
69
# See listing.py for a full description of the output format of the directory
 
70
# listing.
139
71
 
140
72
import os
141
73
import shutil
149
81
from common import (util, studpath)
150
82
import conf.mimetypes
151
83
 
152
 
DEFAULT_LOGMESSAGE = "No log message supplied."
 
84
import action, listing
153
85
 
154
86
# Make a Subversion client object
155
87
svnclient = pysvn.Client()
160
92
mime_dirlisting = "text/html"
161
93
#mime_dirlisting = "application/json"
162
94
 
163
 
class ActionError(Exception):
164
 
    """Represents an error processing an action. This can be
165
 
    raised by any of the action functions, and will be caught
166
 
    by the top-level handler, put into the HTTP response field,
167
 
    and continue.
168
 
 
169
 
    Important Security Consideration: The message passed to this
170
 
    exception will be relayed to the client.
171
 
    """
172
 
    pass
173
 
 
174
95
def handle(req):
175
96
    """Handler for the File Services application."""
176
97
 
180
101
    # Get all the arguments, if POST.
181
102
    # Ignore arguments if not POST, since we aren't allowed to cause
182
103
    # side-effects on the server.
183
 
    action = None
 
104
    act = None
184
105
    fields = None
185
106
    if req.method == 'POST':
186
107
        fields = req.get_fieldstorage()
187
 
        action = fields.getfirst('action')
 
108
        act = fields.getfirst('action')
188
109
 
189
 
    if action is not None:
 
110
    if act is not None:
190
111
        try:
191
 
            handle_action(req, action, fields)
192
 
        except ActionError, message:
 
112
            action.handle_action(req, svnclient, act, fields)
 
113
        except action.ActionError, message:
193
114
            req.headers_out['X-IVLE-Action-Error'] = str(message)
194
115
 
195
 
    handle_return(req)
196
 
 
197
 
def handle_action(req, action, fields):
198
 
    """Perform the "action" part of the response.
199
 
    This function should only be called if the response is a POST.
200
 
    This performs the action's side-effect on the server. If unsuccessful,
201
 
    writes the X-IVLE-Action-Error header to the request object. Otherwise,
202
 
    does not touch the request object. Does NOT write any bytes in response.
203
 
 
204
 
    May throw an ActionError. The caller should put this string into the
205
 
    X-IVLE-Action-Error header, and then continue normally.
206
 
 
207
 
    action: String, the action requested. Not sanitised.
208
 
    fields: FieldStorage object containing all arguments passed.
209
 
    """
210
 
    global actions_table        # Table of function objects
211
 
    try:
212
 
        action = actions_table[action]
213
 
    except KeyError:
214
 
        # Default, just send an error but then continue
215
 
        raise ActionError("Unknown action")
216
 
    action(req, fields)
217
 
 
218
 
def handle_return(req):
219
 
    """Perform the "return" part of the response.
220
 
    This function returns the file or directory listing contained in
221
 
    req.path. Sets the HTTP response code in req, writes additional headers,
222
 
    and writes the HTTP response, if any."""
223
 
 
224
 
    (user, path) = studpath.url_to_local(req.path)
225
 
 
226
 
    # FIXME: What to do about req.path == ""?
227
 
    # Currently goes to 403 Forbidden.
228
 
    if path is None:
229
 
        req.status = req.HTTP_FORBIDDEN
230
 
        req.headers_out['X-IVLE-Return-Error'] = 'Forbidden'
231
 
        req.write("Forbidden")
232
 
    elif not os.access(path, os.R_OK):
233
 
        req.status = req.HTTP_NOT_FOUND
234
 
        req.headers_out['X-IVLE-Return-Error'] = 'File not found'
235
 
        req.write("File not found")
236
 
    elif os.path.isdir(path):
237
 
        # It's a directory. Return the directory listing.
238
 
        req.content_type = mime_dirlisting
239
 
        req.headers_out['X-IVLE-Return'] = 'Dir'
240
 
        # Start by trying to do an SVN status, so we can report file version
241
 
        # status
242
 
        listing = {}
243
 
        try:
244
 
            status_list = svnclient.status(path, recurse=False, get_all=True,
245
 
                            update=False)
246
 
            for status in status_list:
247
 
                filename, attrs = PysvnStatus_to_fileinfo(path, status)
248
 
                listing[filename] = attrs
249
 
        except pysvn.ClientError:
250
 
            # Presumably the directory is not under version control.
251
 
            # Fallback to just an OS file listing.
252
 
            for filename in os.listdir(path):
253
 
                listing[filename] = file_to_fileinfo(path, filename)
254
 
            # The subversion one includes "." while the OS one does not.
255
 
            # Add "." to the output, so the caller can see we are
256
 
            # unversioned.
257
 
            listing["."] = {"isdir" : True,
258
 
                "mtime" : time.ctime(os.path.getmtime(path))}
259
 
 
260
 
        req.write(cjson.encode(listing))
261
 
    else:
262
 
        # It's a file. Return the file contents.
263
 
        # First get the mime type of this file
264
 
        # (Note that importing common.util has already initialised mime types)
265
 
        (type, _) = mimetypes.guess_type(path)
266
 
        if type is None:
267
 
            type = conf.mimetypes.default_mimetype
268
 
        req.content_type = type
269
 
        req.headers_out['X-IVLE-Return'] = 'File'
270
 
 
271
 
        req.sendfile(path)
272
 
 
273
 
def file_to_fileinfo(path, filename):
274
 
    """Given a filename (relative to a given path), gets all the info "ls"
275
 
    needs to display about the filename. Returns a dict containing a number
276
 
    of fields related to the file (excluding the filename itself)."""
277
 
    fullpath = os.path.join(path, filename)
278
 
    d = {}
279
 
    file_stat = os.stat(fullpath)
280
 
    if stat.S_ISDIR(file_stat.st_mode):
281
 
        d["isdir"] = True
282
 
    else:
283
 
        d["isdir"] = False
284
 
        d["size"] = file_stat.st_size
285
 
        (type, _) = mimetypes.guess_type(filename)
286
 
        if type is None:
287
 
            type = conf.mimetypes.default_mimetype
288
 
        d["type"] = type
289
 
    d["mtime"] = time.ctime(file_stat.st_mtime)
290
 
    return d
291
 
 
292
 
def PysvnStatus_to_fileinfo(path, status):
293
 
    """Given a PysvnStatus object, gets all the info "ls"
294
 
    needs to display about the filename. Returns a pair mapping filename to
295
 
    a dict containing a number of other fields."""
296
 
    path = os.path.normcase(path)
297
 
    fullpath = status.path
298
 
    # If this is "." (the directory itself)
299
 
    if path == os.path.normcase(fullpath):
300
 
        # If this directory is unversioned, then we aren't
301
 
        # looking at any interesting files, so throw
302
 
        # an exception and default to normal OS-based listing. 
303
 
        if status.text_status == pysvn.wc_status_kind.unversioned:
304
 
            raise pysvn.ClientError
305
 
        # We actually want to return "." because we want its
306
 
        # subversion status.
307
 
        filename = "."
308
 
    else:
309
 
        filename = os.path.basename(fullpath)
310
 
    d = {}
311
 
    text_status = status.text_status
312
 
    d["svnstatus"] = str(text_status)
313
 
    try:
314
 
        file_stat = os.stat(fullpath)
315
 
        if stat.S_ISDIR(file_stat.st_mode):
316
 
            d["isdir"] = True
317
 
        else:
318
 
            d["isdir"] = False
319
 
            d["size"] = file_stat.st_size
320
 
            (type, _) = mimetypes.guess_type(fullpath)
321
 
            if type is None:
322
 
                type = conf.mimetypes.default_mimetype
323
 
            d["type"] = type
324
 
        d["mtime"] = time.ctime(file_stat.st_mtime)
325
 
    except OSError:
326
 
        # Here if, eg, the file is missing.
327
 
        # Can't get any more information so just return d
328
 
        pass
329
 
    return filename, d
330
 
 
331
 
### ACTIONS ###
332
 
 
333
 
def actionpath_to_local(req, path):
334
 
    """Determines the local path upon which an action is intended to act.
335
 
    Note that fileservice actions accept two paths: the request path,
336
 
    and the "path" argument given to the action.
337
 
    According to the rules, if the "path" argument begins with a '/' it is
338
 
    relative to the user's home; if it does not, it is relative to the
339
 
    supplied path.
340
 
 
341
 
    This resolves the path, given the request and path argument.
342
 
 
343
 
    May raise an ActionError("Invalid path"). The caller is expected to
344
 
    let this fall through to the top-level handler, where it will be
345
 
    put into the HTTP response field. Never returns None.
346
 
 
347
 
    Does not mutate req.
348
 
    """
349
 
    if path is None:
350
 
        path = req.path
351
 
    elif len(path) > 0 and path[0] == os.sep:
352
 
        # Relative to student home
353
 
        path = path[1:]
354
 
    else:
355
 
        # Relative to req.path
356
 
        path = os.path.join(req.path, path)
357
 
 
358
 
    _, r = studpath.url_to_local(path)
359
 
    if r is None:
360
 
        raise ActionError("Invalid path")
361
 
    return r
362
 
 
363
 
def action_remove(req, fields):
364
 
    # TODO: Do an SVN rm if the file is versioned.
365
 
    # TODO: Disallow removal of student's home directory
366
 
    """Removes a list of files or directories.
367
 
 
368
 
    Reads fields: 'path' (multiple)
369
 
    """
370
 
    paths = fields.getlist('path')
371
 
    goterror = False
372
 
    for path in paths:
373
 
        path = actionpath_to_local(req, path)
374
 
        try:
375
 
            if os.path.isdir(path):
376
 
                shutil.rmtree(path)
377
 
            else:
378
 
                os.remove(path)
379
 
        except OSError:
380
 
            goterror = True
381
 
        except shutil.Error:
382
 
            goterror = True
383
 
    if goterror:
384
 
        if len(paths) == 1:
385
 
            raise ActionError("Could not delete the file specified")
386
 
        else:
387
 
            raise ActionError(
388
 
                "Could not delete one or more of the files specified")
389
 
 
390
 
def action_putfile(req, fields):
391
 
    """Writes data to a file, overwriting it if it exists and creating it if
392
 
    it doesn't.
393
 
 
394
 
    Reads fields: 'path', 'data' (file upload)
395
 
    """
396
 
    path = fields.getfirst('path')
397
 
    data = fields.getfirst('data')
398
 
    if path is None: raise ActionError("No path specified")
399
 
    if data is None: raise ActionError("No data specified")
400
 
    path = actionpath_to_local(req, path)
401
 
    data = data.file
402
 
 
403
 
    # Copy the contents of file object 'data' to the path 'path'
404
 
    try:
405
 
        dest = open(path, 'wb')
406
 
        shutil.copyfileobj(data, dest)
407
 
    except OSError:
408
 
        raise ActionError("Could not write to target file")
409
 
 
410
 
# Table of all action functions #
411
 
 
412
 
actions_table = {
413
 
    "remove" : action_remove,
414
 
    "putfile" : action_putfile,
415
 
}
 
116
    listing.handle_return(req, svnclient)