CEF | CEF浏览器客户端功能扩展:实现下载列表功能

1,896 阅读5分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

背景

之前的文章已经实现了浏览器的一些扩展功能,如网页的前进、后退、浏览器的刷新、通过组合快捷键调出控制台窗口、设置cookie等。
这里实现了拓展浏览器的下载功能,实现一个可视化的下载列表,可以控制下载、暂停、删除、打开文件位置、打开文件等功能。

效果图

1659426126968.jpg

功能

  1. 暂停下载: 点击暂停按钮,下载暂停。
  2. 继续下载: 点击继续按钮,下载继续。
  3. 删除下载: 点击删除按钮,删除下载的本地文件。
  4. 更新下载进度: 通过进图条展示下载进度。
  5. 打开文件: 打开下载的本地文件。
  6. 打开文件夹: 打开下载的本地文件所在的文件夹,并选中文件。

具体实现

首先要修改SimpleHandler类,让SimpleHandler继承CefDownloadHandler类,并重新实现OnBeforeDownload方法和OnDownloadUpdated方法。

//simple_handler.h 
class SimpleHandler : public QObject, 
                      public CefClient, 
                      public CefDisplayHandler, 
                      public CefLifeSpanHandler, 
                      public CefLoadHandler, 
                      public CefDownloadHandler //继承CefDownloadHandler类 
{ 
    ……
    virtual CefRefPtr<CefDownloadHandler> GetDownloadHandler() OVERRIDE{return this;} //一定要有
    …… 
    // CefDownloadHandler methods: 
    // 重载OnBeforeDownload和OnDownloadUpdated
    virtual void OnBeforeDownload(CefRefPtr<CefBrowser> browser,
                                  CefRefPtr<CefDownloadItem> download_item,
                                  const CefString &suggested_name,
                                  CefRefPtr<CefBeforeDownloadCallback> callback) override;
                                  
    virtual void OnDownloadUpdated(CefRefPtr<CefBrowser> browser,
                                   CefRefPtr<CefDownloadItem> download_item,
                                   CefRefPtr<CefDownloadItemCallback> callback) override;
    …… 
}

实现OnBeforeDownload方法:
在正式开始下载前会执行OnBeforeDownload方法,在方法中调用callback的Continue,才算执行了下载命令。参数一是下载时文件的建议名称,参数二是下载时是否弹出路径选择窗口,true表示弹出,false表示不弹出。

void ClientHandler::OnBeforeDownload(CefRefPtr<CefBrowser> browser, 
                                     CefRefPtr<CefDownloadItem> download_item, 
                                     const CefString &suggested_name, 
                                     CefRefPtr<CefBeforeDownloadCallback> callback)
{
    callback->Continue(suggested_name, true);
}

实现OnDownloadUpdated方法:
OnDownloadUpdated方法中,我们可以通过download_item参数获取是否完成、是否取消、当前速度、完成度百分比、总大小、已完成大小、开始时间、结束时间、路径、建议名称等信息。通过callback参数实现下载的暂停、取消和继续功能。

void ClientHandler::OnDownloadUpdated(CefRefPtr<CefBrowser> browser, 
                                      CefRefPtr<CefDownloadItem> download_item, 
                                      CefRefPtr<CefDownloadItemCallback> callback)
{
    int64 bytes = download_item->GetCurrentSpeed(); // 下载速度
    int64 totalBytes = download_item->GetTotalBytes();  // 总大小
    int64 reciveBytes = download_item->GetReceivedBytes();  // 完成大小
    int32 id = download_item->GetId();  // id
    QString fileName = QString::fromStdWString(download_item->GetSuggestedFileName());//建议名称
    QString url = QString::fromStdString(download_item->GetURL()); //url
    QString fullPath = download_item->GetFullPath(); // 本地路径
    
    // 插入一条下载
        ...
        
    // 更新下载进度
        ...
    
    // 判断是否下载完成
        ...
    
    // 取消一条下载
        ...
}

踩过的坑:

  • 在正式开始下载前也会执行OnDownloadUpdated方法,甚至OnDownloadUpdated方法是在OnBeforeDownload方法之前执行的。在下载过程中会不断执行OnDownloadUpdated方法。
  • 其中有一个IsInProgress()用来获取是否正在下载。但是我试过,弹出保存框还没有点击保存时,它就返回true。所以不能用它判断是否正在下载过程中。
  • 通过download_item获取建议名称时,有些情况会返回空。

最有意思的是:

  • 当我们点击下载后,会先执行OnDownloadUpdated,然后执行OnBeforeDownload,此时会弹出弹窗Save file。
  • 当我们不管是点击保存还是取消,都会执行若干次OnDownloadUpdated方法,并且每次都可以获取到下载速度、总大小和已完成大小,即使点击了取消,下载速度等值也是有数据的。
  • 在若干次OnDownloadUpdated执行完之后,如果我们之前点击的是取消,download_item->IsCanceled()会返回true。如果我们之前点击的是保存,download_item->IsComplete()会返回true。所以我们没有办法在开始下载前判断是否点击了取消按钮。

解决办法:

  • 当下载速度大于0时,认定它是一次下载,保存它的id和callback。id作为一条下载的唯一标识。(我当初想用download_item作为一条下载的唯一标识,发现同一条下载的download_item值是会变的。)
  • 获取建议名称为空时,通过GetURL()方法获取它的url值,截取url的最后一个反斜杠‘/’后面的字符串作为文件名称。
  • 创建一条下载时:将它的id、callback、文件名、总大小通过信号的方式发送给页面。
  • 更新下载进度时:通过id作为唯一标识,将下载的当前速度、已下载大小或者百分比通过信号的方式发送给页面。
  • 判断是否下载完成:下载完成后才会返回文件下载到本地的路径,通过download_item->GetFullPath()获取。将文件的本地路径通过信号的方式发送给页面。
  • 判断是否取消下载:发送取消下载信号给页面。在页面中添加一个标志位,判断接收到的取消信号是我们点击保存框的取消按钮发送的,还是我们在下载过程中点击删除按钮发送的。如果是我们点击保存框的取消按钮时发送的,我们就要将创建的这条下载删除掉。如果是我们在下载过程中点击删除按钮时执行了callback->Cancel()触发的,就不需要删除这一条下载(因为效果图上没有删除)。
  • 因为我们在创建一条下载时将callback传递给页面了,所以点击页面上的按钮时,直接调用对应的callback的方法就可以实现暂停、继续、取消了。
  • 删除下载就是手动调用callback的取消,如果是删除已经下载完成的,需要将文件从本地删除掉。

删除文件:

QFileInfo fileInfo(filePath);
if(fileInfo.exists())
{
    QFile file(m_filePath);
    file.remove();
}

打开文件:

QFileInfo fileInfo(filePath);
if(fileInfo.exists())
{
    QUrl url = QUrl::fromLocalFile("file:///" + filePath);
    QDesktopServices::openUrl(url);
}

打开文件夹并选中文件:

QProcess *m_processFolder;                  // 打开文件夹进程
m_processFolder = new QProcess(this);

filePath.replace("/", "\"); // 只能识别 "\"
QString cmd = QString("explorer.exe /select,%1").arg(filePath);
m_processFolder->start(cmd);