Files
atom/src/directory.coffee
Kevin Sawicki 371e31c786 🐎 Test for prefix without calling path.join
Directory::relativize is called many times by the fuzzy finder
and using path.join possibly multiple times per call was consuming
much of the time take to show the fuzzy finder view.
2014-01-08 17:55:08 -08:00

146 lines
4.6 KiB
CoffeeScript

path = require 'path'
async = require 'async'
{Emitter} = require 'emissary'
fs = require 'fs-plus'
pathWatcher = require 'pathwatcher'
File = require './file'
# Public: Represents a directory using {File}s
module.exports =
class Directory
Emitter.includeInto(this)
path: null
realPath: null
# Public: Configures a new Directory instance, no files are accessed.
#
# * path:
# A String containing the absolute path to the directory.
# + symlink:
# A Boolean indicating if the path is a symlink (defaults to false).
constructor: (@path, @symlink=false) ->
@on 'first-contents-changed-subscription-will-be-added', =>
# Triggered by emissary, when a new contents-changed listener attaches
@subscribeToNativeChangeEvents()
@on 'last-contents-changed-subscription-removed', =>
# Triggered by emissary, when the last contents-changed listener detaches
@unsubscribeFromNativeChangeEvents()
# Public: Returns the basename of the directory.
getBaseName: ->
path.basename(@path)
# Public: Returns the directory's symbolic path.
#
# This may include unfollowed symlinks or relative directory entries. Or it
# may be fully resolved, it depends on what you give it.
getPath: -> @path
# Public: Returns this directory's completely resolved path.
#
# All relative directory entries are removed and symlinks are resolved to
# their final destination.
getRealPathSync: ->
unless @realPath?
try
@realPath = fs.realpathSync(@path)
catch e
@realPath = @path
@realPath
# Public: Returns whether the given path (real or symbolic) is inside this
# directory.
contains: (pathToCheck) ->
return false unless pathToCheck
if pathToCheck.indexOf(path.join(@getPath(), path.sep)) is 0
true
else if pathToCheck.indexOf(path.join(@getRealPathSync(), path.sep)) is 0
true
else
false
# Public: Returns the relative path to the given path from this directory.
relativize: (fullPath) ->
return fullPath unless fullPath
# Normalize forward slashes to back slashes on windows
fullPath = fullPath.replace(/\//g, '\\') if process.platform is 'win32'
if fullPath is @getPath()
''
else if @isPathPrefixOf(@getPath(), fullPath)
fullPath.substring(@getPath().length + 1)
else if fullPath is @getRealPathSync()
''
else if @isPathPrefixOf(@getRealPathSync(), fullPath)
fullPath.substring(@getRealPathSync().length + 1)
else
fullPath
# Public: Reads file entries in this directory from disk synchronously.
#
# Returns an Array of {File} and {Directory} objects.
getEntriesSync: ->
directories = []
files = []
for entryPath in fs.listSync(@path)
if stat = fs.lstatSyncNoException(entryPath)
symlink = stat.isSymbolicLink()
stat = fs.statSyncNoException(entryPath) if symlink
continue unless stat
if stat.isDirectory()
directories.push(new Directory(entryPath, symlink))
else if stat.isFile()
files.push(new File(entryPath, symlink))
directories.concat(files)
# Public: Reads file entries in this directory from disk asynchronously.
#
# * callback: A function to call with an Error as the first argument and
# an Array of {File} and {Directory} objects as the second argument.
getEntries: (callback) ->
fs.list @path, (error, entries) ->
return callback(error) if error?
directories = []
files = []
addEntry = (entryPath, stat, symlink, callback) ->
if stat?.isDirectory()
directories.push(new Directory(entryPath, symlink))
else if stat?.isFile()
files.push(new File(entryPath, symlink))
callback()
statEntry = (entryPath, callback) ->
fs.lstat entryPath, (error, stat) ->
if stat?.isSymbolicLink()
fs.stat entryPath, (error, stat) ->
addEntry(entryPath, stat, true, callback)
else
addEntry(entryPath, stat, false, callback)
async.eachLimit entries, 1, statEntry, ->
callback(null, directories.concat(files))
# Private:
subscribeToNativeChangeEvents: ->
unless @watchSubscription?
@watchSubscription = pathWatcher.watch @path, (eventType) =>
@emit "contents-changed" if eventType is "change"
# Private:
unsubscribeFromNativeChangeEvents: ->
if @watchSubscription?
@watchSubscription.close()
@watchSubscription = null
# Private: Does given full path start with the given prefix?
isPathPrefixOf: (prefix, fullPath) ->
fullPath.indexOf(prefix) is 0 and fullPath[prefix.length] is path.sep