There are some folder with more than 100 files on it.
But all files and folders names broken with wrong encoding names (UTF->ANSI).
"C:\...\Госдача-Лечебни корпус\вертолетка\Госдача-Лечебни корпус\Госдача-Лечебни корпус\вертолетка\Госдача-Лечебни корпус\вертолетка\Госдача-Лечебни корпус\Госдача-Лечебни корпус\Госдача-Лечебни корпус\вертолетка\Госдача-Лечебни корпус\Госдача-Лечебни корпус\вертолетка\Госдача-Лечебни корпус\..."
Regular function Utf8ToAnsi finxing it, but FindFirst can't search folders with names longer than 255 symbols.
It gaves me only 70/100 files.
FindFirst wraps the Win32 API function FindFirstFile, and the Unicode version of that function can search paths up to 32,767 characters long if you prepend \\?\ to the path you're passing in, like \\?\C:\Folder\Folder\*.
Since Delphi 2009 and newer call the Unicode functions for you, you can just use FindFirst and co there. For Delphi 2007 and earlier (ANSI versions), you'll need to call FindFirstFile/FindNextFile/FindClose from Windows.pas directly. For more information check the Naming a file section of the platform SDK.
Do note that using \\?\ disables various bits of path processing though, so make sure it's a fully qualified path without any '.' or '..' entries. You can use the same trick to open file streams, rename, or copy files with longer paths.
The shell (Explorer) doesn't support this though, so you still need to limit those to at most MAX_PATH characters for things like SHFileOperation (to delete to the recycle bin) or ShellExecute. In many cases you can work around the problem by passing in the DOS 8.3 names instead of the long ones. FindFirst's TSearchRec doesn't expose the short names, but FindFirstFile's TWin32FindData structure does as cAlternateFileName.
Change the current directory (ChDir) to the deepest one you know about, and then pass a relative path to FindFirst or FindFirstFile.
No path component in that file name is longer than MAX_PATH characters, so you should be able to work your way into the directories one step at a time.
Beware that multithreaded programs may be sensitive to changes in the current directory since a process has only one current directory shared by all the threads.
Related
I am trying to get this short script to work. All it should do is take the contents of the folder in Application Support/App of the local user and move it to a usb.
set localLocation to POSIX path of (path to library folder from user domain) & "Application\\ Support/myApp" as text
set usbLocation to "/Volumes/myUsb/myApp"
tell application "Finder" to move entire contents of folder localLocation to folder usbLocation
There is a folder available in both directories (USB & Computer) which match the custom path description described here as "myApp". The code has an error in the first section of the tell, where it is unable to locate the folder in the Application Support. I am not sure why this is the case and I also am struggling to understand the difference between HFS and POSIX, which one is meant to be used? I have a feeling there is a possible concatenate error with types. I am a beginner, please help.
Thankyou!
Understanding how AppleScript deals with file and folder references is a headache, and it's very well known to be convoluted and fussy about file specifiers and path strings.
Simple definitions:
Posix path: A path made up of forward slashes that separate folders in a directory tree, e.g. /Users/CK/Downloads/Some_File.txt.
The root (top-level) directory is represented by a single forward
slash, /. Every posix path is descended from this root directory,
so, generally speaking, posix paths must start with a forward slash.
Folder paths ideally end with a terminating forward slash; file paths
do not; but this is not strict.
HFS path: A path made up of colons that separate folders in a directory tree, e.g. Macintosh HD:Users:CK:Downloads:Some_File.txt.
The root directory is represented by Macintosh HD:, which always
begins an HFS path (unless your system hard drive is called something
else). Folder paths ideally end with a terminating colon; file paths
do not; this is semi-strict.
That's literally all you need to know about posix paths and HFS paths to do some AppleScripting. The tough part is getting to grips with AppleScript's file class types. There are three:
alias: These are your friends, and are the most versatile when used with either Finder or System Events, and when referring to
either files or folders. They are an AppleScript object that
contain a file reference that points to an existing file. Trying
to assign an alias class type to a file reference of a file that
does not exist will throw an error.
file: These are a file reference object that may point to a file that doesn't exist (yet). It qualifies an HFS path string,
identifying it as not just a piece of text, but a path to a file. The
key difference between file and alias is that file is a static
reference, meaning that if it points to a file with a given name, and
that file's name changes, the file object will now reference a file
that does not exist. An alias, on the other hand, dynamically
shifts its reference even after a file is renamed or moved.
POSIX file: If you like using posix paths (and you should), the POSIX file object is your best friend. It takes a posix path
string and qualifies it as a path to a file in much the same way
file does for HFS path strings. Likewise, it is a static reference
and can point to files or folders that don't exist.
My recommendation
My feeling is that HFS paths are an unnecessary complication to what could have been an easy inter-formulation between command line users and AppleScripting. They do confuse people, and since the notation is so widely observed through the results returned by an AppleScript, they appear mandatory in their use.
They are not. I, personally, never use HFS paths. (Almost never).
One isn't intrinsically better than the other, and it is really just a personal preference. So my advice is to pick one style that you're most comfortable with, and learn how to manipulate that type of path in the AppleScript setting.
As I use posix paths, I concern myself with POSIX file objects, and alias objects. That's what I'll focus on now for the rest of this answer.
Your script
Your script threw its first error because you attempted to escape the spaces within the file path string, possibly a habit from typing in a terminal. You don't need to escape any characters in a file string, except for the backslash \, as file path strings will always be enclosed within double quotes.
Still addressing the first line of your script, you have quite correctly retrieved the path to the library folder (which returns an alias); turned it into a posix path string; then appended the "Application Support/myApp" path text. However, it may be helpful to know that you can actually get the path to the application support folder, so your first line can become:
set localLocation to POSIX path of (path to application support from user domain) & "myApp"
The result is a posix path string, but not a file object (but that's fine at the moment).
Your next line then explicitly declares a posix path string:
set usbLocation to "/Volumes/myUsb/myApp"
which is absolutely fine.
The bit that comes next is the tricky stuff: working with Finder:
tell application "Finder" to move entire contents of ¬
folder localLocation to folder usbLocation
That said, you had it almost perfect. Because you're using posix paths, you either need to turn them into alias objects, or explain to finder that it's dealing with a posix path, which is what the POSIX file object does.
To create a POSIX file object, you can either prepend the POSIX file specifier to the posix path string like this:
set usbLocation to POSIX file "/Volumes/myUsb/myApp"
or do a more conventional coercion into a type class like this:
set usbLocation to "/Volumes/myUsb/myApp" as POSIX file
In either case, AppleScript turns this into a file object and converts it to an HFS path. If you run that line just above by itself, AppleScript returns this:
file "Macintosh HD:Volumes:myUsb:myApp"
So, already, we can see there's an equivalence between file objects and HFS paths versus POSIX file objects and posix paths.
You can choose to edit lines 1 and 2 of your script to construct these POSIX file objects straight away; or you can do it at the point where you are interacting with Finder:
tell application "Finder" to move entire contents of ¬
folder (localLocation as POSIX file) to folder (usbLocation as POSIX file)
If doing it here, you have to coerce using as, possibly because prepending it as an object specifier interferes with folder in the role of an object specifier.
That should successfully move all the files into their new location. The other way you could do it is by moving the folder instead of its contents, which is probably quicker if there are many files and sub-directories. Just change the destination path accordingly:
set localLocation to POSIX path of (path to application support folder from user domain) & "myApp" as POSIX file
set usbLocation to POSIX file "/Volumes/myUsb/"
tell application "Finder" to move folder localLocation to folder usbLocation with replacing
(the with replacing is because the folder myApp in the destination will be getting replaced by the folder myApp coming from your hard drive.)
Finally, for completeness, I'll briefly talk about alias objects:
Alias objects
Constructing alias objects from posix paths is fairly simple, and requires two steps. Firstly, you construct the POSIX file object as I showed you above. Then, you coerce this object into an alias. It can be done on one line:
set usbLocation to POSIX file "/Volumes/myUsb/myApp" as alias
or
set usbLocation to "/Volumes/myUsb/myApp" as POSIX file as alias
Remember, however, that alias objects point to files and folders that already exist. Therefore, there will be some instances where you can declare your POSIX file object immediately from a posix path string that points to a non-existent file that, for example, you will be creating. But you can only coerce it into an alias after the file has been created.
One reason you might want to use alias objects is because they are dynamic, as I mentioned before (or, they should be). So if you set localLocation as an alias object at the start, then moved the folder to its destination as in my very last Finder example, the alias object should now automatically point to /Volumes/myUsb/myApp/ (although it doesn't always work successfully, as one of the many bugs AppleScript has).
The other, more important, reason to use alias objects is that they translate directly between the different applications AppleScript interacts with: not all utilise POSIX file objects, and some may not utilise HFS paths; but they virtually all know what an alias object is.
This is key when switching between Finder and System Events for file handling. Generally speaking, file and folder handling is best done with System Events rather than Finder. But, there are a couple of things you can only do in Finder: set a file tag (label index); get the selected files (selection); and know which target folder has focus at the present time (insertion location).
Retrieving the selection in Finder returns Finder-specific classes, namely document files. One cannot utilise these objects with System Events. However, the selection as alias list is something that can be manipulated by System Events:
tell application "Finder" to set S to selection as alias list
tell application "System Events" to get properties of item 1 of S
Finally, the third reason to utilise alias objects is because, for some reason, retrieving files as alias objects is noticeably faster than retrieving them as any other class object. Testing this on the contents of my downloads folder (which is small enough to not keep me waiting, but large enough to get Finder working), the results were:
tell application "Finder" to get entire contents of ¬
(path to downloads folder) as alias list
-- 1.45s
tell application "Finder" to get entire contents of ¬
(path to downloads folder)
-- 2.29s
They are identical statements, with the exception of the coercion to an alias list (a list of alias file objects) in the first. The speed benefit will be more noticeable on larger folders.
Utilising alias objects with Finder is a little easier/simpler than with file or POSIX file objects, because alias objects don't make a distinction between files and folders in the way that the other two do. In your script, it's necessary with Finder to use the folder specifier with the POSIX file objects; with an alias object, you don't need to:
set localLocation to POSIX path of (path to application support folder from user domain) & "myApp" as POSIX file as alias
set usbLocation to POSIX file "/Volumes/myUsb/" as alias
tell application "Finder" to move localLocation to usbLocation with replacing
I disagree to be discouraged from using HFS paths.
If you have to use the Finder it's the most convenient way since the Finder doesn't accept POSIX paths.
To address a folder on the top level of an external volume there is another short way in Finder terminology : folder "foo" of disk "bar".
set localLocation to (path to application support folder from user domain as text) & "myApp"
tell application "Finder" to move entire contents of folder localLocation to folder "myApp" of disk "myUsb"
However entire contents in Finder can be extremely slow. The shell is much faster.
set localLocation to POSIX path of (path to application support folder from user domain) & "myApp/"
set usbLocation to "/Volumes/myUsb/myApp"
do shell script "/bin/cp " & quoted form of localLocation & "*" & space & quoted form of usbLocation
I have a program that downloads some files from Internet. The file name could be very long (100 chars).
The user may choose to save these these files in a folder that has a very long name (200 chars).
So, the total length of the path is over 260 chars.
I tried to create a function that checks if the path is too long and truncates the filename so the whole path will be exactly 260 chars. But functions in TPath class fails to work if the path is over 260 chars. More exactly GetDirectoryName raises a specific error when the path is over 260 chars, so I cannot use it to split the folder from file name.
A major design flaw in Delphi?
I think that TPath raising an error when working on long file names is a big flaw. Simply using GetDirectoryName to READ (not to write) will simply crash your program. TPath should allow processing long paths. MAYBE it should raise the error ONLY when you try to WRITE files with long path. But not even then. NTFS accepts long paths. So, why Delphi should force you to stick to 260? Some programs can handle longs paths. For example, I use Total Commander (never Explorer) so I am not affected by the long file name issue.
Any idea on how to treat this case?
Note: The download process is automated so I won't stop to ask the user to enter a new file name for each file that fails to be under 260 chars. I want to handle this silently.
Personally, my opinion is that TPath is simply wrong here. To assert that Windows paths cannot be greater than 260 characters is simply denying reality. What's more, to deny you the ability to perform text processing on paths is really quite inexplicable. In my opinion then, TPath should be avoided.
Which leads you back to the good old days. You can call ExtractFileDir from SysUtils. It works as well as it ever did.
I need to make my delphi app able to check if a file copied using Robocopy is there or not when its path exceeds 255 characters.
I have tried the usual "If FileExists(MyFile) then ... " but it always returns "false" even if the file is there.
I also tried to get the file's date but I get 1899/12/30 which can be considered as an empty date.
A File search does not return anything either.
Prefix the file name with \\?\ to enable extended-length path parsing. For example you would write
if FileExists('\\?\'+FileName) then
....
Note that this will only work if you are calling the Unicode versions of the Win32 API functions. So if you use a Unicode Delphi then this will do the job. Otherwise you'll have to roll your own version of FileExists that calls Unicode versions of the API functions.
These issues are discussed in great length over on MSDN: Naming Files, Paths, and Namespaces.
I have some compression components (like KAZip, JVCL, zLib) and exactly know how to use them to compress files, but i want to compress multiple folders into one single archive and keep folders structure after extract, how can i do it?
in all those components i just can give a list of files to compress, i can not give struct of folders to extract, there is no way (or i couldn't find) to tell every file must be extracted where:
i have a file named myText.txt in folder FOLDER_A and have a file with same name myText.txt in folder FOLDER_B:
|
|__________ FOLDER_A
| |________ myText.txt
|
|__________ FOLDER_B
| |________ myText.txt
|
i can give a list of files to compress: myList(myText.txt, myText.txt) but i cant give the structure for uncompress files, what is best way to found which file belongs to which folder?
The zip format just does not have folders. Well, it kinda does, but they are kind of empty placeholders, only inserted if you need metadata storage like user access rights. But other than those rather rare advanced things - there is no need for folders at all. What is really done - and what you can observe opening zip file in the notepad and scrolling to the end - is that each file has its path in it, starting with "archive root". In your exanple the zip file should have two entries (two files):
FOLDER_A/myText.txt
FOLDER_B/myText.txt
Note, that the separators used are true slashes, common to UNIX world, not back-slashes used in DOS/Windows world. Some libraries would fix back-slashes it for you, some would not - just do your tests.
Now, let's assume that that tree is contained in D:\TEMP\Project - just for example.
D:\TEMP\Project\FOLDER_A\myText.txt
D:\TEMP\Project\FOLDER_B\myText.txt
There are two more questions (other than path separators): are there more folders within D:\TEMP\Project\ that should be ignored, rather than zipped (like maybe D:\TEMP\Project\FOLDER_C\*.* ? and does your zip-library have direct API to pack the folders wit hall its internal subfolder and files or should you do it file by file ?
Those three questions you should ask yourself and check while choosing the library. The code drafts would be somewhat different.
Now let's start drafting for the libraries themselves:
The default variant is just using Delphi itself.
Enumerate the files in the folder: http://docwiki.embarcadero.com/CodeExamples/XE3/en/DirectoriesAndFilesEnumeraion_(Delphi)
If that enumeration results in absolute paths then strip the common D:\TEMP\Project from the beginning: something like If AnsiStartsText('D:\TEMP\Project\', filename) then Delete(filename, 1, Length('D:\TEMP\Project\'));. You should get paths relative to chosen containing place. Especially if you do not compress the whole path and live some FOLDER_C out of archive.
Maybe you should also call StringReplace to change '\' into '/' on filenames
then you can zip them using http://docwiki.embarcadero.com/Libraries/XE2/en/System.Zip.TZipFile.Add - take care to specify correct relative ArchiveFileName like aforementioned FOLDER_A/myText.txt
You can use ZipMaster library. It is very VCL-bound and may cause troubles using threads or DLLs. But for simple applications it just works. http://www.delphizip.org/
Last version page have links to "setup" package which had both sources, help and demos. Among demos there is an full-featured archive browser, capable of storing folders. So, you just can read the code directly from it. http://www.delphizip.org/191/v191.html
You talked about JVCL, that means you already have Jedi CodeLib installed. And JCL comes with a proper class and function, that judging by name can directly do what you want it too: function TJclSevenzipCompressArchive.AddDirectory(const PackedName: WideString; const DirName: string = ''; RecurseIntoDir: Boolean = False; AddFilesInDir: Boolean = False): Integer;
Actually all those libraries are rather similar on basic level, when i made XLSX export i just made a uniform zipping API, that is used with no difference what an actual zipping engine is installed. But it works with in-memory TStream rather than on-disk files, so would not help you directly. But i just learned than apart of few quirks (like instant vs postponed zipping) on ground level all those libs works the same.
I have this code,
showmessage('C:\TEMP\'+openfiles[openfilelist.ItemIndex].ID);
if removedir('C:\TEMP\'+openfiles[openfilelist.ItemIndex].ID) then
showmessage('Removed')
else
showmessage('Failed');
The message shows C:\TEMP\0 and this directory does exist as the program created it earlier and used files inside it and then later deletes them. I can see the files and directories so I know they're there. The program successfully deletes the files but does not remove the directory.
If I hardcode the directory it works - this means that it accepts the string
C:\TEMP\0 but does not accept C:\TEMP\'+openfiles[openfilelist.ItemIndex].ID both equate to C:\TEMP\0. I cannot hardcode these directories, so what can I do? How do I convert from a string + string to whatever removedir() is expecting. I looked this up at Delphi basics and it's expecting a string.
I'm confused, since string + string = string. What is going on?
Make sure that neither your program nor any other program have the directory as their current working directory. When you recompile the program this may no longer be the case, so it may be a red herring that the hardcoded value works for you.
In addition to the other good answers, you should not be storing your temp folder in C:\TEMP. Use the value returned from GetTempFilename, instead. Unlike C:\TEMP, this location (which varies by operating system) will work on all operating systems, and all levels of user access control. This also eliminates the risk that the location you have hardcoded might also be hardcoded into another system.
If I understood correctly, openfiles[openfilelist.ItemIndex].ID is a string that contains number?
If so, did you check that it does not contain blanks? Something like this:
filename := 'C:\TEMP\' + trim(openfiles[openfilelist.ItemIndex].ID);
showmessage(filename);
if removedir(filename) then
showmessage('Removed')
else
showmessage('Failed');
What type of objects are openfiles and openfilelist?
Do they open folders at all, if so they may still be open when your trying to delete the folder.