0

My app presents user with a list of papers (pdf files). They can click to download (via TidHTTP) and display the pdf in a TWebBrowser. If the file is already present it skips the download. This code was working last time i played with this project (fall 2019) but now when i run it on an iPhone i have a problem.

Symptoms: The first paper clicked on will download and then display fine in the TWebBrowser. Any subsequent paper's click on will download (I can tell because i can do a listing of the *.pdf files in my apps documents folder) but cannot be displayed. I trapped the error which occurs when i point the TWebBrowser to the file with Form1->WebBrowser1->URL = "file://" + LFileName;. the error is, "The specified file was not found". It IS there because i can do i directory listing on it.

If i kill the app and restart it, then go back and click on one of the previously clicked on papers (that did not display) it opens fine and displays in the TWebBrowser. That really makes me think it is some sort of file lock issue because the file is present.

Here is the code:

void showPaper()
{
   // paperName (e.g. 22.pdf)

   UnicodeString LFileName = System::Ioutils::TPath::Combine(System::Ioutils::TPath::GetDocumentsPath(), paperNAME);
   if (!FileExists(LFileName)) { // file is not present so download it  
    UnicodeString URL = pdfURLv4 + paperNAME;  
    TFileStream* fs = new TFileStream(LFileName, fmCreate);
    Form1->Download->ConnectTimeout = 15000;  // give it 15 seconds
    Form1->Download->ReadTimeout = 15000;
    Form1->Download->Request->BasicAuthentication = true;
    Form1->Download->Request->Username = "XXXXXX";
    Form1->Download->Request->Password = "YYYYYY";
    Form1->Download->Request->UserAgent = "Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0";
    try
    {
        Form1->Download->Get(URL, fs);
        Form1->Download->Disconnect();  // make sure socket is closed
    }
    catch(const System::Sysutils::Exception &)
    {
        try
        {
         UnicodeString URL = pdfURLv6 + paperNAME;  // the v6 url has brackets [] around host
         Form1->Download->Get(URL, fs);
         Form1->Download->Disconnect();  
        }
        catch(const System::Sysutils::Exception &)
        {
            ShowMessage(L"No/poor internet connection.");
            Form1->Download->Disconnect();  
            delete fs;
            return;
        }
    }
    delete fs;
   } // end of download if block


  if (FileExists(LFileName))    // have the file so open it
   {
   try 
   {
    Form1->WebBrowser1->URL = "file://" + LFileName;
   }
   catch ( const Exception& e )
   {
    ShowMessage(e.Message);
   }
    ShowMessage(Form1->WebBrowser1->URL);
  }
} // end of showPaper()

When the error occurs the message caught is (on iPhone running 13.3):

enter image description here

The ShowMessage that displays the Form1->TWebBrowser1->URL gives this which is correct:

enter image description here

Am i not closing out the TFileStream properly? The fact that i can kill the app, restart and view the file lets me know the file is getting properly downloaded. Plus, the first time through the code fully works (downloads and then displays in the TWebBrowser). It is only on subsequent attempts that require download before display that it has this "file not found" problem.

EDIT: Now i create a clone of the TWebBrowser WebBrowser1 that i call myW. It works to display the pdf but then i can't figure out how to delete it properly.

Here is my code for creating it and displaying the pdf:

  if (FileExists(LFileName))    // have the file so open it
   {
   try 
   {
   TWebBrowser *myW;
   myW = new TWebBrowser(Form1->Panel3);
   myW->Parent = Form1->Panel3;
   myW->Align = TAlignLayout::Client;
   myW->URL = "file://" + LFileName;
   myW->Visible = true;
   }
   catch ( const Exception& e )
   {
    ShowMessage(e.Message);
   }
  }

Here is my attempt at deleting it:

TComponent *T;
T = Form1->Panel3->Components[0];  // myW is only thing on Panel3
T->Free();  // not working
// T->DisposeOf(); // did not work

EDIT2: Attempt at disposing of temporary TWebBrowser:

I create the TWebBrowser like this (and it works fine to display pdf):

   TWebBrowser *myW;
   myW = new TWebBrowser(Form1->Panel3);
   myW->Parent = Form1->Panel3;
   myW->Align = TAlignLayout::Client;
   myW->URL = "file://" + LFileName;  // displays the pdf
   myW->Visible = true;

Then i try to dispose of it like this but doesn't work:

TComponent *T;
for (int i = 0; i < (Form1->Panel3->ComponentCount); i++) {
   T = Form1->Panel3->Components[i];
    if (TWebBrowser* TB = dynamic_cast<TWebBrowser*>(T))  {
        Form1->Panel3->RemoveComponent(TB);
        TB->Parent = nullptr;
        TB = nullptr;
        break;
      }
    }
}

I don't get any errors, i just can't load the 2nd pdf (getting that file not found error still). I'm using the cast because i can't access T->Parent.

relayman357
  • 793
  • 1
  • 6
  • 30
  • 1
    Your use of `TFileStream` is fine (though your error handling needs work). This sounds more like a `TWebBrowser` bug. – Remy Lebeau Feb 03 '20 at 20:35
  • Thanks Remy. Instead of using a `TWebBrowser` on my form. Can you tell me how i might just create it and destroy it for each use? Would it just be: `TWebBrowser *myBrowser = new TWebBrowser` and when finished viewing i would `delete myBrowser`? I agree on my lack of skill - thank goodness i don't have to make a living at this. ;-) – relayman357 Feb 03 '20 at 20:47
  • Why are you using `TWebBrowser` to display your PDFs at all? Why not simply ask the OS to display a PDF file in whatever reader the user has installed? – Remy Lebeau Feb 03 '20 at 20:49
  • I do on the android version, because I have to ([see here](https://stackoverflow.com/questions/58827928/open-local-pdf-file-android)). I don’t on iOS because the TWebBrowser (until now) has worked fine - and it works even if the user does not have a PDF viewer installed. – relayman357 Feb 03 '20 at 21:52
  • Remy, i'm trying a different approach. Now i clone WebBrowser1 and use the clone (named myW) to display the pdf. But, i can't succesfully get rid of the clone after viewing the pdf. See my Edit above. – relayman357 Feb 04 '20 at 00:59
  • 1
    `T->Free();` will not work under ARC, and neither will `delete T;` (which is what you should be using in C++). Calling `Free()`/`delete` under ARC is the same as if you had just done `T = nullptr;` instead - it will simply remove `T`'s reference to the `TWebBrowser` but will not remove `Panel3`'s references as the `Owner` and `Parent`. `T->DisposeOf()` should work, though. I would suggest being a little more graceful about releasing the references, whereas `DisposeOf()` is more brute force: `T->Parent = nullptr; Form1->Panel3->RemoveComponent(T); T = nullptr;` – Remy Lebeau Feb 04 '20 at 01:11
  • Remy, see my temporary answer below. I can't seem to dispose of it properly. – relayman357 Feb 04 '20 at 15:43
  • 1
    what you posted is not an answer to your problem. You should have posted it as an [edit] to your question instead. Please do so and delete the answer. – Remy Lebeau Feb 04 '20 at 15:47
  • Ok, i changed it. Same symptoms as before - if i kill the app and restart i can view any of the PDF files that are present. That makes me think the `TWebBrowser` code works ok, and that some weird file lock is present after a download. – relayman357 Feb 04 '20 at 16:04
  • Ok, i used a NetHTTPClient component to do the download as a test (completely cutting out the TidHTTP indy component). I'm getting the SAME problem. The first file downloaded can be displayed in the `TWebBrowser` but subsequent that require download first can't (file not found) unless i kill/restart the app. – relayman357 Feb 04 '20 at 16:32
  • 1
    Obviously, the problem is with the `TWebBrowser` itself. Which is why I strongly recommend you not use it at all to display your PDFs, let the OS display them for you. I'm sure iOS has a way to do that. Windows certainly does (`ShellExecute()`). If the user does not have a PDF viewer installed, oh well. Make one a requirement for your app. – Remy Lebeau Feb 04 '20 at 18:13
  • ok, will do. thank you Remy. Surely letting iOS display the pdf can't be as hard (for me) as it was [with Android](https://stackoverflow.com/questions/58827928/open-local-pdf-file-android). – relayman357 Feb 04 '20 at 18:20
  • Interestingly enough, the book [Delphi Cookbook: Recipes to master Delphi for IoT integrations, cross-platform, mobile and server-side development, 3rd Edition](https://www.amazon.com/Delphi-Cookbook-integrations-cross-platform-server-side/dp/1788621301) on page 551-553 shows opening a pdf file in iOS with the `TWebBrowser`. – relayman357 Feb 04 '20 at 18:47
  • 1
    If a single instance of `TWebBrowser` can't load multiple files, then that is clearly a bug. Feel free to [report it to Embarcadero](https://quality.embarcadero.com). On the other hand, you really need to verify your dynamic `TWebBrowser` is actually getting destroyed properly. Try deriving a class from `TWebBrowser` and override its destructor, make sure it is getting called when you expect. – Remy Lebeau Feb 04 '20 at 18:52
  • Sorry to try your patience (you have more than anyone i know), but i don't know how to do that (derive class and override destructor). I've got my code working by copying the downloaded file to a dummy.pdf file and then opening it with a created `TWebBrowser`. I'll post as unaccepted answer. – relayman357 Feb 04 '20 at 20:30
  • Another point, the `TWebBrowser` could open multiple files no problem - as long as they already existed in my app documents folder. It is only when my code had to download the file first that the problem appears (at attempt to load next pdf). I'm stumped, but that is no surprise i'm sure. – relayman357 Feb 04 '20 at 20:40
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/207222/discussion-between-remy-lebeau-and-relayman357). – Remy Lebeau Feb 04 '20 at 22:34

1 Answers1

0
void showPaper()
{
// paperName (e.g. 22.pdf)

   UnicodeString LFileName = System::Ioutils::TPath::Combine(System::Ioutils::TPath::GetDocumentsPath(), paperNAME);
   if (!FileExists(LFileName)) { // file is not present so download it  
   UnicodeString URL = pdfURLv4 + paperNAME;  
   TFileStream* fs = new TFileStream(LFileName, fmCreate);
   Form1->Download->ConnectTimeout = 15000;  // give it 15 seconds
   Form1->Download->ReadTimeout = 15000;
   Form1->Download->Request->BasicAuthentication = true;
   Form1->Download->Request->Username = "XXXXXX";
   Form1->Download->Request->Password = "YYYYYY";
   Form1->Download->Request->UserAgent = "Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0";
   try
   {
    Form1->Download->Get(URL, fs);
    Form1->Download->Disconnect();  // make sure socket is closed
   }
   catch(const System::Sysutils::Exception &)
   {
    try
    {
     UnicodeString URL = pdfURLv6 + paperNAME;  // the v6 url has brackets [] around host
     Form1->Download->Get(URL, fs);
     Form1->Download->Disconnect();  
    }
    catch(const System::Sysutils::Exception &)
    {
        ShowMessage(L"No/poor internet connection.");
        Form1->Download->Disconnect();  
        delete fs;
        return;
    }
}
delete fs;
} // end of download if block



//////// this gets around the wierd file lock issue with TWebBrowser
 UnicodeString TFileName = System::Ioutils::TPath::Combine(System::Ioutils::TPath::GetDocumentsPath(), "dummy.pdf");
 if (FileExists(TFileName)) {
  TFile::Delete(TFileName); // delete it if present
 }
 TFile::Copy(LFileName, TFileName);
//--------------------------------------






if (FileExists(LFileName))    // have the file so open it
 {
 try 
 {
   TWebBrowser *myW;
   myW = new TWebBrowser(Form1->Panel3);
   myW->Parent = Form1->Panel3;
   myW->Align = TAlignLayout::Client;
   myW->URL = "file://" + TFileName;
   myW->Visible = true;
 }
 catch ( const Exception& e )
 {
  ShowMessage(e.Message);
 }
  ShowMessage(Form1->WebBrowser1->URL);
 }
} // end of showPaper()

And after i show the file and user hits close button i do this to get rid of the temporary TWebBrowser:

TComponent *T;
for (int i = 0; i < (Form1->Panel3->ComponentCount); i++) {
   T = Form1->Panel3->Components[i];
    //if (T->ClassName() == "TWebBrowser") {
    if (TWebBrowser* TB = dynamic_cast<TWebBrowser*>(T))  {
        Form1->Panel3->RemoveComponent(TB);
        TB->Parent = nullptr;
        TB = nullptr;
        break;
      }
}
relayman357
  • 793
  • 1
  • 6
  • 30
  • 1
    You don't need `if (T->ClassName() == "TWebBrowser")` at all. `dynamic_cast` returns `nullptr` if the cast fails, use that instead. You are also not `break`'ing from the loop when you find the `TWebBrowser` – Remy Lebeau Feb 04 '20 at 15:49