diff --git a/src/directory.coffee b/src/directory.coffee deleted file mode 100644 index 790de62f6..000000000 --- a/src/directory.coffee +++ /dev/null @@ -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 diff --git a/src/file.coffee b/src/file.coffee deleted file mode 100644 index b4fffe060..000000000 --- a/src/file.coffee +++ /dev/null @@ -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 diff --git a/src/keymap.coffee b/src/keymap.coffee index 8fccf060d..98b4a89ad 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -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'] diff --git a/src/text-buffer.coffee b/src/text-buffer.coffee index cd7102cb8..e41a792e2 100644 --- a/src/text-buffer.coffee +++ b/src/text-buffer.coffee @@ -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. # diff --git a/src/theme-manager.coffee b/src/theme-manager.coffee index eb4c5a3b3..80204905a 100644 --- a/src/theme-manager.coffee +++ b/src/theme-manager.coffee @@ -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. #