如何使用JavaScript遍历文件系统目录和文件?

3

我正在使用Javascript编写一个应用程序,将与Phonegap一起使用以制作Android应用程序。我正在使用Phonegap文件API来读取目录和文件。相关代码如下所示:

document.addEventListener("deviceready", onDeviceReady, false);

// PhoneGap is ready
//
function onDeviceReady() {
    window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, onFileSystemSuccess, fail);
}

function onFileSystemSuccess(fileSystem) {
    fileSystem.root.getDirectory("/sdcard", {create: false, exclusive: false}, getDirSuccess, fail);
}

function getDirSuccess(dirEntry) {
    // Get a directory reader
    var directoryReader = dirEntry.createReader();

    // Get a list of all the entries in the directory
    directoryReader.readEntries(readerSuccess,fail);
}

var numDirs = 0;
var numFiles = 0;

function readerSuccess(entries) {
    var i;
    for (i=0; i<entries.length; i++) 
    {
        if(entries[i].isFile === true)
        {
        numFiles++;
        entries[i].file(fileSuccess,fail);
        }
        else if (entries[i].isDirectory === true)
        {
        numDirs++;
        getDirSuccess(entries[i]);
        }
    }
}

目前程序运行良好。阅读器将读取 /sdcard 目录的内容。如果遇到文件,它会调用 fileSuccess(为了简洁起见,我已经在代码中排除了此部分),如果遇到另一个目录,则会再次调用 getDirSuccess。我的问题是:我如何知道整个 /sdcard 目录何时被读取完毕?我想不出一个不需要多次遍历 /sdcard 目录就能完成这个任务的好方法。如果您有任何想法,将不胜感激,并提前感谢您!

2个回答

2

因为我必须自己做这件事,所以我会在好问题上+1。我会使用旧的setTimeout技巧。一旦取消不再发生,你就知道完成了并可以触发事件,但要确保它只被触发一次。

这就是我的意思,我已经将变量命名为long,只是为了更易读(不是我的风格)...

// create timeout var outside your "readerSuccess" function scope
var readerTimeout = null, millisecondsBetweenReadSuccess = 100;

function readerSuccess(entries) {
    var i = 0, len = entries.length;
    for (; i < len; i++) {
        if (entries[i].isFile) {
            numFiles++;
            entries[i].file(fileSuccess,fail);
        } else if (entries[i].isDirectory) {
            numDirs++;
            getDirSuccess(entries[i]);
        }
        if (readerTimeout) {
            window.clearTimeout(readerTimeout);
        }
    }
    if (readerTimeout) {
        window.clearTimeout(readerTimeout);
    }
    readerTimeout = window.setTimeout(weAreDone, millisecondsBetweenReadSuccess);
}

// additional event to call when totally done
function weAreDone() {
   // do something
}

因此,这个逻辑是当您阅读内容时,不断取消调用“weAreDone”函数。不确定这是否是最佳或更有效的方法,但它不会导致超过一个循环,只要“millisecondsBetweenReadSuccess”适当。


这个方法确实有效,所以加1分。我将millisecondsBetweenReadSuccess设置为5000,weAreDone()只被调用一次。如果将其设置为4000,则会被调用两次。我希望避免使用时间敏感的函数,因为如果我在不同的设备上使用此代码,它可能无法正常工作(millisecondsBetweenReadSuccess可能需要更大)。如果我找不到更好的解决方案,我会将您的方案标记为已接受。如果我找到更优雅的解决方案,我一定会在这里发布。 - vdelricco
哇,5秒钟真的很长。我会稍微修改一下,这样你就不会有这么长的超时时间了。你通过验证我的想法帮助了我,所以我很感激。 - King Friday
有趣的是,在for循环中添加额外的if语句导致weAreDone函数被调用两次(同时仍设置为5秒)。 - vdelricco
我想我必须实现这个,所以我会弄清楚并在确保后再把它发回来。谢谢。 - King Friday
我已经成功将millisecondsBetweenReadSuccess降至150。我是通过在for循环的开头加入clearTimeout if语句来实现的。我还在fileSuccess函数的开头添加了clearTimeout if语句,这意味着我还必须在fileSuccess函数的结尾添加readerTimeout=setTimeout行。如果你理解了,请告诉我。 - vdelricco

1

不要使用setTimeout,因为如果你的设备非常慢,它可能会失败。相反,您可以使用计数器来查看还需要调用多少个回调函数。如果计数器达到零,则已完成:)

这是递归代码:

var fileSync = new function(){
    this.filesystem = null;

    this.getFileSystem = function(callback){
        var rfs = window.requestFileSystem || window.webkitRequestFileSystem;

        rfs(
              1// '1' means PERSISTENT
            , 0// '0' is about max. storage size: 0==we don't know yet
            , function(filesystem){
                fileSync.filesystem = filesystem;
                callback(filesystem);
            }
            , function(e){
                alert('An error occured while requesting the fileSystem:\n\n'+ e.message);
            }
        );
    }

    this.readFilesFromReader = function(reader, callback, recurse, recurseFinishedCallback, recurseCounter)
    {
        if (recurse && !recurseCounter)
            recurseCounter = [1];

        reader.readEntries(function(res){
                callback(res);

                if (recurse)
                {
                    for (var i=0; i<res.length; i++) {
                        /* only handle directories */
                        if (res[i].isDirectory == true)
                        {
                            recurseCounter[0]++;
                            fileSync.readFilesFromReader(res[i].createReader(), callback, recurse, recurseFinishedCallback, recurseCounter);
                        }
                    }
                }
                /* W3C specs say: Continue calling readEntries() until an empty array is returned.
                 * You have to do this because the API might not return all entries in a single call.
                 * But... Phonegap doesn't seem to accommodate this, and instead always returns the same dir-entries... OMG, an infinite loop is created :-/
                */
                //if (res.length)
                //  fileSync.readFilesFromReader(reader, callback, recurse, recurseFinishedCallback, recurseCounter);
                //else
                if (recurse && --recurseCounter[0] == 0)
                {
                    recurseFinishedCallback();
                }
            }
        , function(e){
            fileSync.onError(e);
            if (recurse && --recurseCounter[0] == 0)
                recurseFinishedCallback();
        });
    };

    this.onError = function(e){
        utils.log('onError in fileSync: ' + JSON.stringify(e));
        if (utils.isDebugEnvironment())
            alert('onError in fileSync: '+JSON.stringify(e));
    }
}

var utils = new function(){
    this.log = function(){
        for (var i=0;i<arguments.length;i++)
            console.log(arguments[i]);
    }
    this.isDebugEnvironment = function(){ return true }// simplified
}

测试此功能的示例代码:

var myFiles = [];
var directoryCount = 0;

window.onerror = function(){ alert('window.onerror=\n\n' + arguments.join('\n')) }

var gotFilesCallback = function(entries)
{
    for (var i=0;i<entries.length;i++)
    {
        if (entries[i].isFile == true)
            myFiles.push(entries[i].fullPath)
        else
            ++directoryCount;
    }
}

var allDoneCallback = function(){
    alert('All files and directories were read.\nWe found '+myFiles.length+' files and '+directoryCount+' directories, shown on-screen now...');
    var div = document.createElement('div');
    div.innerHTML = '<div style="border: 1px solid red; position: absolute;top:10px;left:10%;width:80%; background: #eee;">'
        + '<b>Filesystem root:</b><i>' + fileSync.filesystem.root.fullPath + '</i><br><br>'
        + myFiles.join('<br>').split(fileSync.filesystem.root.fullPath).join('')
    + '</div>';
    document.body.appendChild(div);
}

/* on-device-ready / on-load, get the filesystem, and start reading files */
var docReadyEvent = window.cordova ? 'deviceready':'load';
document.addEventListener(docReadyEvent, function()
{
    fileSync.getFileSystem(function(filesystem){
        var rootDirReader = filesystem.root.createReader();
        fileSync.readFilesFromReader(rootDirReader, gotFilesCallback, true, allDoneCallback);
    })
}, false);

嘿,@PaulFrinky,我之前就停止了在这个项目上的工作,所以无法对其进行测试。不过逻辑和代码似乎都是正确的,你可以得到一次点赞 :) - vdelricco

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接