 
     # Update the listing of files in the current_path, or display of the current file.
     update_file_list_tab: (no_focus) =>
-        if @_updating_file_list_tab_LOCK
-            return # already updating it
-        @_updating_file_list_tab_LOCK = true
         @_update_file_list_tab no_focus, () =>
             @_show_all_files = false
-            setTimeout( (() => @_updating_file_list_tab_LOCK = false), 500 )
 
     init_listing_show_all: () =>
         @container.find(".project-file-listing-show_all").click () =>

FROM_WEB_TIMEOUT_S = 45


##################################################
# Initialize the modal project management dialogs
##################################################
delete_path_dialog = $("#project-delete-path-dialog")
move_path_dialog   = $("#project-move-path-dialog")

class Dialog
    constructor: (opts) ->
        opts = defaults opts,
            dialog      : required
            submit      : required
            before_show : undefined
            after_show  : undefined

        @opts = opts

        submit = () =>
            try
                opts.dialog.modal('hide')
                opts.submit(opts.dialog, @project)
            catch e
                console.log("Exception submitting modal: ", e)
            return false

        opts.dialog.submit submit
        opts.dialog.find("form").submit submit
        opts.dialog.find(".btn-submit").click submit
        opts.dialog.find(".btn-close").click(() -> opts.dialog.modal('hide'); return false)

    show: (project) =>
        @project = project
        @opts.before_show(@opts.dialog, project)
        @opts.dialog.modal()
        @opts.after_show(@opts.dialog, project)
        return false

delete_path_dialog = new Dialog
    dialog      : $("#project-delete-path-dialog")
    submit      : (dialog, project) ->
        path = project.current_path.join('/')
        commit_mesg = dialog.find("input[type=text]").val()
        if commit_mesg == ""
            commit_mesg = "deleted #{path}"
        project.path_action
            action      : 'delete'
            branch      : project.meta.display_branch
            path        : path
            commit_mesg : commit_mesg

    before_show : (dialog, project) ->
        dialog.find(".project-delete-path-dialog-filename").text(project.current_pathname())
        dialog.find("input[type=text]").val("")
    after_show  : (dialog) ->
        dialog.find("input[type=text]").focus()

move_path_dialog = new Dialog
    dialog      : $("#project-move-path-dialog")
    submit      : (dialog, project) ->
        src      = project.current_pathname()
        dest     = dialog.find("input[name=new-filename]").val()
        if src == dest
            # nothing to do
            return
        why      = dialog.find("input[name=why]").val()
        if why == ""
            why = "move #{src} to #{dest}"
        project.path_action
            action      : 'move'
            branch      : project.meta.display_branch
            path        : src
            commit_mesg : why
            extra_options : {dest:dest}
    before_show : (dialog, project) ->
        dialog.find(".project-move-path-dialog-filename").text(project.current_pathname())
        dialog.find("input[name=new-filename]").val(project.current_pathname())
        dialog.find("input[name=why]").val("")
    after_show  : (dialog) ->
        dialog.find("input[name=new-filename]").focus()



##################################################
# Define the project page class

            return false

    _update_file_list_tab: (no_focus, cb) =>

        path = @current_path.join('/')
        if path == @_requested_path
            # already requested
            return

        if not @_requested_path?
            spinner = @container.find(".project-file-listing-spinner")
            @_file_list_tab_spinner_timer = setTimeout( (() -> spinner.show().spin()), 1000 )

        if @public_access
            f = salvus_client.public_project_directory_listing
        else
            f = salvus_client.project_directory_listing

        @_requested_path = path

        f
            project_id : @project.project_id
            path       : path
            time       : @_sort_by_time
            hidden     : @container.find("a[href=#hide-hidden]").is(":visible")
            timeout    : 20
            cb         : (err, listing) =>
                if path != @_requested_path
                    # requested another path after this one, so ignore
                    # this now useless listing
                    cb?()
                    return
                delete @_requested_path

                clearTimeout(@_file_list_tab_spinner_timer)
                @container.find(".project-file-listing-spinner").spin(false).hide()

                if err
                    alert_message(type:"error", message:"unable to show listing for #{path} -- #{err}")
                    cb?(err)
                else
                    @render_file_listing
                        path     : path
                        listing  : listing
                        no_focus : no_focus
                        cb       : cb
                    @update_snapshot_link()

    render_file_listing: (opts) =>
        {path, listing, no_focus, cb} = defaults opts,
            path     : required     # directory we are rendering the listing for
            listing  : required     # the listing data
            no_focus : false
            cb       : undefined

        url_path = path
        if url_path.length > 0 and url_path[path.length-1] != '/'
            url_path += '/'

        if @current_tab.name == "project-file-listing"
            @push_state('files/' + url_path)

        # Update the display of the path above the listing or file preview
        @set_current_path(path)
        @update_current_path()

        # If the files haven't changed -- a VERY common case -- don't rebuild the whole listing.
        files = misc.to_json(listing)  # use json to deep compare -- e.g., file size matters!
        if @_update_file_list_tab_last_path == path and @_update_file_list_tab_last_path_files == files and @_update_file_sort_by_time == @_sort_by_time and @_last_show_all_files == @_show_all_files
            cb?()
            return
        else
            @_update_file_list_tab_last_path       = path
            @_update_file_list_tab_last_path_files = files
            @_update_file_sort_by_time             = @_sort_by_time
            @_last_show_all_files                  = @_show_all_files

        @_last_listing = listing

        if @current_path[0] == '.trash'
            @container.find("a[href=#empty-trash]").show()
            @container.find("a[href=#trash]").hide()
        else
            @container.find("a[href=#empty-trash]").hide()
            @container.find("a[href=#trash]").show()

        # Now rendering the listing or file preview
        file_or_listing = @container.find(".project-file-listing-file-list")

        # TODO: for long listings this file_or_listing.empty() dominates.
        # We should just change data/displayed names of entries or something and hide others -- be way more clever. For LATER.
        file_or_listing.empty()

        directory_is_empty = true

        @container.find(".project-file-tools a").removeClass("disabled")

        # Hide the edit button
        @container.find(".project-file-tools a[href=#edit]").addClass("disabled")

        # Hide the move and delete buttons if and only if this is the top level path
        if path == ""
            @container.find(".project-file-tools a[href=#move]").addClass("disabled")
            @container.find(".project-file-tools a[href=#delete]").addClass("disabled")

        click_file = (e) =>
            obj = $(e.delegateTarget).closest(".project-path-link").data('obj')
            target = $(e.target)
            if target.hasClass("salvus-file-action") or target.parent().hasClass('salvus-file-action')
                @file_action_dialog(obj)
            else
                if obj.isdir
                    @set_current_path(obj.fullname)
                    @update_file_list_tab()
                else
                    @open_file
                        path       : obj.fullname
                        foreground : not(e.which==2 or (e.ctrlKey or e.metaKey))
            e.preventDefault()

        # TODO: not used
        ###
        that = @
        file_dropped_on_directory = (event, ui) ->
            src = ui.draggable.data('name')
            if not src?
                return
            dest = $(@).data('name')
            that.move_file
                src  : src
                dest : dest
                cb   : (err) =>
                    if not err
                        that.update_file_list_tab(true)
        ###

        if @current_path.length > 0
            # Create special link to the parent directory
            t = template_project_file.clone()
            t.find("a[href=#file-action]").hide()
            parent = @current_path.slice(0, @current_path.length-1).join('/')
            if parent == ""
                parent = "."
            t.data('name', parent)
            t.find(".project-file-name").html("Parent Directory")
            t.find(".project-file-icon").removeClass("fa-file").addClass('fa-reply')
            t.find("input").hide()  # hide checkbox, etc.
            # Clicking to open the directory
            t.click () =>
                @current_path.pop()
                @update_file_list_tab()
                return false
            #t.droppable(drop:file_dropped_on_directory, scope:'files')
            t.find("a").tooltip
                trigger : 'hover'
                delay   : { show: 500, hide: 100 }
            t.find(".fa-arrows").tooltip
                trigger : 'hover'
                delay   : { show: 500, hide: 100 }

            file_or_listing.append(t)



        #console.log("done updating misc stuff", misc.walltime(tm))

        # Show the files
        #console.log("building listing for #{path}...")

        tm = misc.walltime()

        masked_file_exts_bad  = (key for key of masked_file_exts)
        masked_file_exts_good = (value for key, value of masked_file_exts)
        masked_file_bad_index = []
        masked_file_good_name = []
        n = 0
        @container.find(".project-file-listing-show_all").hide().find('span').text('')
        search = @_file_search_box.val()
        for obj, i in listing.files
            if not search and (not @_show_all_files and n >= MAX_FILE_LISTING_SIZE)
                @container.find(".project-file-listing-show_all").show().find('span').text(listing.files.length - n)
                break
            n += 1
            t = template_project_file.clone()
            t.data(obj:obj)
            if obj.isdir
                t.find(".project-file-name").text(obj.name)
                date = undefined
                if path == ".snapshots/master" and obj.name.length == '2014-04-04-061502'.length
                    date = misc.parse_bup_timestamp(obj.name)
                    t.find(".project-file-name").text(date)
                else if obj.mtime
                    date = new Date(obj.mtime*1000)
                if date?
                    t.find(".project-file-last-mod-date").attr('title', date.toISOString()).timeago()
                name = obj.name
                t.find(".project-file-icon").removeClass("fa-file").addClass("fa-folder-open-o").css('font-size':'21pt')
            else
                if obj.name.indexOf('.') != -1
                    ext = filename_extension(obj.name)
                    name = obj.name.slice(0,obj.name.length - ext.length - 1)
                else
                    ext = ''
                    name = obj.name
                t.find(".project-file-name").text(name)
                if ext != ''
                    if ext in masked_file_exts_bad
                        masked_file_bad_index.push(i)
                    if ext in masked_file_exts_good
                        masked_file_good_name.push(obj.name)
                    t.find(".project-file-name-extension").text('.' + ext)
                    if file_associations[ext]? and file_associations[ext].icon?
                        t.find(".project-file-icon").removeClass("fa-file").addClass(file_associations[ext].icon)
                if obj.mtime?
                    date = (new Date(obj.mtime*1000)).toISOString()
                    t.find(".project-file-last-mod-date").attr('title', date).timeago()
                if obj.size?
                    t.find(".project-file-size").text(human_readable_size(obj.size))
                if obj.commit?.date?
                    date = (new Date(obj.commit.date*1000)).toISOString()
                    t.find(".project-file-last-commit-date").attr('title', date).timeago()
                else
                    t.find(".project-file-last-commit-date-container").hide()
                if obj.commit?.message?
                    t.find(".project-file-last-commit-message").text(trunc(obj.commit.message, 70))
            #end if

            obj.fullname = if path != "" then path + '/' + obj.name else obj.name
            directory_is_empty = false
            # Add our new listing entry to the list:
            file_or_listing.append(t)
            t.click(click_file)

            continue
            ###
            # Define file actions using a closure
            @_init_listing_actions(t, path, obj.name, obj.fullname, obj.isdir? and obj.isdir, obj.snapshot?)

            # Drag handle for moving files via drag and drop.
            handle = t.find(".project-file-drag-handle")
            handle.click () =>
                # do not want clicking on the handle to open the file.
                return false
            t.draggable
                handle         : handle
                zIndex         : 100
                opacity        : 0.75
                revertDuration : 200
                revert         : "invalid"
                axis           : 'y'
                scope          : 'files'

            t.find("a").tooltip
                trigger : 'hover'
                delay   : { show: 500, hide: 100 }
            t.find(".fa-arrows").tooltip
                trigger : 'hover'
                delay   : { show: 500, hide: 100 }
            ###

        # Masks (greys out) files that the user probably doesn't want to open
        if account.account_settings.settings.other_settings.mask_files
            # mask compiled files corresponding to source files
            for index in masked_file_bad_index
                filename = listing.files[index].name
                ext = filename_extension(filename)
                name = filename.slice(0, filename.length - ext.length - 1)
                compare_name = name

                # TODO: other unusual cases may need to be added here
                if ext == 'gz'
                    second_extension = 'synctex'
                    if filename_extension(name) == second_extension
                        compare_name = name.slice(0, name.length - second_extension.length - 1)
                    else
                        continue

                good_name_index = masked_file_good_name.indexOf(compare_name + "." + masked_file_exts[ext])
                if good_name_index != -1
                    # mask the matched file
                    i = if path == "" then index else index + 1 # skip over 'Parent Directory' link
                    $(@container.find(".project-file-listing-file-list").children()[i]).addClass("project-file-listing-masked-file")

            # mask files starting with a '.'
            for file, index in listing.files
                if file.name.indexOf('.') == 0
                    i = if path == "" then index else index + 1 # skip over 'Parent Directory' link
                    $(@container.find(".project-file-listing-file-list").children()[i]).addClass("project-file-listing-masked-file")

        #@clear_file_search()
        #console.log("done building listing in #{misc.walltime(tm)}")
        tm = misc.walltime()
        @update_file_search()
        #console.log("done building file search #{misc.walltime(tm)}")
        tm = misc.walltime()

        # No files
        if directory_is_empty and path != ".trash" and path.slice(0,10) != ".snapshots"
            @container.find(".project-file-listing-no-files").show()
        else
            @container.find(".project-file-listing-no-files").hide()

        if path.slice(0,10) == '.snapshots'
            @container.find(".project-file-listing-snapshot-warning").show()
        else
            @container.find(".project-file-listing-snapshot-warning").hide()

        if no_focus? and no_focus
            cb?(); return

        @focus_file_search()
        #console.log("done with everything #{misc.walltime(tm)}")

        cb?()

    _init_listing_actions: (t, path, name, fullname, isdir, is_snapshot) =>
        if not fullname?
