Page 1 of 2 12 LastLast
Results 1 to 20 of 30

Thread: Saving a file using a relative path

  1. #1
    Join Date
    Apr 2014
    Posts
    59
    Thanks
    7
    Qt products
    Qt5
    Platforms
    Windows

    Default Saving a file using a relative path

    Hi,

    It is simple enough to open a file with a relative path using QApplication::applicationDirPath(). If I use that for saving, however, I end up with an absolute path. Since I intend to have my program run from a thumb drive, that can be problematic when a different computer is used. I currently have "debug" hard coded as the root, but when I switch to a release build, I will need to find all the instances, and change them. All my searches turn up solutions to opening files, which is not an issue. If someone could suggest a solution, it would be appreciated.

  2. #2
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    4,775
    Thanks
    262
    Thanked 773 Times in 763 Posts
    Qt products
    Qt5
    Platforms
    Windows Android

    Default Re: Saving a file using a relative path

    If you can open a file for reading using a path relative to your application's directory, you can open it for writing the same way. I think you are going to have to post the code that demonstrates the problem you are having because somehow you must be converting the relative path to an absolute path somewhere along the line.
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  3. #3
    Join Date
    Apr 2014
    Posts
    59
    Thanks
    7
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Saving a file using a relative path

    Oops, I guess I did a bad job of explaining.

    In one instance what I am doing is saving the path, as a string, in a QTableView. In that case I have a dialog that opens a QFileDialog to get the path to an image.
    Qt Code:
    1. void AddImageDialog::on_selectBtn_clicked()
    2. {
    3. QString home = "debug";
    4. QString path = QFileDialog::getOpenFileName(this, "Select Imagae", home + "/images",
    5. "Images(*.jpg);;All Files(*.*)");
    6. if(ui->relativeCB->isChecked())
    7. {
    8. QFileInfo relativePath(path);
    9. ui->pathTxt->setText(home + "/images/" + relativePath.fileName());
    10. }
    11. else
    12. ui->pathTxt->setText(path);
    13. }
    To copy to clipboard, switch view to plain text mode 
    The path is then retrieved from pathTxt by the delegate, and put in the model.
    Qt Code:
    1. else if(header == "Image")
    2. {
    3. AddImageDialog *dialog = new AddImageDialog(parent);
    4. dialog->exec();
    5.  
    6. model->setData(index, dialog->getPath());
    7. }
    To copy to clipboard, switch view to plain text mode 
    I then display the image in the cell.

    If I use QApplication::applicationDirPath(), then the path ends up being hard coded to my development directory, witch will break the path, if I move the folder. I have several other, similar, situations scattered throughout various classes, so you can imagine how unmanageable it is.

  4. #4
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    4,775
    Thanks
    262
    Thanked 773 Times in 763 Posts
    Qt products
    Qt5
    Platforms
    Windows Android

    Default Re: Saving a file using a relative path

    One way I avoid this is to not use the QFileDialog::getOpenFileName() static method. Instead, I use the non-static mechanism of creating a QFileDialog instance on the stack and connecting slots to the QFileDialog::fileSelected() and QFileDialog::directoryEntered() signals. I then call QFileDialog::exec() and let the slots handle it.

    In particular, I use the directoryEntered() signal to keep track of where the user last browsed for a file, and can use that location to initialize the dialog the next time I use it. If I want to remember between executions, I use QSettings to put it in an ini file. The first time I run the program, I can choose to either use the applicationDirPath -or- (better) use something appropriate from QStandardPaths.

    An additional remedy is to give the user a way to browse for the directory containing the images if the stored path doesn't exist. You can do that with QFileDialog with FileMode set to Directory and Option set to ShowDirsOnly. Once you get the name of the directory (from the directoryEntered() or maybe fileSelected() signal). You can then update your table to use this for subsequent image loads.
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  5. #5
    Join Date
    Apr 2014
    Posts
    59
    Thanks
    7
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Saving a file using a relative path

    If I understand you correctly, you are suggesting I make the user set a new path every time they switch machines. I also have another column that links to a PDF. While it is not an issue if the PDF is online, it defeats the purpose if it is stored locally.

    Let me give a use case example of what I am trying to accomplish. If I am working on a project in Qt, for example, and want to move to another machine, I just need to move the project folder. In school, we used Android Studio, in a class, and I could not easily work on a project at home, and in class, because the path was hard coded into the project, I forget were. I could put it on a thumb drive and switch, as long as I made sure the root directory was the same, which meant plugging in various USB devices, until it was the same. Alternately, I could go into the setting, somewhere, and change the root manually, each time. Neither of those options were convenient, and making the root the same was the easiest. While making sure the root of the thumb drive is the same, compared to changing, possibly, hundreds of links when switching machines, is one way around it, I want the user to not have to worry about any of that, and have it just work, like a Qt project would.

  6. #6
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    4,775
    Thanks
    262
    Thanked 773 Times in 763 Posts
    Qt products
    Qt5
    Platforms
    Windows Android

    Default Re: Saving a file using a relative path

    If I understand you correctly, you are suggesting I make the user set a new path every time they switch machines.
    No, I am suggesting that you ask the user to browse for the correct path if the path that is saved can't be found on a different machine (or maybe also, if the path exists, the images aren't there).

    We use this method in some of our apps. Most of the information is stored in the app's "project" file, but this file can refer to external files that contain experimental data that is too big or not appropriate to store in the project file. These are stored with absolute paths. When we open the project file, we also try to automatically find the external files. If we can't (eg. we're working on our laptop instead of our desktop PC) we put up a dialog that says, "Can't find XYZ. Do you want to browse for it?" If they say yes and browse to the new location, we remember that and correct the absolute paths to point to the new location so we won't have to ask again. If the user says "no", then you have to go to Plan B, which in our case is to allow the user to do as much processing as possible in the absence of the external data.

    Storing relative paths doesn't avoid this problem, because the relative path on one PC might not be the same (or even exist) on a different PC and you're stuck in the same boat. The only way relative paths work is if everything you need is stored under a single "root" directory, so all you need remember is the directory where your project file lives on the system where you are now using it, and everythings else lives in or beneath that.
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  7. #7
    Join Date
    Apr 2014
    Posts
    59
    Thanks
    7
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Saving a file using a relative path

    OK, I understand what you are saying now. I guess I am still not explaining myself well as that is not really what I am going for. I only need it to work if the file is saved in a folder, inside the application folder, or the one that opens by default in my code. It sounds like my only other option is to store the files as BLOBs in the data base, as opposed to saving the paths as a strings. I might try setting a global is main(), but I am not sure if that will even work.

  8. #8
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    4,775
    Thanks
    262
    Thanked 773 Times in 763 Posts
    Qt products
    Qt5
    Platforms
    Windows Android

    Default Re: Saving a file using a relative path

    I only need it to work if the file is saved in a folder, inside the application folder
    Well, if you're on Windows 7 or 10, this isn't going to work because most users don't have permission to create files in the application's install directory except at install time. Sometimes they aren't even allowed to write to files that already exist there. So using the application directory only works when you're the developer and the application directory is just a normal location on your disk somewhere.

    It is better to use QStandardPaths to find a directory and use that as the default location to create and save project files instead of hard-coding a fixed path in your source code. Using QStandardPaths is basically finding a relative location; it just changes as a function of the PC and user.

    It sounds like my only other option is to store the files as BLOBs in the data base
    If your app won't work if the files aren't present somewhere in the file system, then this is about your only viable solution.
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  9. #9
    Join Date
    Apr 2014
    Posts
    59
    Thanks
    7
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Saving a file using a relative path

    Hi, I missed the notification, so sorry for my late reply.

    Well, if you're on Windows 7 or 10, this isn't going to work because most users don't have permission to create files in the application's install directory except at install time
    I think you are misunderstanding my purpose, I do not intend to use an installer as this is meant to be portable, run on a thumb drive. The user should be able to put the application anyplace they want, or easily move it around. They should have no problem copying a PDF or image into the application folder.

    I am trying to avoid using BLOBs as I do not believe this is how they were intended to be used. Databases are far from my strong point, so I could be mistaken about that.

  10. #10
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    4,775
    Thanks
    262
    Thanked 773 Times in 763 Posts
    Qt products
    Qt5
    Platforms
    Windows Android

    Default Re: Saving a file using a relative path

    You have completely baffled me about what you are trying to accomplish. If you are storing everything on a thumb drive, and everything is stored relative to the directory the program is running in, then why are you having such a hard time getting the current (application) directory and creating paths relative to that?

    If you are storing images or whatever as separate files, then either they are stored relative to the program directory or they aren't. And if they aren't stored in the relative location where you expect them to be then either a) the user has moved or deleted them (or they weren't there to begin with) or b) the relative path you think you are constructing isn't correctly pointing where you think it is.

    So if your app can't find the files it needs, then you have the choice of a) running without them or b) trying to find them in an alternative location. The easiest way to do this is to simply ask the user where he or she put them. If the answer to that is "I deleted them" then you are back to Plan A.

    By the way, if you are using SQLite as a database, BLOBs in the database take basically the same space as a file on disk (and possibly less because there is less file system overhead), so you can guarantee you'll always have the files you need by storing them in a database. Then you just have the problem of finding the database at runtime.
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  11. #11
    Join Date
    Apr 2014
    Posts
    59
    Thanks
    7
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Saving a file using a relative path

    I apologize for not doing a very good job of describing my situation, but I think you hit on the problem with
    then why are you having such a hard time getting the current (application) directory and creating paths relative to that?
    I am storing the path, in the database, as a string(VARCHAR), either absolute, or relative, I am leaving it to the user to select which. So, once it is entered into the database, it basically becomes an absolute path (which is why I cannot use QApplication::applicationDirPath()). For example, this is how I handle it in the model.
    Qt Code:
    1. QVariant TableModel::data(const QModelIndex &idx, int role) const
    2. {
    3. QString header = QSqlTableModel::headerData(idx.column(), Qt::Horizontal).toString();
    4. if(header == "Datasheet")
    5. {
    6. QString link = QSqlTableModel::data(idx, Qt::DisplayRole).toString();
    7.  
    8. switch(role)
    9. {
    10. case Qt::DisplayRole:
    11. return QString();
    12. break;
    13. case Qt::DecorationRole:
    14. if(QStringRef(&link, 0, 4) == "http")
    15. {
    16. QPixmap pixmap(":/Images/Images/internet.png");
    17. return pixmap;
    18. }
    19. else if(link == "")
    20. return QVariant();
    21. else
    22. {
    23. QPixmap pixmap(":/Images/Images/pdf.png");
    24. return pixmap;
    25. }
    26. break;
    27. case Qt::UserRole:
    28. return link;
    29. break;
    30. }
    31. }
    32.  
    33. if(header == "Image")
    34. {
    35. QString link = QSqlTableModel::data(idx, Qt::DisplayRole).toString();
    36.  
    37. switch(role)
    38. {
    39. case Qt::ToolTipRole:
    40. return QString("<img scr='%1'>").arg(link);
    41. break;
    42. }
    43. }
    44.  
    45. return QSqlTableModel::data(idx, role);
    46. }
    To copy to clipboard, switch view to plain text mode 

    And just for completeness, unless I forgot something, the PDFs are handled this way
    Qt Code:
    1. void MainWindow::on_dataView_activated(const QModelIndex &index)
    2. {
    3. QString header = model->headerData(index.column(), Qt::Horizontal).toString();
    4.  
    5. if(header == "Datasheet")
    6. {
    7. QString link = index.data(Qt::UserRole).toString();
    8.  
    9. if(QStringRef(&link, 0, 4) == "http")
    10. QDesktopServices::openUrl(link);
    11. else if(link == "")
    12. return;
    13. else
    14. QDesktopServices::openUrl("file///" + link);
    15. }
    16. }
    To copy to clipboard, switch view to plain text mode 
    Then you just have the problem of finding the database at runtime.
    There is no problem using QApplication::applicationDirPath() for that, since it is not based on a string.

    This has gotten me to thinking that I might be able to use similar mechanics, like I do in the last bit using QStringRef(), to determine if the link has been truncated, to a relative link, in which case I should then be able to use QApplication::applicationDirPath(). I am going to have to think on that a bit.

    <edit> I expect the user to not worry about the relative path part, if they are not interested having the application portable.</edit>
    Last edited by admkrk; 28th June 2017 at 06:15.

  12. #12
    Join Date
    Mar 2008
    Location
    Kraków, Poland
    Posts
    1,525
    Thanked 279 Times in 274 Posts
    Qt products
    Qt4
    Platforms
    Unix/X11 Windows

    Default Re: Saving a file using a relative path

    First of all the application must have a fixed status. So put in main() this line
    Qt Code:
    1. QDir::setCurrent(QCoreApplication::applicationDirPath());
    To copy to clipboard, switch view to plain text mode 
    After that, you can use paths relative to the application directory.

  13. The following user says thank you to Lesiok for this useful post:

    admkrk (3rd July 2017)

  14. #13
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    4,775
    Thanks
    262
    Thanked 773 Times in 763 Posts
    Qt products
    Qt5
    Platforms
    Windows Android

    Default Re: Saving a file using a relative path

    either absolute, or relative, I am leaving it to the user to select which.
    So you seem to have set up a built-in contradiction in use cases: If you store an absolute location in the database, you cannot have a portable app because that absolute location most likely won't exist or have the same content on a different PC. If you want a portable app and portable data, you cannot store an absolute location in the DB.

    I see three options to solving this contradiction:

    1 - The app can't be portable and you are free to store absolute locations in the DB
    2 - The app is portable and stored locations must be relative to the app
    3 - The app is portable but must be able to run with missing files

    Option 2 makes the most sense to me, but it will only work if you either: a) store the files the user needs in the database as BLOBs or b) copy the files the user needs to a known subdirectory of the app. In either case, you have the problem of the original files getting out of sync with the copies. You could set up a file system watcher to watch for changes, but then you're back to the original problem of finding an absolute location. However, if the files are static you may not have to worry about changes. If they aren't static, then you could implement a "scan for changes" feature that lets the user sync up the copies with the originals.
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  15. #14
    Join Date
    Apr 2014
    Posts
    59
    Thanks
    7
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Saving a file using a relative path

    Actually, option 3 is closest to what I have since the user is made aware that if they do not save in the appropriate folder, it will be missing. While BLOBs would work for images, PDFs would be problematic since they can be online, or local. In that case, a link is the only way to do it. For the files to be portable, they are stored in a subdirectory of the application, that is why the file dialog opens up where it does. I could have it open using a standard path and copy it, I do do that elsewhere, but if the user does not want the portability, then they would have two copies, taking up space on their machine.

    If I understand Lesiok's reply(I need to read the documents better and do some experimenting), I need to reverse my process, and just store the filename. then use something like
    Qt Code:
    1. return QString("<img scr='%1'>").arg(home + "/images/" + link);
    To copy to clipboard, switch view to plain text mode 
    except home would be however I get the application path. Or better yet, store it as "/images/" + relativePath.fileName(), which would make determining if it is relative easier. If that will work, that will solve my problem.

  16. #15
    Join Date
    Apr 2014
    Posts
    59
    Thanks
    7
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Saving a file using a relative path

    Thanks guys! It seems to be working now.
    Qt Code:
    1. if(header == "Image")
    2. {
    3. QString link = QSqlTableModel::data(idx, Qt::DisplayRole).toString();
    4.  
    5. switch(role)
    6. {
    7. case Qt::ToolTipRole:
    8. if(QStringRef(&link, 0, 1) == '/')
    9. link.prepend(QDir::current().currentPath());
    10.  
    11. return QString("<img src='%1'>").arg(link);
    12. break;
    13. }
    14. }
    To copy to clipboard, switch view to plain text mode 
    This shows the image regardless of it being absolute, or relative. While I have not tested it on a different drive, I did make a quick app that grabbed a path and opened the link from the same folder on different drives. Although I did not save in between, I see no reason that would be a problem. Now I just need to track down all the instances where I used relative paths.

  17. #16
    Join Date
    Apr 2014
    Posts
    59
    Thanks
    7
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Saving a file using a relative path

    Using
    Qt Code:
    1. QDir::current().currentPath()
    To copy to clipboard, switch view to plain text mode 
    has been working good, until today. In a different class, I noticed it was returning one level up from the application path. It is not a big deal in this case, and easy enough to work around, but I am curious about the inconstancy. As near as I can tell, the difference is using it in the constructor results in it being up a level. Can someone explain why this happens, or point me to an explanation, I am not sure where to look?

  18. #17
    Join Date
    Mar 2008
    Location
    Kraków, Poland
    Posts
    1,525
    Thanked 279 Times in 274 Posts
    Qt products
    Qt4
    Platforms
    Unix/X11 Windows

    Default Re: Saving a file using a relative path

    Result of QDir::current() is not fixed. Apparently somewhere along the way you change current dir.

  19. #18
    Join Date
    Apr 2014
    Posts
    59
    Thanks
    7
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Saving a file using a relative path

    I was using it in a subclass of QStyledItemDelegate, so I could could keep a QSet of strings in a file, as opposed to having to hard code them all (they are used to check if a column should use a completer for editing). I put a qDebud() in one of the functions, I think it was the one that checks the set:
    Qt Code:
    1. bool Delegate::isLineEdit(QString header) const
    2. {
    3. if(completerLines.contains(header))
    4. return true;
    5.  
    6. return false;
    7. }
    To copy to clipboard, switch view to plain text mode 
    It was right in the function, but up one level in the constructor. I do not see how the current directory could have changed. It works fine with this though:
    Qt Code:
    1. Delegate::Delegate(QSqlTableModel *model, QObject *parent) :
    2. QStyledItemDelegate(parent)
    3. {
    4. this->model = model;
    5. filter = new UniqueFilterModel(this);
    6.  
    7. //
    8. QString path = QCoreApplication::applicationDirPath();
    9. path.append("/data/completer.txt");
    10. QFile inFile(path);
    11. if(!inFile.open(QFile::ReadOnly | QFile::Text))
    12. {
    13. // error message
    14. qDebug() << "Error opening file";
    15.  
    16. return;
    17. }
    18.  
    19. QTextStream in(&inFile);
    20. while(!in.atEnd())
    21. {
    22. QString line = in.readLine();
    23. completerLines.insert(line);
    24. }
    25. inFile.close();
    26. }
    To copy to clipboard, switch view to plain text mode 
    But using:
    Qt Code:
    1. QString path = QDir::current().currentPath();
    2. path.append("/data/completer.txt");
    To copy to clipboard, switch view to plain text mode 
    does not work in the constructor.
    Of course that also means I need to include QCoreApplication, which I am sure is not small.

  20. #19
    Join Date
    Mar 2008
    Location
    Kraków, Poland
    Posts
    1,525
    Thanked 279 Times in 274 Posts
    Qt products
    Qt4
    Platforms
    Unix/X11 Windows

    Default Re: Saving a file using a relative path

    QDir::current() and QCoreApplication::applicationDirPath() may be the same but not necessarily. When You create link on desktop to run application You can set working directory (Start in). Default is application directory but You can change this.
    Running application from cmd line You do this
    Qt Code:
    1. cd <APP DIR><enter>
    2. APP_NAME<enter>
    To copy to clipboard, switch view to plain text mode 
    and current dir == app dir
    or
    Qt Code:
    1. <APP DIR>/APP_NAME<enter>
    To copy to clipboard, switch view to plain text mode 
    and current dir != app dir.

  21. #20
    Join Date
    Apr 2014
    Posts
    59
    Thanks
    7
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Saving a file using a relative path

    I think I understand what you are saying, but am not sure how that would affect this since I am not running it from command line.

    I did as you suggested before and used
    Qt Code:
    1. QDir::setCurrent(QCoreApplication::applicationDirPath());
    To copy to clipboard, switch view to plain text mode 
    in main(). Is that not supposed to make QDir::current() constant? The only difference is where in the class I put
    Qt Code:
    1. QString path = QDir::current().currentPath();
    2. path.append("/data/completer.txt");
    To copy to clipboard, switch view to plain text mode 
    If I put it in the constructor, the result is: path_to_Build_folder/data/completer.txt.
    If I put it in my function, the result is: path_to_Build_folder/debug/data/completer.txt.
    The only difference I can see is when it is called. What I do not understand is how the directory is affected by that.

    I am starting to think that having it in the constructor is not a good idea anyway, since the list will not be updated if it is changed somewhere else. I do not really want to do a read every time the user edits a cell either, though.

Similar Threads

  1. Replies: 0
    Last Post: 14th September 2013, 11:14
  2. Replies: 16
    Last Post: 24th January 2013, 03:52
  3. Downloading a file and saving in a path
    By StarRocks in forum Qt Programming
    Replies: 19
    Last Post: 3rd January 2013, 07:43
  4. qmake absolute vs relative LIBS path
    By TheShow in forum Newbie
    Replies: 7
    Last Post: 12th October 2010, 15:40
  5. Replies: 8
    Last Post: 17th October 2009, 09:10

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.