From: Brian Vanderburg II on
I've got a quick question about developing a portable applications. By
portable I mean like portable-apps, can run without being installed and
without 'touching' user account folders.

What is the best way of doing this in wxWidgets. In my application I
currently always create a file configuration and use wxStandardPaths.
For now I'm testing out this approach:

Derive from wxStandardPaths a custom AppPaths object, and override the
needed GetDataDir/GetUserDataDir/etc, and have a custom GuiAppTraits
return the new paths object. By default they simply call
wxStandardPaths::GetDataDir, but if a portable mode flag is set it will
determine the executable path and solve for a data directory/user data
directory like that instead. I may need to override others to get
localizations working. Also, create a configuration file with
wxFileConfig in the user data directory by directly specifying the path
to the configure file.

Also, what is a good way to detect portable mode besides using a
'command line' flag 'myapp --portable' or 'myapp /portable', which would
work but require the user to execute it from a command line interface.

B. Vanderburg II


From: Julian Smart on
Hi Brian,

I've done this with an application (Writer's Cafe 2, currently in beta)
and I didn't override wxStandardPaths, I have my own set of functions
(that use wxStandardPaths for the non-portable case). I also handle path
localization in my own functions, mostly relative to the executable path
or application data path.

I use a local wxFileConfig if in portable mode, as per your method. To
set and detect portable mode, I do the following.

From my Mobile Preferences dialog, the user can run a wizard that
copies the program to the external drive and creates a small
configuration file (also using wxFileConfig) next to the executable. The
configuration file tells the app where the actual settings are located,
relative to the drive root. Now when the user starts the app from the
mobile drive, it deduces the drive location from the executable location
and then the regular settings are loaded from a file in the Application
Data folder on the mobile drive. So from that point on only temporary
files need be created on the fixed drive.

When writing MRU files and other data paths to settings (registry or
file), I convert them to paths relative to the current external drive if
appropriate. I also save the name of the drive so that when reading
settings, all files can be converted back to the correct paths for the
_current_ drive name. This ensures that the paths still work if the
drive changes (and if the operating system changes!)

My mobile setup wizard can copy the app on any of the 3 major platforms
from itself (where it's currently installed), from a set of zip files on
local disk, or from the latest version on the web site (again in zip
form to make it easy to unpack and copy after it automatically downloads
the file). Having the installation done by the app reduces the
opportunity for things to end up in the wrong place, although the
downside is you need at least one conventional installation of the app.
I use the structure:

/Applications/<AppName>/<Platform>/<App Folder>

though equally you could have

/Applications/<Platform>/<AppName>/<App Folder>

or something different. On Windows, the wizard creates a shortcut on the
root of the drive, and on Linux it creates a script which has the same
effect. I haven't been able to create a shortcut on Mac OS.

Note that on Linux you may need to add something like this to /etc/fstab
to allow apps to run from the drive:

/dev/sdb1 /media/usbdisk vfat
rw,shortname=mixed,uid=1000,gid=1000,user,auto,exec 0 0

otherwise nothing shows up as executable (e.g. on a FAT32-formatted drive).

I also have a mode whereby the app is aware of a mobile drive, but the
program itself doesn't have to be run from the drive.

I wouldn't claim this is all trivial to do - especially the wizard and
installation onto the drive - but once done it's a nice capability to have.

Best regards,

Julian

Brian Vanderburg II wrote:
> I've got a quick question about developing a portable applications.
> By portable I mean like portable-apps, can run without being installed
> and without 'touching' user account folders.
>
> What is the best way of doing this in wxWidgets. In my application I
> currently always create a file configuration and use wxStandardPaths.
> For now I'm testing out this approach:
>
> Derive from wxStandardPaths a custom AppPaths object, and override the
> needed GetDataDir/GetUserDataDir/etc, and have a custom GuiAppTraits
> return the new paths object. By default they simply call
> wxStandardPaths::GetDataDir, but if a portable mode flag is set it
> will determine the executable path and solve for a data directory/user
> data directory like that instead. I may need to override others to
> get localizations working. Also, create a configuration file with
> wxFileConfig in the user data directory by directly specifying the
> path to the configure file.
>
> Also, what is a good way to detect portable mode besides using a
> 'command line' flag 'myapp --portable' or 'myapp /portable', which
> would work but require the user to execute it from a command line
> interface.
>
> B. Vanderburg II
>
>
> _______________________________________________
> wx-users mailing list
> wx-users(a)lists.wxwidgets.org
> http://lists.wxwidgets.org/mailman/listinfo/wx-users
>
>


--
Julian Smart, Anthemion Software Ltd.
28/5 Gillespie Crescent, Edinburgh, Midlothian, EH10 4HU
www.anthemion.co.uk | +44 (0)131 229 5306
Tools for writers: www.writerscafe.co.uk
wxWidgets RAD: www.anthemion.co.uk/dialogblocks

From: klaas.holwerda on
Brian Vanderburg II wrote:
> I've got a quick question about developing a portable applications.
> By portable I mean like portable-apps, can run without being installed
> and without 'touching' user account folders.
In my application i always use two environment variables, one MyApp_ROOT
and one HOME or User_Settings.
Sometimes a third for common application libraries/data.
The application is then started via a bat or shell file, settings those
variables to anywhere the user/installer wants them to be.
Even wxFileConfig and wxStandardPaths can be used with those variables
available in the applycation.
All big applications (CAE CAD) work like this. In any case NEVER do
anything close to storing into the registry, portable or not its a
nightmare.
Now in the above case, you can set the variables MyApp_ROOT etc. during
the installation phase, making the myapp.bat.sh file ready to start. Of
course the value can also be detected inside your app, finding the
location of it on the current drive/memory stick.
The main thing is that the user/maintainer of the application has the
freedom to setup your application location they he wants, and is not
forced to do it they way you think he wants. Like we prefer to put all
applications on the central application server, also a sort of portable,
but the location is fixed, while the user changes location/computer, and
so has data files, although they are often stored on a central disk too.
For most windows application this is already a real nightmare to do.
While on Linux/Unix this is in general very easy.

Being this the main part of my job, i am very picky on how applications
are set up :-)
Hope it helps,

Klaas



From: Brian Vanderburg II on
Thanks for the reply. That sounds pretty good. The only app I've
currently tried such on detects portable mode by executable name. If it
contains 'port', then it runs in portable mode, and an NSIS installer
gives the option to install in 'normal' or 'portable' mode, but
different versions can't share data (linux version and msw version would
have different user configuration files)

My normal application startup goes something like:

1. Set app info (app name, vendor name)
2. Create user directories if needed
3. Create configuration object
4. Etc

Seems like from this a good approach might to insert '2. Detect portable
settings', which would set a flag if portable mode is detected or not,
and read in information needed from the portable mode file in the
executable directory.

App::DetectPortableSettings()
{
m_portable = false;

m_exeDir = GetExecutableDir() (using
wxStandardPaths::GetExecutablePath())
if(::wxFileExists(m_exe + "portable.conf"))
{
m_portable = true;

// Find drive location that executable is on
m_driveLocation = ..

wxFileConfig blah(...)
// Read location of real settings/app paths relative to
discovered drive location
// and set up other directory variables
}
}

App::GetDataDir()
{
if(m_portable)
return m_exeDir + "data";
else
return wxStandardPaths::GetDataDir()
}

App::GetUserDataDir()
{
if(m_portable)
return m_userDataDir;
else
...
}

But how do you determine the drive location from an executable. Such as
"K:\blah\blah.exe' is easy since the system has a volume, but
'/mnt/media/blah' is not so easy, perhaps scanning fstab or something.
Also, even on windows, a drive location may not be a drive letter if the
user mounts it as a folder or junction point as is possible for NTFS,
but more likely many users would not do that.



Brian Vanderburg II


Julian Smart wrote:
> Hi Brian,
>
> I've done this with an application (Writer's Cafe 2, currently in
> beta) and I didn't override wxStandardPaths, I have my own set of
> functions (that use wxStandardPaths for the non-portable case). I also
> handle path localization in my own functions, mostly relative to the
> executable path or application data path.
>
> I use a local wxFileConfig if in portable mode, as per your method. To
> set and detect portable mode, I do the following.
>
> From my Mobile Preferences dialog, the user can run a wizard that
> copies the program to the external drive and creates a small
> configuration file (also using wxFileConfig) next to the executable.
> The configuration file tells the app where the actual settings are
> located, relative to the drive root. Now when the user starts the app
> from the mobile drive, it deduces the drive location from the
> executable location and then the regular settings are loaded from a
> file in the Application Data folder on the mobile drive. So from that
> point on only temporary files need be created on the fixed drive.
> When writing MRU files and other data paths to settings (registry or
> file), I convert them to paths relative to the current external drive
> if appropriate. I also save the name of the drive so that when reading
> settings, all files can be converted back to the correct paths for the
> _current_ drive name. This ensures that the paths still work if the
> drive changes (and if the operating system changes!)
>
> My mobile setup wizard can copy the app on any of the 3 major
> platforms from itself (where it's currently installed), from a set of
> zip files on local disk, or from the latest version on the web site
> (again in zip form to make it easy to unpack and copy after it
> automatically downloads the file). Having the installation done by the
> app reduces the opportunity for things to end up in the wrong place,
> although the downside is you need at least one conventional
> installation of the app. I use the structure:
>
> /Applications/<AppName>/<Platform>/<App Folder>
>
> though equally you could have
>
> /Applications/<Platform>/<AppName>/<App Folder>
>
> or something different. On Windows, the wizard creates a shortcut on
> the root of the drive, and on Linux it creates a script which has the
> same effect. I haven't been able to create a shortcut on Mac OS.
>
> Note that on Linux you may need to add something like this to
> /etc/fstab to allow apps to run from the drive:
>
> /dev/sdb1 /media/usbdisk vfat
> rw,shortname=mixed,uid=1000,gid=1000,user,auto,exec 0 0
>
> otherwise nothing shows up as executable (e.g. on a FAT32-formatted
> drive).
>
> I also have a mode whereby the app is aware of a mobile drive, but the
> program itself doesn't have to be run from the drive.
>
> I wouldn't claim this is all trivial to do - especially the wizard and
> installation onto the drive - but once done it's a nice capability to
> have.
>
> Best regards,
>
> Julian
>
> Brian Vanderburg II wrote:
>> I've got a quick question about developing a portable applications.
>> By portable I mean like portable-apps, can run without being
>> installed and without 'touching' user account folders.
>>
>> What is the best way of doing this in wxWidgets. In my application I
>> currently always create a file configuration and use
>> wxStandardPaths. For now I'm testing out this approach:
>>
>> Derive from wxStandardPaths a custom AppPaths object, and override
>> the needed GetDataDir/GetUserDataDir/etc, and have a custom
>> GuiAppTraits return the new paths object. By default they simply
>> call wxStandardPaths::GetDataDir, but if a portable mode flag is set
>> it will determine the executable path and solve for a data
>> directory/user data directory like that instead. I may need to
>> override others to get localizations working. Also, create a
>> configuration file with wxFileConfig in the user data directory by
>> directly specifying the path to the configure file.
>>
>> Also, what is a good way to detect portable mode besides using a
>> 'command line' flag 'myapp --portable' or 'myapp /portable', which
>> would work but require the user to execute it from a command line
>> interface.
>>
>> B. Vanderburg II
>>
>>
>> _______________________________________________
>> wx-users mailing list
>> wx-users(a)lists.wxwidgets.org
>> http://lists.wxwidgets.org/mailman/listinfo/wx-users
>>
>>
>
>

From: Julian Smart on
Hi Brian,

Brian Vanderburg II wrote:
> Thanks for the reply. That sounds pretty good. The only app I've
> currently tried such on detects portable mode by executable name. If
> it contains 'port', then it runs in portable mode, and an NSIS
> installer gives the option to install in 'normal' or 'portable' mode,
> but different versions can't share data (linux version and msw version
> would have different user configuration files)
Right; there should definitely be a 'standard' app data location on the
drive that all versions of an app can use.
> My normal application startup goes something like:
>
> 1. Set app info (app name, vendor name)
> 2. Create user directories if needed
> 3. Create configuration object
> 4. Etc
>
> Seems like from this a good approach might to insert '2. Detect
> portable settings', which would set a flag if portable mode is
> detected or not, and read in information needed from the portable mode
> file in the executable directory.
>
> App::DetectPortableSettings()
> {
> m_portable = false;
>
> m_exeDir = GetExecutableDir() (using
> wxStandardPaths::GetExecutablePath())
> if(::wxFileExists(m_exe + "portable.conf"))
> {
> m_portable = true;
>
> // Find drive location that executable is on
> m_driveLocation = ..
>
> wxFileConfig blah(...)
> // Read location of real settings/app paths relative to
> discovered drive location
> // and set up other directory variables
> }
> }
>
> App::GetDataDir()
> {
> if(m_portable)
> return m_exeDir + "data";
> else
> return wxStandardPaths::GetDataDir()
> }
>
> App::GetUserDataDir()
> {
> if(m_portable)
> return m_userDataDir;
> else
> ...
> }
Yes, this looks good. When in mobile mode I simply set the user data
path to <mobile drive>/Application Data/<App Name>.
>
> But how do you determine the drive location from an executable. Such
> as "K:\blah\blah.exe' is easy since the system has a volume, but
> '/mnt/media/blah' is not so easy, perhaps scanning fstab or
> something. Also, even on windows, a drive location may not be a drive
> letter if the user mounts it as a folder or junction point as is
> possible for NTFS, but more likely many users would not do that.
I use the following to get the available drives:

/// Get available drives, optionally only the removable ones
bool AnthemionFileUtilities::GetAvailableDrives(wxArrayString& paths,
wxArrayString& names, bool removableOnly)
{
#ifdef __WXGTK__
wxArrayString p;

// Enumerate drives in mount directory
wxDir dir;
if (wxDirExists(wxT("/media")))
AnthemionFileUtilitiesGetDirs(wxT("/media"), p);

if (wxDirExists(wxT("/mnt")))
AnthemionFileUtilitiesGetDirs(wxT("/mnt"), p);

names.Clear();
size_t i;
for (i = 0; i < p.GetCount(); i++)
{
// Filter out non-removeable drives
if (!removableOnly || (/* p[i].Find(wxT("floppy")) ==
wxNOT_FOUND && */ p[i].Find(wxT("cdrom")) == wxNOT_FOUND))
{
paths.Add(p[i]);
names.Add(p[i]);
}
}
#else
// From dirctrlg.cpp
extern size_t wxGetAvailableDrives(wxArrayString &paths,
wxArrayString &names, wxArrayInt &icon_ids);

wxArrayString paths1, names1;
wxArrayInt iconIds;

wxGetAvailableDrives(paths1, names1, iconIds);

size_t i;
for (i = 0; i < paths1.GetCount(); i++)
{
#ifdef __WXMSW__
if (!removableOnly || (::GetDriveType(paths1[i]) ==
DRIVE_REMOVABLE))
#endif
#ifdef __WXMAC__
// Don't include root path if we're just looking at removeable
drives
if (!removableOnly || (paths1[i] != wxT("/")))
#endif
{
paths.Add(paths1[i]);
names.Add(names1[i]);
}
}
#endif
return true;
}

and then this to use the available drives to extract the volume from a
filename:

/// Try to get a volume (drive) from the filename, return true if
successful.
bool AnthemionFileUtilities::GetVolumeFromFilename(const wxString&
filename, wxString& volume)
{
#ifdef __WXMSW__
volume = wxFileName(filename).GetVolume();

if (volume.Length() == 1)
volume += wxT(":");

return !volume.IsEmpty();
#else
// Try matching first part of the filename

wxArrayString drives, names;
GetAvailableDrives(drives, names);

// Find the largest volume that matches
wxString lastVol;

size_t i;
for (i = 0; i < drives.GetCount(); i++)
{
wxString substr(filename.Left(drives[i].Length()));
if (substr != wxT("/") && substr == drives[i])
{
if (lastVol.IsEmpty() || substr.Length() > lastVol.Length())
{
volume = drives[i];
lastVol = volume;
}
}
}
return volume.Length() > 0;
#endif
}

I'm sure these could be improved but they work reasonably.

If it still fails to find the drive, I show a dialog prompting for the
drive.

Regards,

Julian