Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

It's not clear with the error message 'No such file or directory' when no permission to create the file #542

Closed
neasakim opened this issue Apr 17, 2017 · 12 comments
Milestone

Comments

@neasakim
Copy link

我尝试进行一个简单的下载,没有涉及UI,但是似乎并没有进行下载,Log是直接显示blockComplete和completed,请问我是哪里做错了
private void testDownload() {
String url = "http://apk.tt286.com:8005/api/charge?token=zhuoyou-sz-stkj&from=szstkj&blink=" +
"http://newmarket.tt286.com:8080/180/apk/2017/03/30/1401483xt7/1490864660014.apk&" +
"hot=1&company_type=1&apkid=1088662&imei=865774025208699&userip=192.168.1.178&" +
"network=WIFI&brand=ZTE&model=N958St&resolution=720x1280&dpi=0&osversion=4.4.4&apilevel=19&city=%E6%B7%B1%E5%9C%B3";
FileDownloader.getImpl().create(url)
.setPath(FileDownloadUtils.getDefaultSaveRootPath())
.setListener(new FileDownloadListener() {
@OverRide
protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) {
if (DEBUG) Log.d(TAG, "pending: soFarBytes " + soFarBytes);
}

                @Override
                protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes, int totalBytes) {
                    if (DEBUG) Log.d(TAG, "connected: soFarBytes " + soFarBytes);
                }

                @Override
                protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) {
                    if (DEBUG) Log.d(TAG, "progress: soFarBytes " + soFarBytes);
                }

                @Override
                protected void blockComplete(BaseDownloadTask task) {
                    if (DEBUG) Log.d(TAG, "blockComplete: soFarBytes url = " + task.getUrl() + ", path = " + task.getPath() + ", name = " + task.getFilename());
                }

                @Override
                protected void retry(final BaseDownloadTask task, final Throwable ex, final int retryingTimes, final int soFarBytes) {
                    if (DEBUG) Log.d(TAG, "retry: soFarBytes " + soFarBytes);
                }

                @Override
                protected void completed(BaseDownloadTask task) {
                    if (DEBUG) Log.d(TAG, "completed: " + task.getFilename());
                }

                @Override
                protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) {
                    if (DEBUG) Log.d(TAG, "paused: soFarBytes " + soFarBytes);
                }

                @Override
                protected void error(BaseDownloadTask task, Throwable e) {
                    if (DEBUG) Log.d(TAG, "error: task " + task + ", e " + e);
                }

                @Override
                protected void warn(BaseDownloadTask task) {
                    if (DEBUG) Log.d(TAG, "warn: task " + task);
                }
            }).start();
}
@FateChen
Copy link

我的也是这个问题。直接执行完成回调。安卓7.0

@Jacksgong
Copy link
Collaborator

Jacksgong commented Jun 16, 2017

@neasakim @FateChen 是有直接回调blockCompletecompleted的场景的,这个场景是要下载的文件已经存在了,此时就直接回调完成了,就是直接回调了这两个接口。


P.S. 如果希望重新下载,可以调用BaseDownload#setForceReDownload(true),让任务从头开始下载。

@FateChen
Copy link

但是文件夹没有!!!

@Jacksgong
Copy link
Collaborator

Jacksgong commented Jun 16, 2017

@FateChen 可以提供下具体的上下文吗,如果启动了下载,下载结束后会做Content-Length与文件大小大小校验,否则是会报错误的。


  • 针对某一个连接这中情况,还是在什么情况下?可否大概说明下。
  • 如果没有特征,可否clone demo project下来,试试看里面的几千个任务的交叉下载等测试,进行调整与复现?

希望能够提供尽可能多的信息便于定位问题,根据你们的使用,无法定位到这个问题。

@Jacksgong
Copy link
Collaborator

@FateChen

目前整个FileDownloader中回调触发完成的只有两个地方:

1. FileDownloadHelper.inspectAndInflowDownloaded

/**
 * @param id              the {@code id} used for filter out which task would be notified the
 *                        'completed' message if need.
 * @param path            if the file with {@code path} is exist it means the relate task would
 *                        be completed.
 * @param forceReDownload whether the task is force to redownload ignore whether the file has
 *                        been exist or not.
 * @param flowDirectly    {@code true} if flow the message if need directly without throw to the
 *                        message-queue.
 * @return whether the task with {@code id} has been downloaded.
 */
public static boolean inspectAndInflowDownloaded(int id, String path, boolean forceReDownload,
                                                 boolean flowDirectly) {
    if (forceReDownload) {
        return false;
    }

    if (path != null) {
        final File file = new File(path);
        if (file.exists()) {
            MessageSnapshotFlow.getImpl().inflow(MessageSnapshotTaker.
                    catchCanReusedOldFile(id, file, flowDirectly));
            return true;
        }
    }

    return false;
}

这里主要是检查,只有文件存在了才会回调完成

2. DownloadStatusCallback#onCompleted

void onCompleted() throws IOException {
    if (handler == null) {
        // direct
        if (interceptBeforeCompleted()) {
            return;
        }

        handleCompleted();
    } else {
        // flow
        handler.sendEmptyMessage(FileDownloadStatus.completed);
    }
}
private boolean interceptBeforeCompleted() {
    if (model.isChunked()) {
        model.setTotal(model.getSoFar());
    } else if (model.getSoFar() != model.getTotal()) {
        onError(new FileDownloadGiveUpRetryException(
                FileDownloadUtils.formatString("sofar[%d] not equal total[%d]",
                        model.getSoFar(), model.getTotal())));
        return true;
    }

    return false;
}

如上面在interceptBeforeCompleted中,如果非chunked文件,会做下载大小的校验。

而对于文件的创建

1. DownloadTaskHunter#prepare

private void prepare() {
    final BaseDownloadTask.IRunningTask runningTask = mTask.getRunningTask();
    final BaseDownloadTask origin = runningTask.getOrigin();

    if (origin.getPath() == null) {
        origin.setPath(FileDownloadUtils.getDefaultSaveFilePath(origin.getUrl()));
        if (FileDownloadLog.NEED_LOG) {
            FileDownloadLog.d(this, "save Path is null to %s", origin.getPath());
        }
    }

    final File dir;
    if (origin.isPathAsDirectory()) {
        dir = new File(origin.getPath());
    } else {
        final String dirString = FileDownloadUtils.getParent(origin.getPath());
        if (dirString == null) {
            throw new InvalidParameterException(
                    FileDownloadUtils.formatString("the provided mPath[%s] is invalid," +
                            " can't find its directory", origin.getPath()));
        }
        dir = new File(dirString);
    }

    if (!dir.exists()) {
        //noinspection ResultOfMethodCallIgnored
        dir.mkdirs();
    }
}

这里进行了父目录的创建,这个是在intoLaunchPool的时候调用的,也就是调用BaseDownloadTask#start以后。

2. DownloadLaunchRunnable#handlePreAllocateFetchDataTask#run

这两个地方都调用了FileDownloadUtils#createOutputStream,这个进行创建文件夹,包括offset。

也就是说一旦建立连接(回调FileDownloadListener#onConnected)之后,就立马会创建对应的文件。


从代码层面以及demo project中进行复现是无法定位到该问题的。针对启动了下载立马回调blockCompletecompleted连文件都没有 可否提供进一步的信息,多谢了。

@FateChen
Copy link

这是我的整个代码:
初始化: FileDownloader.init(this);放在extends MultiDexApplication的类
public static final String APP_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + "/cy/app";
FileDownloader.getImpl().create("https://dldir1.qq.com/music/clntupate/QQMusicSetup.exe")
.setPath(APP_DIR)
.setListener(new FileDownloadListener() {
@OverRide
protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) {
LogUtil.d("downloadAPP pending 等待,已经进入下载队列");
}

                @Override
                protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes, int totalBytes) {
                    LogUtil.d("downloadAPP connected 已经连接上");
                }

                @Override
                protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) {
                    LogUtil.d("downloadAPP  下载进度回调 progress soFarBytes:"+ soFarBytes +",totalBytes:"+totalBytes);
                }

                @Override
                protected void blockComplete(BaseDownloadTask task) {
                    LogUtil.d("downloadAPP blockComplete 在完成前同步调用该方法,此时已经下载完成");
                }

                @Override
                protected void retry(final BaseDownloadTask task, final Throwable ex, final int retryingTimes, final int soFarBytes) {
                    LogUtil.d("downloadAPP retry 重试之前把将要重试是第几次回调回来");
                }

                @Override
                protected void completed(BaseDownloadTask task) {
                    LogUtil.d("downloadAPP completed 完成整个下载过程");
                }

                @Override
                protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) {
                    LogUtil.d("downloadAPP paused 暂停下载");
                }

                @Override
                protected void error(BaseDownloadTask task, Throwable e) {
                    LogUtil.d("downloadAPP error" + e.getMessage());
                }

                @Override
                protected void warn(BaseDownloadTask task) {
                    LogUtil.d("downloadAPP warn");
                }
            }).start();

@FateChen
Copy link

然后就直接执行了直接调用blockComplete和completed 、文件夹为空 里面没有文件。我查看过

@Jacksgong
Copy link
Collaborator

@FateChen 可否交代下遇到该问题的上下文或更详细的用于定位问题的信息。 因为你说 然后就直接执行了直接调用blockComplete和completed 、文件夹为空 里面没有文件。我查看过 这样无法定位到问题,因为我无法从demo中验证该问题,从代码中跟踪不到问题所在。

@FateChen
Copy link

上下文指什么?

@Jacksgong
Copy link
Collaborator

Jacksgong commented Jun 17, 2017

@FateChen 对,现在你提供贴的源码就是上下文。

我创建了一个新的分支 test/reproduce-542 并且在上面使用你的代码进行尝试性的复现你的问题。但是并没有复现,你可以clone下来自己试试,以下是结果分析:

论证结果

该情况的特征是下载文件存储在需要主动请求存储权限的目录/sdcard/cy/目录下。

需要特别注意

manifest中我删除了android:maxSdkVersion="18",原因是demo中默认所有是存储在Environment.getDownloadCacheDirectory()这个目录,这个目录在sdk>=18之后就不再需要申明存储权限了,而sdcard/cy是始终都需要申请读写权限的,因此我删除了这个。

情况一

AndroidManifest中申明了android.permission.WRITE_EXTERNAL_STORAGE这个权限,但是在Android 6.0或以上机型没有主动请求获取该权限,因此等同于没有该权限。

AndroidManifest中没有申明android.permission.WRITE_EXTERNAL_STORAGE这个权限。

这种情况下: 会直接回调FileDownloadListener#error(BaseDownloadTask, Throwable)ThrowableIOException,其Message是: No such file or directory,原因是没有权限,创建/sdcard/cy目录失败。

结果

这个并非BUG,并没有直接返回成功。而是返回了FileDownloadListener#error(BaseDownloadTask, Throwable)

image

情况二

AndroidManifest中申明了android.permission.WRITE_EXTERNAL_STORAGE这个权限,并且主动请求获取到了该权限。

image

结果

这种情况下,成功下载,并且下载的文件存在且完整。
image

@Jacksgong
Copy link
Collaborator

特别值得一提的是,如果希望将 sdcard/cy/app作为一个存储的目录,而非将其作为下载文件的存储路径,可以使用接口: BaseDownloadTask#setPath(path, true),如注释:

/**
 * @param path            The absolute path for saving the download file.
 * @param pathAsDirectory {@code true}: if the {@code path} is absolute directory to store the
 *                        downloading file, and the {@code filename} will be found in
 *                        contentDisposition from the response as default, if can't find
 *                        contentDisposition,the {@code filename} will be generated by
 *                        {@link FileDownloadUtils#generateFileName(String)}  with {@code url}.
 *                        </p>
 *                        {@code false}: if the {@code path} = (absolute directory/filename).
 * @see #isPathAsDirectory()
 * @see #getFilename()
 */
BaseDownloadTask setPath(final String path, final boolean pathAsDirectory);

@Jacksgong
Copy link
Collaborator

也十分感谢这个Issue,这里确实有改进点,在没有权限,并创建父级目录失败后,不应该继续执行接下来的逻辑,直到真正启动下载,开始写入了才发现连父级目录都不存在,此时返回No such file or directoryerror信息不够准确。

针对这个 ,在下一个版本我会优化这里的错误信息,将原本的No such file or directory优化为Create parent directory failed, please make sure you have permission to create file or directory on the path: $path

@Jacksgong Jacksgong added this to the 1.5.6 milestone Jun 17, 2017
@Jacksgong Jacksgong changed the title 直接调用blockComplete和completed It's not clear with the error message 'No such file or directory' when no permission to create the file Jun 17, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants