Adding only FileIndexerManager. Separating out QuickFileOpen

This commit is contained in:
Ty Voliter 2012-02-28 14:04:19 -08:00
Родитель 915c3a663e
Коммит bdf4d7b008
12 изменённых файлов: 391 добавлений и 1 удалений

377
src/FileIndexManager.js Normal file
Просмотреть файл

@ -0,0 +1,377 @@
/*
* Copyright 2012 Adobe Systems Incorporated. All Rights Reserved.
*/
/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define: false, $: false, brackets, PathUtils */
/*
* Manages a collection of FileIndexes where each index maintains a list of information about
* files that meet the criteria specified by the index. The indexes are created lazily when
* they are queried and marked dirty when Brackets becomes active.
*
* TODO (issue 325 ) - FileIndexer doesn't currently add a file to the index when the user createa
* a new file within brackets.
*
*/
define(function (require, exports, module) {
'use strict';
var NativeFileSystem = require("NativeFileSystem").NativeFileSystem,
PerfUtils = require("PerfUtils"),
ProjectManager = require("ProjectManager"),
Strings = require("strings");
/**
* All the indexes are stored in this object. The key is the name of the index
* and the value is a FileIndex.
*/
var _indexList = {};
/**
* Tracks whether _indexList should be considered dirty and invalid. Calls that access
* any data in _indexList should call syncFileIndex prior to accessing the data.
* @type {boolean}
*/
var _indexListDirty = true;
/** class FileIndex
*
* A FileIndex contains an array of fileInfos that meet the criteria specified by
* the filterFunction. FileInfo's in the fileInfo array should unique map to one file.
*
* @constructor
* @param {!string} indexname
* @param {function({!entry})} filterFunction returns true to indicate the entry
* should be included in the index
*/
function FileIndex(indexName, filterFunction) {
this.name = indexName;
this.fileInfos = [];
this.filterFunction = filterFunction;
}
/** class FileInfo
*
* Class to hold info about a file that a FileIndex wishes to retain.
*
* @constructor
* @param {!string}
*/
function FileInfo(entry) {
this.name = entry.name;
this.fullPath = entry.fullPath;
}
/**
* Adds a new index to _indexList and marks the list dirty
*
* A future performance optimization is to only build the new index rather than
* marking them all dirty
*
* @private
* @param {!string} indexName must be unque
* @param {!function({entry} filterFunction should return true to include an
* entry in the index
*/
function _addIndex(indexName, filterFunction) {
if (_indexList.hasOwnProperty(indexName)) {
throw new Error("Duplicate index name");
}
if (typeof filterFunction !== "function") {
throw new Error("Invalid arguments");
}
_indexList[indexName] = new FileIndex(indexName, filterFunction);
_indexListDirty = true;
}
/**
* Checks the entry against the filterFunction for each index and adds
* a fileInfo to the index if the entry meets the criteria. FileInfo's are
* shared between indexes.
*
* @private
* @param {!entry} entry to be added to the indexes
*/
// future use when files are incrementally added
//
function _addFileToIndexes(entry) {
// skip invisible files on mac
if (brackets.platform === "mac" && entry.name.charAt(0) === ".") {
return;
}
var fileInfo = new FileInfo(entry);
//console.log(entry.name);
$.each(_indexList, function (indexName, index) {
if (index.filterFunction(entry)) {
index.fileInfos.push(fileInfo);
}
});
}
/**
* Error dialog when max files in index is hit
*/
function _showMaxFilesDialog() {
return brackets.showModalDialog(
brackets.DIALOG_ID_ERROR,
Strings.ERROR_MAX_FILES_TITLE,
Strings.ERROR_MAX_FILES
);
}
/* Recursively visits all files that are descendent of dirEntry and adds
* files files to each index when the file matches the filter critera
* @private
* @param {!DirectoryEntry} dirEntry
* @returns {$.Promise}
*/
function _scanDirectorySubTree(dirEntry) {
if (!dirEntry) {
throw new Error("Bad dirEntry passed to _scanDirectorySubTree");
}
// keep track of directories as they are asynchronously read. We know we are done
// when dirInProgress becomes empty again.
var state = { fileCount: 0,
dirInProgress: {}, // directory names that are in progress of being read
dirError: {}, // directory names with read errors. key=dir path, value=error
maxFilesHit: false // used to show warning dialog only once
};
var deferred = new $.Deferred();
// inner helper function
function _dirScanDone() {
var key;
for (key in state.dirInProgress) {
if (state.dirInProgress.hasOwnProperty(key)) {
return false;
}
}
return true;
}
function _finishDirScan(dirEntry) {
//console.log("finished: " + dirEntry.fullPath);
delete state.dirInProgress[dirEntry.fullPath];
if (_dirScanDone()) {
//console.log("dir scan completly done");
deferred.resolve();
}
}
// inner helper function
function _scanDirectoryRecurse(dirEntry) {
state.dirInProgress[dirEntry.fullPath] = true;
//console.log("started dir: " + dirEntry.fullPath);
dirEntry.createReader().readEntries(
// success callback
function (entries) {
// inspect all children of dirEntry
entries.forEach(function (entry) {
if (entry.isFile) {
_addFileToIndexes(entry);
state.fileCount++;
// For now limit the number of files that are indexed. This limit could be increased
// if files were indexed in a worker thread so scanning didn't block the UI
if (state.fileCount === 10000) {
if (!state.maxFilesHit) {
state.maxFilesHit = true;
_showMaxFilesDialog();
}
return;
}
} else if (entry.isDirectory) {
_scanDirectoryRecurse(entry);
}
});
_finishDirScan(dirEntry);
},
// error callback
function (error) {
state.dirError[dirEntry.fullPath] = error;
_finishDirScan(dirEntry);
}
);
}
_scanDirectoryRecurse(dirEntry);
return deferred.promise();
}
// debug
function _logFileList(list) {
list.forEach(function (fileInfo) {
console.log(fileInfo.name);
});
console.log("length: " + list.length);
}
/**
* Clears the fileInfo array for all the indexes in _indexList
* @private
*/
function _clearIndexes() {
$.each(_indexList, function (indexName, index) {
index.fileInfos = [];
});
}
/**
* Markes all file indexes dirty
*/
function markDirty() {
_indexListDirty = true;
}
/**
* Used by syncFileIndex function to prevent reentrancy
* @private
*/
var _syncFileIndexReentracyGuard = false;
/**
* Clears and rebuilds all of the fileIndexes and sets _indexListDirty to false
* @return {$.Promise} resolved when index has been updated
*/
function syncFileIndex() {
if (_syncFileIndexReentracyGuard) {
throw new Error("syncFileIndex cannot be called Recursively");
}
_syncFileIndexReentracyGuard = true;
var rootDir = ProjectManager.getProjectRoot();
if (_indexListDirty) {
PerfUtils.markStart("FileIndexManager.syncFileIndex(): " + rootDir.fullPath);
_clearIndexes();
return _scanDirectorySubTree(rootDir)
.done(function () {
PerfUtils.addMeasurement("FileIndexManager.syncFileIndex(): " + rootDir.fullPath);
_indexListDirty = false;
_syncFileIndexReentracyGuard = false;
//_logFileList(_indexList["all"].fileInfos);
//_logFileList(_indexList["css"].fileInfos);
});
} else {
_syncFileIndexReentracyGuard = false;
return $.Deferred().resolve();
}
}
/**
* Returns the FileInfo array for the specified index
* @param {!string} indexname
* @return {$.Promise} a promise that is resolved with an Array of FileInfo's
*/
function getFileInfoList(indexName) {
var result = new $.Deferred();
if (!_indexList.hasOwnProperty(indexName)) {
throw new Error("indexName not found");
}
syncFileIndex()
.done(function () {
result.resolve(_indexList[indexName].fileInfos);
});
return result.promise();
}
/**
* Calls the filterFunction on every in the index specified by indexName
* and return a a new list of FileInfo's
* @param {!string}
* @param {function({string})} filterFunction
* @return {$.Promise} a promise that is resolved with an Array of FileInfo's
*/
function getFilteredList(indexName, filterFunction) {
var result = new $.Deferred();
if (!_indexList.hasOwnProperty(indexName)) {
throw new Error("indexName not found");
}
syncFileIndex()
.done(function () {
var resultList = [];
getFileInfoList(indexName)
.done(function (fileList) {
resultList = fileList.filter(function (fileInfo) {
return filterFunction(fileInfo.name);
});
result.resolve(resultList);
});
});
return result.promise();
}
/**
* returns an array of fileInfo's that match the filename parameter
* @param {!string} indexName
* @param {!filename}
* @return {$.Promise} a promise that is resolved with an Array of FileInfo's
*/
function getFilenameMatches(indexName, filename) {
return getFilteredList(indexName, function (item) {
return item === filename;
});
}
/**
* Add the indexes
*/
_addIndex(
"all",
function (entry) {
return true;
}
);
_addIndex(
"css",
function (entry) {
var filename = entry.name;
return PathUtils.filenameExtension(filename) === ".css";
}
);
$(ProjectManager).on("projectRootChanged", function (event, projectRoot) {
markDirty();
});
exports.markDirty = markDirty;
exports.getFileInfoList = getFileInfoList;
exports.getFilenameMatches = getFilenameMatches;
});

Просмотреть файл

@ -10,9 +10,10 @@
* creating and updating the project tree when projects are opened and when changes occur to
* the file tree.
*
* This module dispatches 1 event:
* This module dispatches these events:
* - initializeComplete -- When the ProjectManager initializes the first
* project at application start-up.
* - projectRootChanged -- when _projectRoot changes
*
* These are jQuery events, so to listen for them you do something like this:
* $(ProjectManager).on("eventname", handler);
@ -409,6 +410,9 @@ define(function (require, exports, module) {
// Point at a real folder structure on local disk
NativeFileSystem.requestNativeFileSystem(rootPath,
function (rootEntry) {
var projectRootChanged = (!_projectRoot || !rootEntry)
|| _projectRoot.fullPath !== rootEntry.fullPath;
// Success!
_projectRoot = rootEntry;
@ -426,6 +430,10 @@ define(function (require, exports, module) {
if (isFirstProjectOpen) {
$(exports).triggerHandler("initializeComplete", _projectRoot);
}
if (projectRootChanged) {
$(exports).triggerHandler("projectRootChanged", _projectRoot);
}
});
resultRenderTree.fail(function () {
result.reject();

Просмотреть файл

@ -57,6 +57,10 @@ define(function (require, exports, module) {
exports.ERROR_CREATING_FILE_TITLE = "Error creating file";
exports.ERROR_CREATING_FILE = "An error occurred when trying to create the file \"{0}\". {1}";
// FileIndexManager error string
exports.ERROR_MAX_FILES_TITLE = "Error Indexing Files";
exports.ERROR_MAX_FILES = "The maximum number of files have been indexed. Actions that look up files in the index may function incorrectly.";
exports.SAVE_CLOSE_TITLE = "Save Changes";
exports.SAVE_CLOSE_MESSAGE = "Do you want to save the changes you made in the document \"{0}\"?";
exports.SAVE_CLOSE_MULTI_MESSAGE = "Do you want to save your changes to the following files?";

Просмотреть файл

@ -24,6 +24,7 @@ define(function (require, exports, module) {
require("spec/ProjectManager-test.js");
require("spec/WorkingSetView-test.js");
require("spec/KeyMap-test.js");
require("spec/FileIndexManager-test.js");
require("spec/CodeHintUtils-test.js");
require("spec/CSSManager-test.js");

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл