Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows: How to canonicalize a file to the special folder?

i want to to persist some filenames for the user (e.g. recent files).

Let's use six example files:

  • c:\Documents & Settings\Ian\My Documents\Budget.xls
  • c:\Documents & Settings\Ian\My Documents\My Pictures\Daughter's Winning Goal.jpg
  • c:\Documents & Settings\Ian\Application Data\uTorrent
  • c:\Documents & Settings\All Users\Application Data\Consonto\SpellcheckDictionary.dat
  • c:\Develop\readme.txt
  • c:\Program Files\Adobe\Reader\WhatsNew.txt

i'm now hard-coding path to special folders. If the user redirects their folders, roams to another computer, or upgrades their operating system, the paths will be broken:

i want to be a good developer, and convert these hard-coded absolute paths to relative paths from the appropriate special folders:

  • %CSIDL_Personal%\Budget.xls
  • %CSIDL_MyPictures%\Daughter's Winning Goal.jpg
  • %CSIDL_AppData%\uTorrent
  • %CSIDL_Common_AppData%\Consonto\SpellcheckDictionary.dat
  • c:\Develop\readme.txt
  • %CSIDL_Program_Files%\Adobe\Reader\WhatsNew.txt

The difficulty comes with the fact that there can be multiple representations for the same file, e.g.:

  • c:\Documents & Settings\Ian\My Documents\My Pictures\Daughter's Winning Goal.jpg
  • %CSIDL_Profile%\My Documents\My Pictures\Daughter's Winning Goal.jpg
  • %CSIDL_Personal%\My Pictures\Daughter's Winning Goal.jpg
  • %CSIDL_MyPictures%\Daughter's Winning Goal.jpg

Note also that in Windows XP My Pictures are stored in My Documents:

%CSIDL_Profile%\My Documents
%CSIDL_Profile%\My Documents\My Pictures

But on Vista/7 they are separate:

%CSIDL_Profile%\Documents
%CSIDL_Profile%\Pictures

Note: i realize the syntax %CSIDL_xxx%\filename.ext is not valid; that Windows will not expand those keywords like they are environment strings. i'm only using it as a way to ask this question. Internally i would obviously store the items some other way, perhaps as a CSIDL parent and the tail of the path, e.g.:

 CSIDL_Personal         \Budget.xls
 CSIDL_MyPictures       \Daughter's Winning Goal.jpg
 CSIDL_AppData          \uTorrent
 CSIDL_Common_AppData   \Consonto\SpellcheckDictionary.dat
 -1                     c:\Develop\readme.txt   (-1, since 0 is a valid csidl)
 CSIDL_Program_Files    \Adobe\Reader\WhatsNew.txt

The question becomes, how to use, as much as possible, paths relative to canonical special folders?


I'm thinking:

void CanonicalizeSpecialPath(String path, ref CSLID cslid, ref String relativePath)
{
   return "todo";
}

See also

  • MSDN: CSIDL Enumeration
  • New Old Thing: Beware of roaming user profiles
  • New Old Thing: Beware of redirected folders, too
  • MSDN: PathCanonicalize Function
like image 961
Ian Boyd Avatar asked Jul 05 '10 17:07

Ian Boyd


People also ask

How do I set the path of a file in Windows?

Click the Start button and then click Computer, click to open the location of the desired file, hold down the Shift key and right-click the file. Copy As Path: Click this option to paste the full file path into a document. Properties: Click this option to immediately view the full file path (location).

How do you create a file path?

If you're using Windows 10, hold down Shift on your keyboard and right-click on the file, folder, or library for which you want a link. If you're using Windows 11, simply right-click on it. Then, select “Copy as path” in the contextual menu.

How do I find a file path?

Find File Path The file path is found at the top of the folder or webpage where you are viewing the file. For example: When viewing files in your web browser, the Google Drive file path is at the top of the Google Drive webpage.

How do I find a file path in Windows 10?

Search File Explorer: Open File Explorer from the taskbar or right-click on the Start menu, choose File Explorer and then select a location from the left pane to search or browse. For example, select This PC to look in all devices and drives on your computer, or select Documents to look only for files stored there.


2 Answers

I suppose you could find out how the CSIDL map to paths (using something like SHGetKnownFolderPath), build a reverse dictionary of them, then check whether the beginning of the path you want to store matches any of the keys in the dictionary and then remove the beginning and store the CSIDL that matched.

Not overtly elegant, but it should get the work done.

like image 110
dguaraglia Avatar answered Sep 28 '22 05:09

dguaraglia


function CanonicalizeSpecialPath(const path: string): string;
var
    s: string;
    BestPrefix: string;
    BestCSIDL: Integer;
    i: Integer;
begin
    BestPrefix := ''; //Start with no csidl being the one
    BestCSIDL := 0;

    //Iterate over the csidls i know about today for Windows XP.    
    for i := Low(csidls) to High(csidls) do
    begin
       //Get the path of this csidl. If the OS doesn't understand it, it returns blank
       s := GetSpecialFolderPath(0, i, False);
       if s = '' then
          Continue;

       //Don't do a string search unless this candidate is larger than what we have
       if (BestPrefix='') or (Length(s) > Length(BestPrefix)) then
       begin
          //The special path must be at the start of our string
          if Pos(s, Path) = 1 then //1=start
          begin
             //This is the best csidl we have so far
             BestPrefix := s;
             BestCSIDL := i;
          end;
       end;
    end;

    //If we found nothing useful, then return the original string
    if BestPrefix = '' then
    begin
       Result := Path;
       Exit;
    end;

    {
       Return the canonicalized path as pseudo-environment string, e.g.:

           %CSIDL_PERSONAL%\4th quarter.xls
    }
    Result := '%'+CsidlToStr(BestCSIDL)+'%'+Copy(Path, Length(BestPrefix)+1, MaxInt);
end;

And then there's a function that "expands" the special environment keywords:

function ExpandSpecialPath(const path: string): string;
begin
   ...
end;

which expands:

%CSIDL_PERSONAL%\4th quarter.xls

into

\\RoamingProfileServ\Users\ian\My Documents\4th quarter.xls

It does it by looking for %xx% at the start of the string, and expanding it.

like image 35
Ian Boyd Avatar answered Sep 28 '22 03:09

Ian Boyd