I’m trying to debug an issue on iOS where a call to file.copyFileTo(newFile)
fails.
jassert(newFile.hasWriteAccess());
passes.
The copyFile method:
bool File::copyFileTo (const File& newFile) const
{
return (*this == newFile)
|| (exists() && newFile.deleteFile() && copyInternal (newFile));
}
I have confirmed that *this != newFile
The file does not exist.
deleteFile
returns true.
The debugger does not take me into copyInternal
at all (I have a breakpoint set there).
Any clues?
1 Like
ToxVox
June 22, 2021, 11:49pm
2
I run into the same iOS problem. It works fine on MacOS.
Does anyone have any new information?
ttg
June 23, 2021, 5:48am
3
From where you’re trying to copy? are you using native FileChooser?
If you’re trying to copy a file outside of your app (eg from iCloud/Local Device/Files) you need to use Url and maybe even handle input/output streams yourself.
ToxVox
June 23, 2021, 8:52am
4
Yes, I’m using the native FileChooser.
void FileManager::openFileChooser(
const juce::String& dialogBoxTitle,
const juce::File& initialFileOrDirectory,
const juce::String& filePatternsAllowed,
bool useOSNativeDialogBox,
bool treatFilePackagesAsDirectories,
Component* parentComponent,
int FileChooserFlags)
{
fileChooser.reset (new juce::FileChooser (dialogBoxTitle, initialFileOrDirectory, filePatternsAllowed, useOSNativeDialogBox));
fileChooser->launchAsync (juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectFiles,
[this] (const juce::FileChooser& chooser)
{
compoListeners.call([this](Listener &l)
{
l.fileChanged(this->fileChooser->getURLResult());
});
});
}
And here is the copy function:
void AppSoundControls::fileChanged (juce::URL url)
{
juce::File selectedFile = url.getLocalFile();
if(!selectedFile.exists())
{
DBG(__FUNCTION__ << " - No file selected " << selectedFile.getFullPathName());
return;
}
DBG(__FUNCTION__ << " - Selected file exists: " << selectedFile.getFullPathName());
const juce::File newFile = FileHelpers::getProjectsDir().getChildFile(processor.getProjectState().getProjectName()).getChildFile(Uuid().toString() + "_" + File::createLegalFileName(selectedFile.getFileName()));
jassert(newFile.hasWriteAccess()); // No errors
if(!selectedFile.copyFileTo(newFile))
{
DBG(__FUNCTION__ << " - Copy error: " << newFile.getFullPathName());
return;
}
DBG(__FUNCTION__ << " - Copy success: " << newFile.getFullPathName());
}
As I said, this works on macOS.
ttg
June 23, 2021, 10:50am
5
Sadly on iOS especially with Files supporting ‘cloud’ documents you’ll need to use the InputStream
and write it yourself.
I wish JUCE would have some nice async/threaded mechanism built-in. sadly currently there’s none.
Are you using getURLResults to retrieve the file selected from the FileChooser ? (instead of getResults)
So after you get the inputstream properly, write it to a fileoutputstream in your ‘sandboxed’ / app writable path(s).
1 Like
ToxVox
June 23, 2021, 12:48pm
6
Thanks for your hint @ttg !
It works now on iOS and MacOS:
void AppSoundControls::fileChanged (juce::URL url)
{
auto inputStream = url.createInputStream(false);
if(inputStream == nullptr)
{
DBG(__FUNCTION__ << " - Error: Can't create inputStream ");
return;
}
else
DBG(__FUNCTION__ << " - Ok: inputStream created");
juce::File newFile = FileHelpers::getProjectsDir().getChildFile(processor.getProjectState().getProjectName()).getChildFile(Uuid().toString() + "_" + File::createLegalFileName(url.getLocalFile().getFileName()));
if(!newFile.hasWriteAccess())
{
DBG(__FUNCTION__ << " - Error: No write access ");
return;
}
std::unique_ptr<juce::FileOutputStream> outputStream;
outputStream = newFile.createOutputStream();
outputStream->writeFromInputStream(*inputStream, inputStream->getTotalLength());
}
1 Like
To save someone else a bunch of wasted time debuging what’s wrong, be aware that File::copyFileTo
will succeed on the iOS Similator but not on a real device.
Would be great to have the limitations of these functions documented.
Or even better not make them available on platforms where they don’t work.
2 Likes