mirror of
https://github.com/atom/atom.git
synced 2026-01-23 05:48:10 -05:00
Require File from pathwatcher
This commit is contained in:
@@ -1,146 +0,0 @@
|
||||
path = require 'path'
|
||||
|
||||
async = require 'async'
|
||||
{Emitter} = require 'emissary'
|
||||
fs = require 'fs-plus'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
|
||||
File = require './file'
|
||||
|
||||
# Public: Represents a directory on disk.
|
||||
#
|
||||
# ## Requiring in packages
|
||||
#
|
||||
# ```coffee
|
||||
# {Directory} = require 'atom'
|
||||
# ```
|
||||
module.exports =
|
||||
class Directory
|
||||
Emitter.includeInto(this)
|
||||
|
||||
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 (default: 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 {String} 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 1st argument and
|
||||
# an {Array} of {File} and {Directory} objects as the 2nd 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))
|
||||
|
||||
subscribeToNativeChangeEvents: ->
|
||||
unless @watchSubscription?
|
||||
@watchSubscription = pathWatcher.watch @path, (eventType) =>
|
||||
@emit "contents-changed" if eventType is "change"
|
||||
|
||||
unsubscribeFromNativeChangeEvents: ->
|
||||
if @watchSubscription?
|
||||
@watchSubscription.close()
|
||||
@watchSubscription = null
|
||||
|
||||
# Does given full path start with the given prefix?
|
||||
isPathPrefixOf: (prefix, fullPath) ->
|
||||
fullPath.indexOf(prefix) is 0 and fullPath[prefix.length] is path.sep
|
||||
172
src/file.coffee
172
src/file.coffee
@@ -1,172 +0,0 @@
|
||||
crypto = require 'crypto'
|
||||
path = require 'path'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
Q = require 'q'
|
||||
{Emitter} = require 'emissary'
|
||||
_ = require 'underscore-plus'
|
||||
fs = require 'fs-plus'
|
||||
runas = require 'runas'
|
||||
|
||||
# Public: Represents an individual file.
|
||||
#
|
||||
# You should probably create a {Directory} and access the {File} objects that
|
||||
# it creates, rather than instantiating the {File} class directly.
|
||||
#
|
||||
# ## Requiring in packages
|
||||
#
|
||||
# ```coffee
|
||||
# {File} = require 'atom'
|
||||
# ```
|
||||
module.exports =
|
||||
class File
|
||||
Emitter.includeInto(this)
|
||||
|
||||
path: null
|
||||
cachedContents: null
|
||||
|
||||
# Public: Creates a new file.
|
||||
#
|
||||
# path - A {String} containing the absolute path to the file
|
||||
# symlink - A {Boolean} indicating if the path is a symlink (default: false).
|
||||
constructor: (@path, @symlink=false) ->
|
||||
throw new Error("#{@path} is a directory") if fs.isDirectorySync(@path)
|
||||
|
||||
@handleEventSubscriptions()
|
||||
|
||||
# Subscribes to file system notifications when necessary.
|
||||
handleEventSubscriptions: ->
|
||||
eventNames = ['contents-changed', 'moved', 'removed']
|
||||
|
||||
subscriptionsAdded = eventNames.map (eventName) -> "first-#{eventName}-subscription-will-be-added"
|
||||
@on subscriptionsAdded.join(' '), =>
|
||||
# Only subscribe when a listener of eventName attaches (triggered by emissary)
|
||||
@subscribeToNativeChangeEvents() if @exists()
|
||||
|
||||
subscriptionsRemoved = eventNames.map (eventName) -> "last-#{eventName}-subscription-removed"
|
||||
@on subscriptionsRemoved.join(' '), =>
|
||||
# Detach when the last listener of eventName detaches (triggered by emissary)
|
||||
subscriptionsEmpty = _.every eventNames, (eventName) => @getSubscriptionCount(eventName) is 0
|
||||
@unsubscribeFromNativeChangeEvents() if subscriptionsEmpty
|
||||
|
||||
# Sets the path for the file.
|
||||
setPath: (@path) ->
|
||||
|
||||
# Public: Returns the {String} path for the file.
|
||||
getPath: -> @path
|
||||
|
||||
# Public: Return the {String} filename without any directory information.
|
||||
getBaseName: ->
|
||||
path.basename(@path)
|
||||
|
||||
# Public: Overwrites the file with the given String.
|
||||
write: (text) ->
|
||||
previouslyExisted = @exists()
|
||||
@writeFileWithPrivilegeEscalationSync(@getPath(), text)
|
||||
@cachedContents = text
|
||||
@subscribeToNativeChangeEvents() if not previouslyExisted and @hasSubscriptions()
|
||||
|
||||
# Deprecated
|
||||
readSync: (flushCache) ->
|
||||
if not @exists()
|
||||
@cachedContents = null
|
||||
else if not @cachedContents? or flushCache
|
||||
@cachedContents = fs.readFileSync(@getPath(), 'utf8')
|
||||
else
|
||||
@cachedContents
|
||||
|
||||
@setDigest(@cachedContents)
|
||||
@cachedContents
|
||||
|
||||
# Public: Reads the contents of the file.
|
||||
#
|
||||
# flushCache - A {Boolean} indicating whether to require a direct read or if
|
||||
# a cached copy is acceptable.
|
||||
#
|
||||
# Returns a promise that resovles to a String.
|
||||
read: (flushCache) ->
|
||||
if not @exists()
|
||||
promise = Q(null)
|
||||
else if not @cachedContents? or flushCache
|
||||
if fs.getSizeSync(@getPath()) >= 1048576 # 1MB
|
||||
throw new Error("Atom can only handle files < 1MB, for now.")
|
||||
|
||||
deferred = Q.defer()
|
||||
promise = deferred.promise
|
||||
content = []
|
||||
bytesRead = 0
|
||||
readStream = fs.createReadStream @getPath(), encoding: 'utf8'
|
||||
readStream.on 'data', (chunk) ->
|
||||
content.push(chunk)
|
||||
bytesRead += chunk.length
|
||||
deferred.notify(bytesRead)
|
||||
|
||||
readStream.on 'end', ->
|
||||
deferred.resolve(content.join(''))
|
||||
|
||||
readStream.on 'error', (error) ->
|
||||
deferred.reject(error)
|
||||
else
|
||||
promise = Q(@cachedContents)
|
||||
|
||||
promise.then (contents) =>
|
||||
@setDigest(contents)
|
||||
@cachedContents = contents
|
||||
|
||||
# Public: Returns whether the file exists.
|
||||
exists: ->
|
||||
fs.existsSync(@getPath())
|
||||
|
||||
setDigest: (contents) ->
|
||||
@digest = crypto.createHash('sha1').update(contents ? '').digest('hex')
|
||||
|
||||
# Public: Get the SHA-1 digest of this file
|
||||
getDigest: ->
|
||||
@digest ? @setDigest(@readSync())
|
||||
|
||||
# Writes the text to specified path.
|
||||
#
|
||||
# Privilege escalation would be asked when current user doesn't have
|
||||
# permission to the path.
|
||||
writeFileWithPrivilegeEscalationSync: (path, text) ->
|
||||
try
|
||||
fs.writeFileSync(path, text)
|
||||
catch error
|
||||
if error.code is 'EACCES' and process.platform is 'darwin'
|
||||
authopen = '/usr/libexec/authopen' # man 1 authopen
|
||||
unless runas(authopen, ['-w', '-c', path], stdin: text) is 0
|
||||
throw error
|
||||
else
|
||||
throw error
|
||||
|
||||
handleNativeChangeEvent: (eventType, path) ->
|
||||
if eventType is "delete"
|
||||
@unsubscribeFromNativeChangeEvents()
|
||||
@detectResurrectionAfterDelay()
|
||||
else if eventType is "rename"
|
||||
@setPath(path)
|
||||
@emit "moved"
|
||||
else if eventType is "change"
|
||||
oldContents = @cachedContents
|
||||
@read(true).done (newContents) =>
|
||||
@emit 'contents-changed' unless oldContents == newContents
|
||||
|
||||
detectResurrectionAfterDelay: ->
|
||||
_.delay (=> @detectResurrection()), 50
|
||||
|
||||
detectResurrection: ->
|
||||
if @exists()
|
||||
@subscribeToNativeChangeEvents()
|
||||
@handleNativeChangeEvent("change", @getPath())
|
||||
else
|
||||
@cachedContents = null
|
||||
@emit "removed"
|
||||
|
||||
subscribeToNativeChangeEvents: ->
|
||||
unless @watchSubscription?
|
||||
@watchSubscription = pathWatcher.watch @path, (eventType, path) =>
|
||||
@handleNativeChangeEvent(eventType, path)
|
||||
|
||||
unsubscribeFromNativeChangeEvents: ->
|
||||
if @watchSubscription?
|
||||
@watchSubscription.close()
|
||||
@watchSubscription = null
|
||||
@@ -4,7 +4,7 @@ fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
CSON = require 'season'
|
||||
KeyBinding = require './key-binding'
|
||||
File = require './file'
|
||||
{File} = require 'pathwatcher'
|
||||
{Emitter} = require 'emissary'
|
||||
|
||||
Modifiers = ['alt', 'control', 'ctrl', 'shift', 'cmd']
|
||||
|
||||
@@ -5,8 +5,7 @@ Serializable = require 'serializable'
|
||||
TextBufferCore = require 'text-buffer'
|
||||
{Point, Range} = TextBufferCore
|
||||
{Subscriber, Emitter} = require 'emissary'
|
||||
|
||||
File = require './file'
|
||||
{File} = require 'pathwatcher'
|
||||
|
||||
# Represents the contents of a file.
|
||||
#
|
||||
|
||||
@@ -7,7 +7,7 @@ Q = require 'q'
|
||||
|
||||
{$} = require './space-pen-extensions'
|
||||
Package = require './package'
|
||||
File = require './file'
|
||||
{File} = require 'pathwatcher'
|
||||
|
||||
# Public: Handles loading and activating available themes.
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user