Accessing files from a file server with a cross-platform app
-
This is not directly Qt related, but I'm hoping there might be a clever solution:
In my cross-platform app I need to open a well known (meaning the user doesn't select it) file located on a file server (using QFile).
The file server is running under Windows 2003 server.
Under windows, I can use UNC notation (\FILESRV\myShare\myFile).
Under Linux and Mac, I need to mount the share from what I understand (using smb) , but that would mean the path will be different on each platform.Has anyone had to deal with something like this?
-
You can use next command:
@
smbclient \\FILESERV\myShare --command="get myFile"
@
It will download your remote file to current directory. -
I don't want to use a command line.
I want to use in my code for example
@
QFile file;
file.setFileName( fileName );
if ( !file.open( QIODevice::ReadOnly ) ) { ...}
@
but I need the fileName to be the same for all platforms. -
You can use QProcess to download it from share. I'm not sure you will be able to open remote samba file from linux without mounting samba share.
-
if the file server was something different, would there be a cross-platform solution?
-
I'm not sure that there will be truly cross platform solution. But simple #ifdef sections will help you.
-
The problem is I'd like to have a list of files in a table on a database server (eg. Descr and FilePath).
I would show this table in QTableView, and the user can select one and the app would open it.
-
You can store filePath in one string and for *nix remove filename from it and run QProcess with smbclient
Something like this (not tested, maybe not even compiling)
@
QString path = "\\FILESRV\myShare\myFile";
int lastDelimiterIndex = path.lastIndexOf("\");
QString shareName = path.left(lastDelimiterIndex).replace("\", "\\");
QString fileName = path.mid(lastDelimiterIndex+1);
@
So you will have in shareName name of share (first arg for smbclient) and in fileName you will have file name (part of second arg for smbclient) -
ok... that could work.
what about stuff like nfs? does that work at all? -
Never worked with nfs, so I can't help you in this, sorry.
-
the other option I've been thinking about is to store the files in the database... they aren't that big, but I've been trying to avoid it...
-
Rather than using a windows share/samba where the path on each system is dependent upon where the share is mounted how about using some other protocol such as http or (WebDAV if you need write support too)?
-
Well the problem is that Qt is very limited (to my knowledge) in this respect.
Whereas in Obj-C there are truly great functions where one can easily load the contents of a url (which can be anything) in a string (through functions like initWithContentsOfURL) or file (with fileHandleForReadingFromURL), I'm surprised there are no such functions in the generally rich classes of QString and QFile.The best I've managed to do when I have to do something similar, is use a helper function like
@
QString MyHelperClass::downloadFile(const QString url)
{
QNetworkAccessManager manager;
QEventLoop loop;
QNetworkReply *reply = manager.get(QNetworkRequest(QUrl(url)));
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));loop.exec();
QString fname = QString("%1/tmp_%2")
.arg(QDesktopServices::storageLocation(QDesktopServices::TempLocation))
.arg(QDateTime::currentDateTime().toString("yyyyMMddHHmmss"));if (QFile::exists(fname)) QFile::remove(fname); QFile *tf = new QFile(fname); if (!tf->open(QIODevice::WriteOnly)) { QMessageBox::information(this, tr("Download file"), tr("Unable to save the file %1: %2.").arg(fname).arg(tf->errorString())); delete tf; }; tf->write(reply->readAll()); delete reply; tf->flush(); tf->close(); delete tf; QFileInfo fi(fname); return fi.canonicalFilePath();
}
@
to generate a temporary copy of a file locally, and then use the filename this function returns as an argument to QFile.
I'm pretty sure my implementation is terrible and that's why I would like to avoid this approach, but I haven't been able to come up with something better. -
What do you need to do with the file(s) after you get access to them? Why are you saving it locally in the above example? It would be more efficient just to read from the QNetworkReply into whatever data structure is appropriate.
Also, please be aware that using local event loops like this are (in general) a bad idea. Other events are still processed which could result in your instance of MyHelperClass being deleted, then when your local event loop exits you are into the realms of undefined behaviour.
-
I need an actual filepath in this case, because it is passed to a 3rd party library (which uses QFile open as far as I know).
I'm pretty sure my function does not work without the loop.exec() call, and that's why I added it.That is why I mentioned I'm surprised that there is no helper function to perform such a common ( I would think) task.
-
Your function does not work without the local event loop as you are trying to use QNAM in a synchronous manner when it is designed for asynchronous use. The easiest way is to connect a slot up to the finished() signal of QNetworkReply. Something like this:
@
void MtHelperClass::sendRequest( const QUrl& url )
{
QNetworkRequest request;
request.setUrl( url );QNetworkReply* reply = m_nam->get( request ); connect( reply, SIGNAL( finished() ), SLOT( processReply() ) ); connect( reply, SIGNAL( error( QNetworkReply::NetworkError ) ), SLOT( onReplyError( QNetworkReply::NetworkError ) ) );
}
@where m_nam is a pointer to your application's QNetworkAccessManager. Then in the two referenced slots you either handle the reply as you do in your example function or you handle the error.
There are also other signals that you can connect to. These allow you to show download progrerss for example or to check the http metadata (e.g. response code).
-
But in my case I needed something synchronous....
I tried various things (like putting everything in another thread, and working with signals etc), but it got very complicated. -
Why? Just call your library function when you know the file is ready to be used. You could emit a signal from the slot that proceses your network reply and connect your library function up to that. Waiting for a file to be downloaded will freeze your main thread for potentially a long time depending upon file size/network speed/latency etc.
-
I'm talking about a local area network here, and downloading a file from a fileserver.
The general setup would be overly complicated though:
You'd have put in an intermediary step before using the library open function, where you'd initiate a download to a temporary location, work with signals/slots to return a unique filename, pass it to the library, and then somehow dispose of the temporary file...Unfortunately, there doesn't seem to be a way around it.
I with there was a synchronous function to get files when you know latency is not an issue. -
Well you can use a local event loop as you are doing now, I just mentioned that there are good reasons why this might be a bad idea but that is your call to make.
From what you have described it sounds like you are trying to shoehorn an asynchronous operation into a synchronous paradigm. Fetching a file from a network will always take a very long time in comparison to executing a few instructions but if you can live with that delay blocking your main thread then that's fine.
As for the syntactic sugar of a convenience method to fetch the file for you it looks as if you have most if not all of this already. QNAM provides the low-medium level support for this kind of thing but only you know the specifics of what to do with the fetched data so Qt leaves this to you.
If you feel very strongly that Qt should have such high-level convenience methods then your options are:
File a bug (feature request)
Write it yourself and issue a merge request on Gitorious.
Some convenience methods do already exist such as QDesktopServices::openUrl(). In fact you might be able to use QDesktopServices::setUrlHandler() to provide your convenience method here.