Cannot make sense out a Delphi windows file name - delphi

I am trying to copy from a file X to this name
C:\RIP2\France Clidat\Les Plus Belles Oeuvres - France Clidat\(01)3_ Un Sospiro.flac
I have checked that there is no bad characters, If I force directorires it creates
C:\RIP2\France Clidat\Les Plus Belles Oeuvres - France Clidat
but it refuses to write the file and I do not understand why a simple test
procedure foo(str: string);
var
f:File;
begin
Assign(f,str);
Rewrite(f);
CloseFile(f);
end;
will crash saying it is not a valid file name but it is!
If I remove ALL blank spaces it works
I am lost please Help

Since command line copies usually require wrapping double quotes around the file name, I'm wondering if the API would need something similar. Maybe try single or double quotes to see if that solves the problem?

Try to make sure your directories are there before you try to create the file in them.
I tried with D2010 on Win7 and it worked with ForceDirectories:
const
sFilename = 'C:\RIP2\France Clidat\Les Plus Belles Oeuvres - France Clidat\(01)3_ Un Sospiro.flac';
procedure foo(str: string);
var
f: File;
begin
if not ForceDirectories(ExtractFileDir(str)) then
showMessage('ForceDirectories failed')
else
begin
AssignFile(f,str);
Rewrite(f);
CloseFile(f);
end;
end;
procedure TForm10.Button1Click(Sender: TObject);
begin
foo(sFilename);
end;

Try some tests to see if it's objecting to the parens, underscore, spaces, etc.. Then you'll know more.
Call GetLastError to find out if you're throwing an error. Windows may be trying to tell you something, and your code isn't listening.

Related

OpenOffice Desktop Instance can not be created (com object)

I have some problems to create an instance of the StarOffice Desktop object.
I used the standard construct below but whenever it comes to the line: StarDesktop := StarOffice.CreateInstance('com.sun.star.frame.Desktop');
My StarDesktop Variant stays unassigned. I am pretty sure that the code is ok until there but perhaps something with the OpenOffice installation is messed up.
Is there a way to check the com objects or did somebody had the same problem and could solve it...
uses
ComObj;
procedure OpenOfficeDocument;
var
StarOffice: Variant;
StarDesktop: Variant;
begin
StarOffice := CreateOleObject('com.sun.star.ServiceManager');
StarDesktop := StarOffice.CreateInstance('com.sun.star.frame.Desktop');
// StarDesktop is always "unassigned"
....
Yes, I know. I should have stated more clearly that I am too 100% sure that it would work normally in a correct environment.
But my question is what could be the cause why it doesn't work. Why the 'com.sun.star.frame.Desktop' instance is unassigned. I have no option/way to debug it...
And it is a bit unfair to vote me down, I researched for one hour without finding something to explain why it could not work.
Or how and where to check if something is wrong with the Office installation (I uninstalled and reinstalled it twice already"
Again, I know this will work for others and normally would work for me, but something is wrong at my system and I would like to know some help to point me in the direction what could be wrong in the system (and not in the code example...)
is OpenOffice installed on client?
doesn't throw any exception?
I'm using Bernard Marcelly's Delphi 7 OOo tool and as can you see his code like that;
var
OpenOffice, StarDesktop: Variant;
...
OpenOffice:= CreateOleObject('com.sun.star.ServiceManager');
if isNullEmpty(OpenOffice) then Raise Exception.Create('OpenOffice connection is impossible');
StarDesktop:= OpenOffice.createInstance('com.sun.star.frame.Desktop');
if isNullEmpty(Result) then Raise Exception.Create(Format('Impossible to create service : %s', ['com.sun.star.frame.Desktop']));
...
'some constants converted to string'
So, if StarDesktop is null, possible can not access Oo Desktop service. If OpenOffice installed properly some features may be missing, options have to set.
This works for me (in my application):
class procedure TOpenOffice.Connect;
begin
if IsConnected then
Exit;
try
FServiceManager := CreateOleObject('com.sun.star.ServiceManager');
except
FServiceManager := Null;
end;
if VarIsNull(FServiceManager) then
raise EOpenOfficeException.Create(StrConnectionFailed);
FDesktop := CreateService('com.sun.star.frame.Desktop');
FDispatchHelper := CreateService('com.sun.star.frame.DispatchHelper');
FIntrospection := CreateService('com.sun.star.beans.Introspection');
FReflection := CreateService('com.sun.star.reflection.CoreReflection');
end;
and:
class function TOpenOffice.CreateService(const ServiceName: string): Variant;
begin
Result := FServiceManager.createInstance(ServiceName);
if VarIsNull(Result) then
raise EOpenOfficeException.CreateFmt(StrCouldNotCreateService,
[ServiceName]);
end;

Create and/or Write to a file

I feel like this should be easy, but google is totally failing me at the moment. I want to open a file, or create it if it doesn't exist, and write to it.
The following
AssignFile(logFile, 'Test.txt');
Append(logFile);
throws an error on the second line when the file doesn't exist yet, which I assume is expected. But I'm really failing at finding out how to a) test if the file exists and b) create it when needed.
FYI, working in Delphi XE.
You can use the FileExists function and then use Append if exist or Rewrite if not.
AssignFile(logFile, 'Test.txt');
if FileExists('test.txt') then
Append(logFile)
else
Rewrite(logFile);
//do your stuff
CloseFile(logFile);
Any solution that uses FileExists to choose how to open the file has a race condition. If the file's existence changes between the time you test it and the time you attempt to open the file, your program will fail. Delphi doesn't provide any way to solve that problem with its native file I/O routines.
If your Delphi version is new enough to offer it, you can use the TFile.Open with the fmOpenOrCreate open mode, which does exactly what you want; it returns a TFileStream.
Otherwise, you can use the Windows API function CreateFile to open your file instead. Set the dwCreationDisposition parameter to OPEN_ALWAYS, which tells it to create the file if it doesn't already exist.
You should be using TFileStream instead. Here's a sample that will create a file if it doesn't exist, or write to it if it does:
var
FS: TFileStream;
sOut: string;
i: Integer;
Flags: Word;
begin
Flags := fmOpenReadWrite;
if not FileExists('D:\Temp\Junkfile.txt') then
Flags := Flags or fmCreate;
FS := TFileStream.Create('D:\Temp\Junkfile.txt', Flags);
try
FS.Position := FS.Size; // Will be 0 if file created, end of text if not
sOut := 'This is test line %d'#13#10;
for i := 1 to 10 do
begin
sOut := Format(sOut, [i]);
FS.Write(sOut[1], Length(sOut) * SizeOf(Char));
end;
finally
FS.Free;
end;
end;
If you are just doing something simple, the IOUtils Unit is a lot easier. It has a lot of utilities for writing to files.
e.g.
procedure WriteAllText(const Path: string; const Contents: string);
overload; static;
Creates a new file, writes the specified string to the file, and then
closes the file. If the target file already exists, it is overwritten.
You can also use the load/save feature in a TStringList to solve your problem.
This might be a bad solution, because the whole file will be loaded into memory, modified in memory and then saved to back to disk. (As opposed to your solution where you just write directly to the file). It's obviously a bad solution for multiuser situations.
But this approach is OK for smaller files, and it is easy to work with and easy understand.
const
FileName = 'test.txt';
var
strList: TStringList;
begin
strList := TStringList.Create;
try
if FileExists(FileName) then
strList.LoadFromFile(FileName);
strList.Add('My new line');
strList.SaveToFile(FileName);
finally
strList.Free;
end;
end;

How can I get this File Writing code to work with Unicode (Delphi)

I had some code before I moved to Unicode and Delphi 2009 that appended some text to a log file a line at a time:
procedure AppendToLogFile(S: string);
// this function adds our log line to our shared log file
// Doing it this way allows Wordpad to open it at the same time.
var F, C1 : dword;
begin
if LogFileName <> '' then begin
F := CreateFileA(Pchar(LogFileName), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0);
if F <> 0 then begin
SetFilePointer(F, 0, nil, FILE_END);
S := S + #13#10;
WriteFile(F, Pchar(S)^, Length(S), C1, nil);
CloseHandle(F);
end;
end;
end;
But CreateFileA and WriteFile are binary file handlers and are not appropriate for Unicode.
I need to get something to do the equivalent under Delphi 2009 and be able to handle Unicode.
The reason why I'm opening and writing and then closing the file for each line is simply so that other programs (such as WordPad) can open the file and read it while the log is being written.
I have been experimenting with TFileStream and TextWriter but there is very little documentation on them and few examples.
Specifically, I'm not sure if they're appropriate for this constant opening and closing of the file. Also I'm not sure if they can make the file available for reading while they have it opened for writing.
Does anyone know of a how I can do this in Delphi 2009 or later?
Conclusion:
Ryan's answer was the simplest and the one that led me to my solution. With his solution, you also have to write the BOM and convert the string to UTF8 (as in my comment to his answer) and then that worked just fine.
But then I went one step further and investigated TStreamWriter. That is the equivalent of the .NET function of the same name. It understands Unicode and provides very clean code.
My final code is:
procedure AppendToLogFile(S: string);
// this function adds our log line to our shared log file
// Doing it this way allows Wordpad to open it at the same time.
var F: TStreamWriter;
begin
if LogFileName <> '' then begin
F := TStreamWriter.Create(LogFileName, true, TEncoding.UTF8);
try
F.WriteLine(S);
finally
F.Free;
end;
end;
Finally, the other aspect I discovered is if you are appending a lot of lines (e.g. 1000 or more), then the appending to the file takes longer and longer and it becomes quite inefficient.
So I ended up not recreating and freeing the LogFile each time. Instead I keep it open and then it is very fast. The only thing I can't seem to do is allow viewing of the file with notepad while it is being created.
For logging purposes why use Streams at all?
Why not use TextFiles? Here is a very simple example of one of my logging routines.
procedure LogToFile(Data:string);
var
wLogFile: TextFile;
begin
AssignFile(wLogFile, 'C:\MyTextFile.Log');
{$I-}
if FileExists('C:\MyTextFile.Log') then
Append(wLogFile)
else
ReWrite(wLogFile);
WriteLn(wLogfile, S);
CloseFile(wLogFile);
{$I+}
IOResult; //Used to clear any possible remaining I/O errors
end;
I actually have a fairly extensive logging unit that uses critical sections for thread safety, can optionally be used for internal logging via the OutputDebugString command as well as logging specified sections of code through the use of sectional identifiers.
If anyone is interested I'll gladly share the code unit here.
Char and string are Wide since D2009. Thus you should use CreateFile instead of CreateFileA!
If you werite the string you shoudl use Length( s ) * sizeof( Char ) as the byte length and not only Length( s ). because of the widechar issue. If you want to write ansi chars, you should define s as AnsiString or UTF8String and use sizeof( AnsiChar ) as a multiplier.
Why are you using the Windows API function instead of TFileStream defined in classes.pas?
Try this little function I whipped up just for you.
procedure AppendToLog(filename,line:String);
var
fs:TFileStream;
ansiline:AnsiString;
amode:Integer;
begin
if not FileExists(filename) then
amode := fmCreate
else
amode := fmOpenReadWrite;
fs := TFileStream.Create(filename,{mode}amode);
try
if (amode<>fmCreate) then
fs.Seek(fs.Size,0); {go to the end, append}
ansiline := AnsiString(line)+AnsiChar(#13)+AnsiChar(#10);
fs.WriteBuffer(PAnsiChar(ansiline)^,Length(ansiline));
finally
fs.Free;
end;
Also, try this UTF8 version:
procedure AppendToLogUTF8(filename, line: UnicodeString);
var
fs: TFileStream;
preamble:TBytes;
outpututf8: RawByteString;
amode: Integer;
begin
if not FileExists(filename) then
amode := fmCreate
else
amode := fmOpenReadWrite;
fs := TFileStream.Create(filename, { mode } amode, fmShareDenyWrite);
{ sharing mode allows read during our writes }
try
{internal Char (UTF16) codepoint, to UTF8 encoding conversion:}
outpututf8 := Utf8Encode(line); // this converts UnicodeString to WideString, sadly.
if (amode = fmCreate) then
begin
preamble := TEncoding.UTF8.GetPreamble;
fs.WriteBuffer( PAnsiChar(preamble)^, Length(preamble));
end
else
begin
fs.Seek(fs.Size, 0); { go to the end, append }
end;
outpututf8 := outpututf8 + AnsiChar(#13) + AnsiChar(#10);
fs.WriteBuffer(PAnsiChar(outpututf8)^, Length(outpututf8));
finally
fs.Free;
end;
end;
If you try to use text file or Object Pascal typed/untyped files in a multithreaded application you gonna have a bad time.
No kidding - the (Object) Pascal standard file I/O uses global variables to set file mode and sharing. If your application runs in more than one thread (or fiber if anyone still use them) using standard file operations could result in access violations and unpredictable behavior.
Since one of the main purposes of logging is debugging a multithreaded application, consider using other means of file I/O: Streams and Windows API.
(And yes, I know it is not really an answer to the original question, but I do not wish to log in - therefor I do not have the reputation score to comment on Ryan J. Mills's practically wrong answer.)

Problem adding lots of strings to a TStringList

I have a problem adding strings to a TStringList. I've searched other posts but couldn't find an answer to this.
What I'm trying to do is to add a big amount of strings to a TStringList (more than 14000) but somewhere in the process I get an EAccessViolation. Here's the code I'm using:
procedure TForm1.FormCreate(Sender: TObject);
begin
List := TStringList.Create;
List.Duplicates := dupAccept;
end;
procedure TForm1.ButtonStartClick(Sender: TObject);
begin
List.Clear;
List.Add('125-AMPLE');
List.Add('TCUMSON');
List.Add('ATLV 4300');
List.Add('150T-15');
List.Add('TDL-08ZE');
List.Add('RT20L');
List.Add('SIN LINEA');
List.Add('TIARA');
List.Add('FL200ZK1');
List.Add('FL250ZK1');
List.Add('SIN LINEA');
List.Add('CENTAURO-70 S.P.');
List.Add('CORSADO');
{ This list continues to about 14000 strings...}
List.Add('VOSJOD 2');
List.Add('Z 125');
List.Add('ZUMY');
List.Add('NEW AGE 125');
List.Add('SIN LINEA');
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FreeAndNil(List);
end;
¿What's wrong with this code? The list contains duplicate strings so I set the Duplicates property to dupAccept. I was able to load the list using LoadFromFile, but I don't want to have a text file outside my application.
I hope you can help me!!! Please tell me if you need any further information.
Thank you very much. I really appreciate your help.
The suggestions for using an external file are on the mark here. However, your post indicates your desire for not having an external file. I would then suggest you link the file to the executable as a resource. You can easily do this following these steps:
Place all the strings into a text file called stringdata.txt (or whatever name you choose). Then create a .rc file of whatever name you choose and put the following into it (STRING_DATA can be any identifier you choose):
STRING_DATA RCDATA "stringdata.txt"
Create a .res file from the .rc:
BRCC32 <name of rc>.rc
Now reference this file from the source code. Place the following someplace in the unit:
{$R <name of res>.res}
Instead of loading from a file stream, load from a resource stream:
StringData := TResourceStream.Create(HInstance, 'STRING_DATA', RT_RCDATA);
try
List.LoadFromStream(StringData);
finally
StringData.Free;
end;
If you do command-line automated builds, I would suggest you keep the .rc file under source control and build the .res during the build process. This way you can also keep the stringdata.txt file under source control and any edits are automatically caught on the next build without having to explicitly build the .res file each time the .txt file changes.
What Delphi version are you using? Some older versions had a bug in the memory manager that can cause an access violation when trying to reallocate an array to a size that's too large.
Try adding FastMM4 to your project to replace the old memory manager and see if that helps.
Also, you're probably better off keeping the list in an external file. Yes, it's another file, but it also means that you can change the list without having to recompile the entire program. This also makes creating (and distributing!) updates easier.
Mason is probably right for the cause of the AV; this is quite a large array to grow.
On a side note, when doing such a long processing on a StringList, it's recommended to surround it by BeginUpdate/EndUpdate to avoid firing any update event.
Even if you don't have any now, they might be added later and you'll get problems.
Set list.capacity to the number of items you plan to add, immediately after you create the list. Alternatively, place the list in an RC file (named other than with the name of your project) and add it to your project. This gets compiled into your application, but does not involve executable code to create the list.
I would also worry about compiler integrity with a 14,000 line procedure. People have found other cases where going beyond anything reasonable breaks the compiler in various ways.
You may also want to try THashedStringList, could see a speed boost (although not in this function), although I'm not sure if the add method is a whole lot different.
try using the following instead of your code to add the strings to the StringList
var
Str: string;
begin
Str := '125-AMPLE' + #13#10;
Str := Str + 'TCUMSON' + #13#10;
Str := Str + 'ATLV 4300' + #13#10;
Str := Str + '150T-15' + #13#10;
................
List.Text := Str;
end;

how to quickly verify the case sensitive filename really exists

I have to make a unix compatible windows delphi routine that confirms if a file name exists in filesystem exactly in same CaSe as wanted, e.g. "John.txt" is there, not "john.txt".
If I check "FileExists('john.txt')" its always true for John.txt and JOHN.TXT due windows .
How can I create "FileExistsCaseSensitive(myfile)" function to confirm a file is really what its supposed to be.
DELPHI Sysutils.FileExists uses the following function to see if file is there, how to change it to double check file name is on file system is lowercase and exists:
function FileAge(const FileName: string): Integer;
var
Handle: THandle;
FindData: TWin32FindData;
LocalFileTime: TFileTime;
begin
Handle := FindFirstFile(PChar(FileName), FindData);
if Handle <> INVALID_HANDLE_VALUE then
begin
Windows.FindClose(Handle);
if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
begin
FileTimeToLocalFileTime(FindData.ftLastWriteTime, LocalFileTime);
if FileTimeToDosDateTime(LocalFileTime, LongRec(Result).Hi,
LongRec(Result).Lo) then Exit;
end;
end;
Result := -1;
end;
function FileExistsEx(const FileName: string): Integer;
var
Handle: THandle;
FindData: TWin32FindData;
LocalFileTime: TFileTime;
begin
Handle := FindFirstFile(PChar(FileName), FindData);
if Handle <> INVALID_HANDLE_VALUE then
begin
Windows.FindClose(Handle);
if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
begin
FileTimeToLocalFileTime(FindData.ftLastWriteTime, LocalFileTime);
if FileTimeToDosDateTime(LocalFileTime, LongRec(Result).Hi, LongRec(Result).Lo) then
if AnsiSameStr(FindData.cFileName, ExtractFileName(FileName)) then Exit;
end;
end;
Result := -1;
end;
Tom, I'm also intrigued by your use case. I tend to agree with Motti that it would be counter intuitive and might strike your users as odd.
On windows file names are not case sensitive so I don't see what you can gain from treating file names as if they were case sensitive.
In any case you can't have two files named "John.txt" and "john.txt" and failing to find "John.txt" when "john.txt" exists will probably result in very puzzled users.
Trying to enforce case sensitivity in this context is un-intuitive and I can't see a viable use-case for it (if you have one I'll be happy to hear what it is).
I dealt with this issue a while back, and even if I'm sure that there are neater solutions out there, I just ended up doing an extra check to see if the given filename was equal to the name of the found file, using the case sensitive string comparer...
I ran into a similar problem using Java. Ultimately I ended up pulling up a list of the directory's contents (which loaded the correct case of filenames for each file) and then doing string compare on the filenames of each of the files.
It's an ugly hack, but it worked.
Edit: I tried doing what Banang describes but in Java at least, if you open up file "a.txt" you'r program will stubbornly report it as "a.txt" even if the underlying file system names it "A.txt".
You can implement the approach mention by Kris using Delphi's FindFirst and FindNext routines.
See this article

Resources