Skip to content

Commit

Permalink
Merge pull request #262 from atom-community/add-watcher
Browse files Browse the repository at this point in the history
  • Loading branch information
aminya authored Apr 24, 2021
2 parents fa53cb0 + ec52489 commit 1234ed6
Show file tree
Hide file tree
Showing 4 changed files with 733 additions and 604 deletions.
12 changes: 3 additions & 9 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,14 @@ jobs:
- name: Commit lint ✨
uses: wagoid/commitlint-github-action@v2

- uses: UziTech/action-setup-atom@v1
- name: Setup PNPM
uses: pnpm/action-setup@v1.2.1
with:
version: latest

- name: Install dependencies
run: pnpm install --ignore-scripts ## TODO build fails here!
run: npm install --ignore-scripts ## TODO build fails here!

- name: Format ✨
run: pnpm test.format
run: npm run test.format

- name: Lint ✨
run: pnpm test.lint
run: npm run test.lint

Release:
needs: [Test, Lint]
Expand Down
240 changes: 176 additions & 64 deletions lib/paths-cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Directory, File } from "atom"
import { union } from "./utils"
import { globifyPath, globifyDirectory, globifyGitIgnoreFile } from "./path-utils"
import glob from "fast-glob"
import * as chokidar from "chokidar"

export default class PathsCache extends EventEmitter {
constructor() {
Expand Down Expand Up @@ -39,18 +40,19 @@ export default class PathsCache extends EventEmitter {
* Rebuilds the paths cache
* @returns {Promise<Array<Array<string>>>}
*/
rebuildCache() {
async rebuildCache() {
this.dispose()

this._cancelled = false
this.emit("rebuild-cache")

try {
return this._buildInitialCacheWithGlob()
} catch (e) {
console.error(e)
return this._buildInitialCacheWithAtom()
}
await this._cacheProjectPathsAndRepositories()
const results = await this._cachePaths()

await this._addWatchers()

this.emit("rebuild-cache-done")
return results
}

/**
Expand All @@ -72,12 +74,12 @@ export default class PathsCache extends EventEmitter {
* @param {boolean} isPackageDispose
*/
dispose(isPackageDispose) {
this._fileWatchersByDirectory.forEach((watcher) => {
watcher.dispose()
this._fileWatchersByDirectory.forEach(async (watcher) => {
await watcher.close()
})
this._fileWatchersByDirectory = new Map()
this._filePathsByProjectDirectory = new Map()
this._filePathsByDirectory = new Map()
this._fileWatchersByDirectory.clear()
this._filePathsByProjectDirectory.clear()
this._filePathsByDirectory.clear()
this._repositories = []
if (this._projectWatcher) {
this._projectWatcher.dispose()
Expand Down Expand Up @@ -147,20 +149,120 @@ export default class PathsCache extends EventEmitter {
this._repositories = repositories.filter((r) => r !== null) // filter out non-repository directories
}

/**
* Add watchers for all the projectDirectories
* @return {Promise<void>}
* @private
*/
async _addWatchers() {
await Promise.all(
this._projectDirectories.map((projectDirectory) => this._addWatcherForDirectory(projectDirectory))
)
}

/**
* Add a watcher for the projectDirectory
* @param {Directory} projectDirectory
* @private
*/
async _addWatcherForDirectory(projectDirectory) {
// close if already added
let watcher = this._fileWatchersByDirectory.get(projectDirectory)
if (watcher !== undefined && typeof watcher.close === "function") {
await watcher.close()
}
// add a watcher to run `this._onDirectoryChanged`
const projectPath = projectDirectory.getPath()
const ignored = await this._getAllIgnoredGlob(projectPath)
// TODO smarter handling of directory changes
// TODO get paths from the watcher itself
// TODO track gitignore file
watcher = chokidar
.watch([projectPath, ...ignored], {
persistent: true,
})
.on("add", (addedFile) => {
this.onAddFile(projectDirectory, addedFile)
})
.on("unlink", (removedFile) => {
this.onRemoveFile(projectDirectory, removedFile)
})
.on("addDir", (addedDir) => {
this.onAddDir(projectDirectory, addedDir)
})
.on("unlinkDir", (removedDir) => {
this.onRemoveDir(projectDirectory, removedDir)
})
this._fileWatchersByDirectory.set(projectDirectory, watcher)
}

/**
* @param projectDirectory {Directory}
* @param addedFile {string}
*/
onAddFile(projectDirectory, addedFile) {
this.emit("rebuild-cache")
const filePaths = this._filePathsByProjectDirectory.get(projectDirectory.path)
filePaths.push(addedFile)
this._filePathsByProjectDirectory.set(projectDirectory.path, filePaths)
this.emit("rebuild-cache-done")
}

/**
* @param projectDirectory {Directory}
* @param removedFile {string}
*/
onRemoveFile(projectDirectory, removedFile) {
this.emit("rebuild-cache")
/** @type {string[]} */
const filePaths = this._filePathsByProjectDirectory.get(projectDirectory.path)

// delete the removed file
for (let iFile = 0; iFile < filePaths.length; iFile++) {
const file = filePaths[iFile]
if (file === removedFile) {
delete filePaths[iFile]
}
}
this._filePathsByProjectDirectory.set(projectDirectory.path, filePaths)
this.emit("rebuild-cache-done")
}

/**
* @param projectDirectory {Directory}
* @param addedDir {string}
*/
async onAddDir(projectDirectory, addedDir) {
this.emit("rebuild-cache")
const directory = new Directory(addedDir)
await this._cachePathsForDirectory(projectDirectory, directory)
this.emit("rebuild-cache-done")
}

/**
* @param projectDirectory {Directory}
* @param removedDir {string}
*/
onRemoveDir(projectDirectory, removedDir) {
this.emit("rebuild-cache")
const directory = new Directory(removedDir)
this._removeFilePathsForDirectory(projectDirectory, directory)
this.emit("rebuild-cache-done")
}

/**
* Invoked when the content of the given `directory` has changed
* @param {Directory} projectDirectory
* @param {Directory} directory
* @returns {Promise<void>}
* @private
*/
_onDirectoryChanged(projectDirectory, directory) {
async _onDirectoryChanged(projectDirectory, directory) {
this.emit("rebuild-cache")
this._removeFilePathsForDirectory(projectDirectory, directory)
this._cleanWatchersForDirectory(directory)
this._populateCacheWithGlob(directory.path).catch((e) => {
// fallback to Atom
console.error(e)
this._populateCacheWithAtom(projectDirectory, directory)
})
await this._cachePathsForDirectory(projectDirectory, directory)
this.emit("rebuild-cache-done")
}

/**
Expand All @@ -169,9 +271,10 @@ export default class PathsCache extends EventEmitter {
* @private
*/
_cleanWatchersForDirectory(directory) {
this._fileWatchersByDirectory.forEach((watcher, otherDirectory) => {
// TODO promise all
this._fileWatchersByDirectory.forEach(async (watcher, otherDirectory) => {
if (directory.contains(otherDirectory.path)) {
watcher.dispose()
await await watcher.close()
this._fileWatchersByDirectory.delete(otherDirectory)
}
})
Expand Down Expand Up @@ -250,6 +353,37 @@ export default class PathsCache extends EventEmitter {
})
}

/**
* Caches file paths with Glob or Atom
* @return {Promise<Array<Array<string>>}
* @private
*/
_cachePaths() {
try {
return this._cachePathsWithGlob()
} catch (e) {
console.error(e)
return this._cachePathsWithAtom()
}
}

/**
* Caches file paths for the given directory with Glob or Atom
* @param {Directory} projectDirectory
* @param {Directory} directory
* @return {Promise}
* @private
*/
_cachePathsForDirectory(projectDirectory, directory) {
try {
return this._cachePathsForDirectoryWithGlob(directory.path)
} catch (e) {
// fallback to Atom
console.error(e)
return this._cachePathsForDirectoryWithAtom(projectDirectory, directory)
}
}

/*
██████ ██ ██████ ██████
██ ██ ██ ██ ██ ██
Expand All @@ -259,17 +393,14 @@ export default class PathsCache extends EventEmitter {
*/

/**
* Builds the initial file cache with `glob`
* Builds the file cache with `glob`
* @return {Promise<Array<Array<string>>>}
* @private
*/
async _buildInitialCacheWithGlob() {
await this._cacheProjectPathsAndRepositories()
async _cachePathsWithGlob() {
const result = await Promise.all(
this._projectDirectories.map((projectDirectory) => this._populateCacheWithGlob(projectDirectory.path))
this._projectDirectories.map((projectDirectory) => this._cachePathsForDirectoryWithGlob(projectDirectory.path))
)

this.emit("rebuild-cache-done")
return result
}

Expand Down Expand Up @@ -327,19 +458,27 @@ export default class PathsCache extends EventEmitter {
return []
}

/**
* Get all ignored glob using `this._getGitIgnoreGlob` and `this._getIgnoredPatternsGlob`
* @param {string} directoryPath the given directory path
* @returns {Promise<string[]>}
*/
async _getAllIgnoredGlob(directoryPath) {
const gitignoreGlob = await this._getGitIgnoreGlob(directoryPath)
const ignoredPatternsGlob = await this._getIgnoredPatternsGlob(directoryPath)
return [...gitignoreGlob, ...ignoredPatternsGlob]
}

/**
* Populates cache for the given directory
* @param {string} directoryPath the given directory path
* @return {Promise<Array<string>>}
* @private
*/
async _populateCacheWithGlob(directoryPath) {
async _cachePathsForDirectoryWithGlob(directoryPath) {
const directoryGlob = globifyDirectory(directoryPath)
const gitignoreGlob = await this._getGitIgnoreGlob(directoryPath)
const ignoredPatternsGlob = await this._getIgnoredPatternsGlob(directoryPath)

const files = await glob(
[directoryGlob, ...gitignoreGlob, ...ignoredPatternsGlob],
[directoryGlob, ...(await this._getAllIgnoredGlob(directoryPath))],
// glob options
{
dot: true,
Expand All @@ -360,18 +499,16 @@ export default class PathsCache extends EventEmitter {
*/

/**
* Builds the initial file cache using Atom
* Builds the file cache using Atom
* @return {Promise<Array<Array<string>>>}
* @private
*/
async _buildInitialCacheWithAtom() {
await this._cacheProjectPathsAndRepositories()
async _cachePathsWithAtom() {
const result = await Promise.all(
this._projectDirectories.map((projectDirectory) => {
return this._populateCacheWithAtom(projectDirectory, projectDirectory)
return this._cachePathsForDirectoryWithAtom(projectDirectory, projectDirectory)
})
)
this.emit("rebuild-cache-done")
return result
}

Expand All @@ -382,19 +519,12 @@ export default class PathsCache extends EventEmitter {
* @return {Promise<Array<string>>}
* @private
*/
async _populateCacheWithAtom(projectDirectory, directory) {
async _cachePathsForDirectoryWithAtom(projectDirectory, directory) {
if (this._cancelled) {
return []
}

if (process.platform !== "win32") {
let watcher = this._fileWatchersByDirectory.get(directory)
if (!watcher) {
watcher = directory.onDidChange(() => this._onDirectoryChanged(projectDirectory, directory))
this._fileWatchersByDirectory.set(directory, watcher)
}
}
const entries = await _getDirectoryEntries(directory)
const entries = await this._getDirectoryEntries(directory)
if (this._cancelled) {
return []
}
Expand Down Expand Up @@ -424,7 +554,6 @@ export default class PathsCache extends EventEmitter {
this._filePathsByProjectDirectory.clear()
this._filePathsByDirectory.clear()
this._cancelled = true
this.emit("rebuild-cache-done")
return
}

Expand All @@ -434,23 +563,6 @@ export default class PathsCache extends EventEmitter {
filePathsArray = this._filePathsByDirectory.get(directory.path) || []
this._filePathsByDirectory.set(directory.path, union(filePathsArray, filePaths))

return Promise.all(directories.map((dir) => this._populateCacheWithAtom(projectDirectory, dir)))
return Promise.all(directories.map((dir) => this._cachePathsForDirectoryWithAtom(projectDirectory, dir)))
}
}

/**
* Promisified version of Directory#getEntries
* @param {Directory} directory
* @return {Promise<Array<Directory | File>>}
* @private
*/
function _getDirectoryEntries(directory) {
return new Promise((resolve, reject) => {
directory.getEntries((err, entries) => {
if (err) {
return reject(err)
}
resolve(entries)
})
})
}
Loading

0 comments on commit 1234ed6

Please sign in to comment.