Last active
June 8, 2017 00:06
-
-
Save dunstontc/289527e0be180228c27b303d6edc2b0e to your computer and use it in GitHub Desktop.
atom & the fs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** @babel */ | |
const fs = require('fs-plus') | |
const path = require('path') | |
const hasWriteAccess = (dir) => { | |
const testFilePath = path.join(dir, 'write.test') | |
try { | |
fs.writeFileSync(testFilePath, new Date().toISOString(), { flag: 'w+' }) | |
fs.unlinkSync(testFilePath) | |
return true | |
} catch (err) { | |
return false | |
} | |
} | |
const getAppDirectory = () => { | |
switch (process.platform) { | |
case 'darwin': | |
return process.execPath.substring(0, process.execPath.indexOf('.app') + 4) | |
case 'linux': | |
case 'win32': | |
return path.join(process.execPath, '..') | |
} | |
} | |
module.exports = { | |
setAtomHome: (homePath) => { | |
// When a read-writeable .atom folder exists above app use that | |
const portableHomePath = path.join(getAppDirectory(), '..', '.atom') | |
if (fs.existsSync(portableHomePath)) { | |
if (hasWriteAccess(portableHomePath)) { | |
process.env.ATOM_HOME = portableHomePath | |
} else { | |
// A path exists so it was intended to be used but we didn't have rights, so warn. | |
console.log(`Insufficient permission to portable Atom home "${portableHomePath}".`) | |
} | |
} | |
// Check ATOM_HOME environment variable next | |
if (process.env.ATOM_HOME !== undefined) { | |
return | |
} | |
// Fall back to default .atom folder in users home folder | |
process.env.ATOM_HOME = path.join(homePath, '.atom') | |
}, | |
setUserData: (app) => { | |
const electronUserDataPath = path.join(process.env.ATOM_HOME, 'electronUserData') | |
if (fs.existsSync(electronUserDataPath)) { | |
if (hasWriteAccess(electronUserDataPath)) { | |
app.setPath('userData', electronUserDataPath) | |
} else { | |
// A path exists so it was intended to be used but we didn't have rights, so warn. | |
console.log(`Insufficient permission to Electron user data "${electronUserDataPath}".`) | |
} | |
} | |
}, | |
getAppDirectory: getAppDirectory | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{Directory} = require 'pathwatcher' | |
fs = require 'fs-plus' | |
path = require 'path' | |
url = require 'url' | |
module.exports = | |
class DefaultDirectoryProvider | |
# Public: Create a Directory that corresponds to the specified URI. | |
# | |
# * `uri` {String} The path to the directory to add. This is guaranteed not to | |
# be contained by a {Directory} in `atom.project`. | |
# | |
# Returns: | |
# * {Directory} if the given URI is compatible with this provider. | |
# * `null` if the given URI is not compatibile with this provider. | |
directoryForURISync: (uri) -> | |
normalizedPath = @normalizePath(uri) | |
{host} = url.parse(uri) | |
directoryPath = if host | |
uri | |
else if not fs.isDirectorySync(normalizedPath) and fs.isDirectorySync(path.dirname(normalizedPath)) | |
path.dirname(normalizedPath) | |
else | |
normalizedPath | |
# TODO: Stop normalizing the path in pathwatcher's Directory. | |
directory = new Directory(directoryPath) | |
if host | |
directory.path = directoryPath | |
if fs.isCaseInsensitive() | |
directory.lowerCasePath = directoryPath.toLowerCase() | |
directory | |
# Public: Create a Directory that corresponds to the specified URI. | |
# | |
# * `uri` {String} The path to the directory to add. This is guaranteed not to | |
# be contained by a {Directory} in `atom.project`. | |
# | |
# Returns a {Promise} that resolves to: | |
# * {Directory} if the given URI is compatible with this provider. | |
# * `null` if the given URI is not compatibile with this provider. | |
directoryForURI: (uri) -> | |
Promise.resolve(@directoryForURISync(uri)) | |
# Public: Normalizes path. | |
# | |
# * `uri` {String} The path that should be normalized. | |
# | |
# Returns a {String} with normalized path. | |
normalizePath: (uri) -> | |
# Normalize disk drive letter on Windows to avoid opening two buffers for the same file | |
pathWithNormalizedDiskDriveLetter = | |
if process.platform is 'win32' and matchData = uri.match(/^([a-z]):/) | |
"#{matchData[1].toUpperCase()}#{uri.slice(1)}" | |
else | |
uri | |
path.normalize(pathWithNormalizedDiskDriveLetter) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Task = require './task' | |
# Searches local files for lines matching a specified regex. Implements `.then()` | |
# so that it can be used with `Promise.all()`. | |
class DirectorySearch | |
constructor: (rootPaths, regex, options) -> | |
scanHandlerOptions = | |
ignoreCase: regex.ignoreCase | |
inclusions: options.inclusions | |
includeHidden: options.includeHidden | |
excludeVcsIgnores: options.excludeVcsIgnores | |
globalExclusions: options.exclusions | |
follow: options.follow | |
searchOptions = | |
leadingContextLineCount: options.leadingContextLineCount | |
trailingContextLineCount: options.trailingContextLineCount | |
@task = new Task(require.resolve('./scan-handler')) | |
@task.on 'scan:result-found', options.didMatch | |
@task.on 'scan:file-error', options.didError | |
@task.on 'scan:paths-searched', options.didSearchPaths | |
@promise = new Promise (resolve, reject) => | |
@task.on('task:cancelled', reject) | |
@task.start rootPaths, regex.source, scanHandlerOptions, searchOptions, => | |
@task.terminate() | |
resolve() | |
then: (args...) -> | |
@promise.then.apply(@promise, args) | |
cancel: -> | |
# This will cause @promise to reject. | |
@task.cancel() | |
null | |
# Default provider for the `atom.directory-searcher` service. | |
module.exports = | |
class DefaultDirectorySearcher | |
# Determines whether this object supports search for a `Directory`. | |
# | |
# * `directory` {Directory} whose search needs might be supported by this object. | |
# | |
# Returns a `boolean` indicating whether this object can search this `Directory`. | |
canSearchDirectory: (directory) -> true | |
# Performs a text search for files in the specified `Directory`, subject to the | |
# specified parameters. | |
# | |
# Results are streamed back to the caller by invoking methods on the specified `options`, | |
# such as `didMatch` and `didError`. | |
# | |
# * `directories` {Array} of {Directory} objects to search, all of which have been accepted by | |
# this searcher's `canSearchDirectory()` predicate. | |
# * `regex` {RegExp} to search with. | |
# * `options` {Object} with the following properties: | |
# * `didMatch` {Function} call with a search result structured as follows: | |
# * `searchResult` {Object} with the following keys: | |
# * `filePath` {String} absolute path to the matching file. | |
# * `matches` {Array} with object elements with the following keys: | |
# * `lineText` {String} The full text of the matching line (without a line terminator character). | |
# * `lineTextOffset` {Number} (This always seems to be 0?) | |
# * `matchText` {String} The text that matched the `regex` used for the search. | |
# * `range` {Range} Identifies the matching region in the file. (Likely as an array of numeric arrays.) | |
# * `didError` {Function} call with an Error if there is a problem during the search. | |
# * `didSearchPaths` {Function} periodically call with the number of paths searched thus far. | |
# * `inclusions` {Array} of glob patterns (as strings) to search within. Note that this | |
# array may be empty, indicating that all files should be searched. | |
# | |
# Each item in the array is a file/directory pattern, e.g., `src` to search in the "src" | |
# directory or `*.js` to search all JavaScript files. In practice, this often comes from the | |
# comma-delimited list of patterns in the bottom text input of the ProjectFindView dialog. | |
# * `ignoreHidden` {boolean} whether to ignore hidden files. | |
# * `excludeVcsIgnores` {boolean} whether to exclude VCS ignored paths. | |
# * `exclusions` {Array} similar to inclusions | |
# * `follow` {boolean} whether symlinks should be followed. | |
# | |
# Returns a *thenable* `DirectorySearch` that includes a `cancel()` method. If `cancel()` is | |
# invoked before the `DirectorySearch` is determined, it will resolve the `DirectorySearch`. | |
search: (directories, regex, options) -> | |
rootPaths = directories.map (directory) -> directory.getPath() | |
isCancelled = false | |
directorySearch = new DirectorySearch(rootPaths, regex, options) | |
promise = new Promise (resolve, reject) -> | |
directorySearch.then resolve, -> | |
if isCancelled | |
resolve() | |
else | |
reject() | |
return { | |
then: promise.then.bind(promise) | |
catch: promise.catch.bind(promise) | |
cancel: -> | |
isCancelled = true | |
directorySearch.cancel() | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict' | |
const fs = require('fs-plus') | |
const path = require('path') | |
module.exports = | |
class FileSystemBlobStore { | |
static load(directory) { | |
let instance = new FileSystemBlobStore(directory) | |
instance.load() | |
return instance | |
} | |
constructor(directory) { | |
this.blobFilename = path.join(directory, 'BLOB') | |
this.blobMapFilename = path.join(directory, 'MAP') | |
this.lockFilename = path.join(directory, 'LOCK') | |
this.reset() | |
} | |
reset() { | |
this.inMemoryBlobs = new Map() | |
this.storedBlob = new Buffer(0) | |
this.storedBlobMap = {} | |
this.usedKeys = new Set() | |
} | |
load() { | |
if (!fs.existsSync(this.blobMapFilename)) { | |
return | |
} | |
if (!fs.existsSync(this.blobFilename)) { | |
return | |
} | |
try { | |
this.storedBlob = fs.readFileSync(this.blobFilename) | |
this.storedBlobMap = JSON.parse(fs.readFileSync(this.blobMapFilename)) | |
} catch (e) { | |
this.reset() | |
} | |
} | |
save() { | |
let dump = this.getDump() | |
let blobToStore = Buffer.concat(dump[0]) | |
let mapToStore = JSON.stringify(dump[1]) | |
let acquiredLock = false | |
try { | |
fs.writeFileSync(this.lockFilename, 'LOCK', { | |
flag: 'wx' | |
}) | |
acquiredLock = true | |
fs.writeFileSync(this.blobFilename, blobToStore) | |
fs.writeFileSync(this.blobMapFilename, mapToStore) | |
} catch (error) { | |
// Swallow the exception silently only if we fail to acquire the lock. | |
if (error.code !== 'EEXIST') { | |
throw error | |
} | |
} finally { | |
if (acquiredLock) { | |
fs.unlinkSync(this.lockFilename) | |
} | |
} | |
} | |
has(key) { | |
return this.inMemoryBlobs.has(key) || this.storedBlobMap.hasOwnProperty(key) | |
} | |
get(key) { | |
if (this.has(key)) { | |
this.usedKeys.add(key) | |
return this.getFromMemory(key) || this.getFromStorage(key) | |
} | |
} | |
set(key, buffer) { | |
this.usedKeys.add(key) | |
return this.inMemoryBlobs.set(key, buffer) | |
} | |
delete(key) { | |
this.inMemoryBlobs.delete(key) | |
delete this.storedBlobMap[key] | |
} | |
getFromMemory(key) { | |
return this.inMemoryBlobs.get(key) | |
} | |
getFromStorage(key) { | |
if (!this.storedBlobMap[key]) { | |
return | |
} | |
return this.storedBlob.slice.apply(this.storedBlob, this.storedBlobMap[key]) | |
} | |
getDump() { | |
let buffers = [] | |
let blobMap = {} | |
let currentBufferStart = 0 | |
function dump(key, getBufferByKey) { | |
let buffer = getBufferByKey(key) | |
buffers.push(buffer) | |
blobMap[key] = [currentBufferStart, currentBufferStart + buffer.length] | |
currentBufferStart += buffer.length | |
} | |
for (let key of this.inMemoryBlobs.keys()) { | |
if (this.usedKeys.has(key)) { | |
dump(key, this.getFromMemory.bind(this)) | |
} | |
} | |
for (let key of Object.keys(this.storedBlobMap)) { | |
if (!blobMap[key] && this.usedKeys.has(key)) { | |
dump(key, this.getFromStorage.bind(this)) | |
} | |
} | |
return [buffers, blobMap] | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment