Skip to content
This repository has been archived by the owner on Dec 15, 2022. It is now read-only.

Store filesystem stats on tree-view entries #859

Merged
merged 9 commits into from
Aug 9, 2016
9 changes: 6 additions & 3 deletions lib/directory.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ realpathCache = {}

module.exports =
class Directory
constructor: ({@name, fullPath, @symlink, @expansionState, @isRoot, @ignoredPatterns, @useSyncFS}) ->
constructor: ({@name, fullPath, @symlink, @expansionState, @isRoot, @ignoredPatterns, @useSyncFS, @stats}) ->
@destroyed = false
@emitter = new Emitter()
@subscriptions = new CompositeDisposable()
Expand Down Expand Up @@ -177,6 +177,9 @@ class Directory
stat = fs.lstatSyncNoException(fullPath)
symlink = stat.isSymbolicLink?()
stat = fs.statSyncNoException(fullPath) if symlink
statFlat = _.pick stat, _.keys(stat)...
for key in ["atime", "birthtime", "ctime", "mtime"]
statFlat[key] = statFlat[key]?.getTime()

if stat.isDirectory?()
if @entries.hasOwnProperty(name)
Expand All @@ -185,14 +188,14 @@ class Directory
directories.push(name)
else
expansionState = @expansionState.entries[name]
directories.push(new Directory({name, fullPath, symlink, expansionState, @ignoredPatterns, @useSyncFS}))
directories.push(new Directory({name, fullPath, symlink, expansionState, @ignoredPatterns, @useSyncFS, stats: statFlat}))
else if stat.isFile?()
if @entries.hasOwnProperty(name)
# push a placeholder since this entry already exists but this helps
# track the insertion index for the created views
files.push(name)
else
files.push(new File({name, fullPath, symlink, realpathCache, @useSyncFS}))
files.push(new File({name, fullPath, symlink, realpathCache, @useSyncFS, stats: statFlat}))

@sortEntries(directories.concat(files))

Expand Down
2 changes: 1 addition & 1 deletion lib/file.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ fs = require 'fs-plus'

module.exports =
class File
constructor: ({@name, fullPath, @symlink, realpathCache, useSyncFS}) ->
constructor: ({@name, fullPath, @symlink, realpathCache, useSyncFS, @stats}) ->
@destroyed = false
@emitter = new Emitter()
@subscriptions = new CompositeDisposable()
Expand Down
6 changes: 6 additions & 0 deletions lib/tree-view.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,11 @@ class TreeView extends View
@loadIgnoredPatterns()

@roots = for projectPath in atom.project.getPaths()
stats = fs.lstatSyncNoException(projectPath)
stats = _.pick stats, _.keys(stats)...
for key in ["atime", "birthtime", "ctime", "mtime"]
stats[key] = stats[key].getTime()

directory = new Directory({
name: path.basename(projectPath)
fullPath: projectPath
Expand All @@ -285,6 +290,7 @@ class TreeView extends View
{isExpanded: true}
@ignoredPatterns
@useSyncFS
stats
})
root = new DirectoryView()
root.initialize(directory)
Expand Down
63 changes: 63 additions & 0 deletions spec/file-stats-spec.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
_ = require 'underscore-plus'
{$, $$} = require 'atom-space-pen-views'
fs = require 'fs-plus'
path = require 'path'
temp = require('temp').track()

describe "FileStats", ->
[file1Data, file2Data, timeStarted, treeView] = ["ABCDEFGHIJKLMNOPQRSTUVWXYZ", "0123456789"]

beforeEach ->
timeStarted = Date.now()
rootDirPath = fs.absolute(temp.mkdirSync("tree-view"))
subdirPath = path.join(rootDirPath, "subdir")
filePath1 = path.join(rootDirPath, "file1.txt")
filePath2 = path.join(subdirPath, "file2.txt")

fs.makeTreeSync(subdirPath)
fs.writeFileSync(filePath1, file1Data)
fs.writeFileSync(filePath2, file2Data)
atom.project.setPaths([rootDirPath])

waitsForPromise ->
atom.packages.activatePackage("tree-view")

runs ->
treeView = $(atom.workspace.getLeftPanels()[0].getItem()).view()

afterEach ->
temp.cleanup()

describe "provision of filesystem stats", ->
it "passes stats to File instances", ->
stats = treeView.roots[0].directory.entries["file1.txt"].stats
expect(stats).toBeDefined()
expect(stats.mtime).toBeDefined()
expect(stats.size).toEqual(file1Data.length)

it "passes stats to Directory instances", ->
stats = treeView.roots[0].directory.entries["subdir"].stats
expect(stats).toBeDefined()
expect(stats.mtime).toBeDefined()

it "passes stats to a root directory when initialised", ->
expect(treeView.roots[0].directory.stats).toBeDefined()

it "passes stats to File instances in subdirectories", ->
treeView.find(".entries > li:contains(subdir)")[0].expand()
subdir = treeView.roots[0].directory.entries["subdir"]
stats = subdir.entries["file2.txt"].stats
expect(stats).toBeDefined()
expect(stats.size).toEqual(file2Data.length)

it "converts date-stats to timestamps", ->
stats = treeView.roots[0].directory.entries["file1.txt"].stats
stamp = stats.mtime
expect(_.isDate stamp).toBe(false)
expect(typeof stamp).toBe("number")
expect(Number.isNaN stamp).toBe(false)

it "accurately converts timestamps", ->
stats = treeView.roots[0].directory.entries["file1.txt"].stats
# Two minutes should be enough
expect(Math.abs stats.mtime - timeStarted).toBeLessThan(120000)