Jump to content

chkaufmann

Members
  • Content Count

    167
  • Joined

  • Last visited

Posts posted by chkaufmann


  1. I changed my code and the error seems to be gone. Thanks a lot.

     

    Now my last question is: Are there other potential problems I may have with my application when running in an RDP session? Especially other problems when loading / saving files from local drives? Until now I didn't find a good resource in the internet about where I should be carefull in my application in order to make it run without errors in RDP sessions:

     

    Regards
    Christian


  2. 2 hours ago, Kas Ob. said:

    This means some files had been deleted and the overall archive file size need to be truncated, is it possible to write new file instead updating the existing one ? or just need to use overlapped operation and give the RDP protocol time to finish the file size updating.

    I think I can change the code and create the .ZIP as temporary file and then copy/move this file to the local file on \\tsclient\..... 

    - Normally I use CopyFile() or MoveFile(). What works better with the RDP protocol?

    - When I look at Windows.pas unit there are many variants of these functions. Should I use a different one?

     

    Thanks for your help.

     

    Regards
    Christian


  3. When my application runs in an RDP session I get the following error:

    exception class          : EOSError
    exception message        : System Error. Code: 8. Not enough memory resources are available to process this command.
    
    00469607 Logo12.exe   System.SysUtils                 RaiseLastOSError
    004696d7 Logo12.exe   System.SysUtils                 Win32Check
    00547faa Logo12.exe   System.Classes                  THandleStream.SetSize
    005461af Logo12.exe   System.Classes                  TStream.SetSize64
    010931ff Logo12.exe   ZipForge                   7047 TZFBaseArchiver.ForceUpdate
    0109a2ea Logo12.exe   ZipForge                   9754 TZFBaseArchiver.EndUpdate
    0109cd7e Logo12.exe   ZipForge                  10780 TZFBaseArchiver.DeleteFiles
    0125d084 Logo12.exe   BSStreams                   939 TBSZipStreamWriter.Close

    The problem is, the error doesn't happen on all clients, it even happens on some clients one day and the other day it's not reproducable. What I know is, it happens when the application creates and writes to a local file during a backup and it happens when the function SetEndOfFile(FHandle) is called. The error only apears when I try to save to a local file (located on any \\tsclient\.... path).

     

    When I search the internet I don't find anything helpfull except about changing the settings for the Windows pagefile. Unfortunately the provider of our RDP plattform / servers cannot reproduce the problem. So I'm stuck somehow, I don't even understand if the error message points to the real problem. And I don't understand if this is a problem of resources on the local client or on the server where the RDP session is hosted.

     

    Something else I know is, that some customers get the same error message when they launch the File Explorer in an RDP session and try to copy a big file from their local drive to the server. Therefor I think the problem cannot be in my application itself, it has to do something with the OS configuration on the server or on the client.

     

    So any hint to isolate and fix the problem is welcome.

     

    Thanks.

    Christian


  4. Hi,

    my provider uses https://rspamd.com/ to check emails before sending out. Now my emails get classified by some modules and I would like to solve this:

     

    One is https://rspamd.com/doc/modules/chartable.html

     

    Normally I just add text with FMessage.Body.Add() and then I set this:

     

    FMessage.ContentType := 'text/plain';
    FMessage.CharSet := 'UTF-8';
     

    Is Indy not doing the conversion correctly?

     

    The second one is https://www.rspamd.com/doc/modules/mime_types.html this one. The others don't give a big score:

     

    R_MIXED_CHARSET        2.89     Mixed characters in a message
    MISSING_MIME_VERSION   2.00     MIME-Version header is missing in MIME message
    R_BAD_CTE_7BIT         1.05     Detects bad content-transfer-encoding for text parts 7bit;utf8;
     

    Can somebody give me a hint on how to fix this?

     

    Christian

     


  5. I use a cache for sql query results. For this I create a key with THashBobJenkins for the sql string and then I have dictionary with the results with these hashes as key.

     

    Unfortunately I run into a problem that two different sql strings give the same hashkey. So I probably have to use the whole sql string as key. Or can somebody provide me a good solution to reduce the sql string to a "smaller" key without loosing uniqueness?

     

    Christian


  6. When I use TStringList.SaveToFile() on a hidden text file, I get a create error. Even if I do this as administrator.

     

    So the only solution I see is to read the attributes with GetFileAttributes, then save the file and then use SetFileAttributes to hide the file again. Or do I miss something?

     

    Christian


  7. When a user starts my application, I would like to know all groups assigned to that user. On the command line I can do this:

     

    dsquery user -samid "christian.kaufmann" | dsget user -memberof -expand

     

    Now I have a delphi function I could use to run this call and then parse the input. But before I start to implement that I would like to know:

    - Is a regular user allowed to call this in order to read that information?

    - Is there another way to get this info from the system?

    - Or are only administrators allowed to read this information?

     

    The background is, I would like to show the user a list of available projects based on the groups he is a member of.

     

    Christian


  8. My problem is, that user managment is done by an external company with quite expensive people. Not my choice, and I just try to make things user friendly with the part I do.

     

    One final question: The shared drives as UNC path look like \\tsclient\c\..... Is this "\\tsclient" a default by Microsoft in a terminal session or can this prefix be customized by the administrator?

     

    Christian


  9. My application runs on a server as remote desktop application. Now I want a function to add files from a local the drive. The RDP file is configured to map local drives and I can see/select these in the file open dialog.
    Now I would like to limit this dialog to the clients local drives only. 
    - Is there a global option for that?
    - Is it enough to set a OnFolderChanging event and check the path to be a local drive?
    - Are there other things todo? Since the file open dialog is almost a complete Explorer today, I would like to limit functionality to open/load a file.

     

    Regards
    Christian


  10. Thanks for the reply. I have one final question. First I tried with this call:


     

    var
      tmp : TIdBytes;
    
      FClient.IOHandler.ReadBytes(tmp, -1, False);
      FPartMsg := FPartMsg + IndyTextEncoding_UTF8.GetString(tmp);
      ....

    Here I noticed, that if ReadBytes had nothing to read, tmp was not reset to length zero, but once some bytes could be read, tmp was set to the number of bytes. What is the reason for that?

     

    Christian
     


  11. My application has to connect to another application. It waits for messages in XML format, delimited with SOH and EOT. I wrote a listener thread. So far it works, but I wonder, if this is the way to do it. Here is my code:

    - Is it correct to call ReadLn and then the thread is blocked until any data is sent by the server?

    - I cannot test the error handling with SendData because I don't succeed to deactivate the server in the moment when the data is sent. Will it work that way? Or should I check another property of my TIdTcpClient to see if the server was deactivated?
    - Do I need more cleanup if I destroy my TTsClient object?

     

    Christian
     

    type
      TTsClientThread = class(TThread)
      strict private
        FClient   : TIdTCPClientCustom;
        FMessages : IBSQueue<IBSXmlElement>;
        FPartMsg  : String;
      strict protected
        procedure Execute; override;
      public
        constructor Create(AClient: TIdTCPClientCustom; const AMessages: IBSQueue<IBSXmlElement>);
      end;
    
      TTsClient = class(TIdTCPClientCustom)
      strict private
        FListener : TTsClientThread;
        FMessages : IBSQueue<IBSXmlElement>;
      public
        constructor Create(const AIpAddress: String; APort: Integer);
        destructor Destroy; override;
        function IsAlive: Boolean;
        function Pop(var AMessage: IBSXmlElement): Boolean;
        procedure SendData(const AMessage: IBSXmlElement);
      end;
    
    { TTsClientThread }
    
    constructor TTsClientThread.Create(AClient: TIdTCPClientCustom; const AMessages: IBSQueue<IBSXmlElement>);
    begin
      FClient   := AClient;
      FMessages := AMessages;
      inherited Create;
    end;
    
    procedure TTsClientThread.Execute;
    var
      s : String;
    begin
      while not Terminated do begin
        try
          s := FClient.Socket.ReadLn(Char.EOT, IndyTextEncoding_UTF8).Substring(1);
          if not s.IsEmpty
            then FMessages.Append(NewBSXmlFromString(s));
        except
          on E: EIdSocketError do begin
            Terminate;
            Exit;
          end;
        end;
      end;
    end;
    
    { TTsClient }
    
    constructor TTsClient.Create(const AIpAddress: String; APort: Integer);
    begin
      inherited Create(nil);
      FMessages   := TBSGenerics.GenQueue<IBSXmlElement>;
      Host        := AIpAddress;
      Port        := APort;
      Connect;
      FListener := TTsClientThread.Create(Self, FMessages);
    end;
    
    destructor TTsClient.Destroy;
    begin
      FListener.Free;
      inherited;
    end;
    
    function TTsClient.IsAlive: Boolean;
    begin
      Result := not FListener.Terminated;
    end;
    
    function TTsClient.Pop(var AMessage: IBSXmlElement): Boolean;
    begin
      Result := FMessages.Pop(AMessage);
    end;
    
    procedure TTsClient.SendData(const AMessage: IBSXmlElement);
    begin
      try
        Socket.Write(IndyTextEncoding_UTF8.GetBytes(Char.SOH + AMessage.AsXmlDataString([]) + Char.EOT));
      except
        on E: EIdSocketError
          do FListener.Terminate;
      end;
    end;

     


  12. For many enumeration type I have I define the same set of methods. Here is an example:

      TBSSwGender = (sgeNone, sgeMen, sgeWomen, sgeMixed);
    
      TBSSwGenderHelper = record helper for TBSSwGender
      public
        function AsInteger: Integer;
        function Code: String;
        function Name: String;
      end;
    
    function TBSSwGenderHelper.AsInteger: Integer;
    begin
      Result := Ord(Self);
    end;
    
    function TBSSwGenderHelper.Code: String;
    begin
      Result := '#Enumeration.BSSwGenderCode%d'.Fmt([AsInteger]).Translate;
    end;
    
    function TBSSwGenderHelper.Name: String;
    begin
      Result := '#Enumeration.BSSwGenderName%d'.Fmt([AsInteger]).Translate;
    end;

     

    Now my question is, instead of writing the same code for all types. So something like this:

    TBSEnumHelper<T> = record helper for T
      function AsInteger: Integer;
      ...
    end;
    
    TBSEnumHelper<TBSSwGender> = record helper for TBSSwGender
    

    Christian


  13. This is what I did. The builder class itself has no RTTI. But I created a generic record.

     

    IBSLookupElements is a simple collection of (Key, Caption, ParentKey) elements. Fmt() and Translate() are string helper methods for Format and language specific translations.
    Then TBSCatalogBuilderSimple is a general subclass of my TBSCatalogBuilder class.

     

    Here is my record definition:

    type
      TBSCatalogBuilder<T> = record
      strict private
        FCatalogId : Integer;
        FDefault   : Integer;
        FName      : String;
        FTextIdent : String;
        FTypeData  : PTypeData;
      public
        constructor Create(ACatalogId: Integer; const AName: String);
        function Gen: TBSCatalogBuilder;
        function SetDefault(const AValue: T): TBSCatalogBuilder<T>;
        function SetTextIdent(const AValue: String): TBSCatalogBuilder<T>;
      end;
    
    { TBSCatalogBuilder<T> }
    
    constructor TBSCatalogBuilder<T>.Create(ACatalogId: Integer; const AName: String);
    var
      i : PTypeInfo;
    begin
      i := TypeInfo(T);
      Assert(i.Kind = tkEnumeration, 'Passed type is not an enumeration.');
      FTypeData  := GetTypeData(i);
      FCatalogId := ACatalogId;
      FDefault   := 0;
      FName      := AName;
    end;
    
    function TBSCatalogBuilder<T>.Gen: TBSCatalogBuilder;
    var
      tmp : IBSLookupElements;
    begin
      tmp := NewBSLookupElements;
      for var ix := (FTypeData.MinValue + 1) to FTypeData.MaxValue
        do tmp.AddOrSet(FTextIdent.Fmt([ix]).Translate, ix);
      Result := TBSCatalogBuilderSimple.Create(FCatalogId, FName, tmp, FDefault);
    end;
    
    function TBSCatalogBuilder<T>.SetDefault(const AValue: T): TBSCatalogBuilder<T>;
    begin
      FDefault := TValue.From<T>(AValue).AsOrdinal;
      Result   := Self;
    end;
    
    function TBSCatalogBuilder<T>.SetTextIdent(const AValue: String): TBSCatalogBuilder<T>;
    begin
      FTextIdent := AValue + '%d';
      Result     := Self;
    end;

     

    Then the usage of the helper looks like this:

    type
      TElmeSchlafmodus = (esmNone, esmKeinSchlafmodus, esmTeilzeit, esmVollzeit);
    
      ADef.RegisterCatalog(TBSCatalogBuilder<TElmeSchlafmodus>.Create(CAT_ELME_BETRIEB_SCHLAFMODUS, 'Schlafmodus')
                             .SetTextIdent('#ElmeBetrieb.Schlafmodus')
                             .SetDefault(esmVollzeit).Gen);

     

    Thanks for all help.

     

    Christian


  14. Ok, here is the long story. In my system I define catalogs that can be used as lookup for values. There are complex catalogs with dynamic content that can be edited by the users. And there are simple catalogs that are hard coded in my application. For these hard coded catalogs I have builder class:

      TBSCatalogBuilder = class abstract
      strict protected
        function AddItem(AItemId: Integer; const AName: String; AIsDefault: Boolean = False; const AParentItem: IBSCatalogItem = nil): IBSCatalogItem;
        procedure DoBuildItems; virtual; abstract;
      public
        constructor Create(ACatalogId: Integer; const AName: String; AOptions: TBSCatalogOptions = []);
      end;

    So to define a catalog I create a subclass of TBSCatalogBuilder, overwrite the DoBuildItems method and call AddItem several times to define the catalog.

     

    Now some hard coded catalogs just represent the items of an enumaration type and instead of creating a subclass I would like one class with a constructor that takes the type of the enumeration and a second parameter with the default value. Right now the definition looks like this:

      TBSCatalogBuilderSetType = class sealed(TBSCatalogBuilder)
      strict protected
        procedure DoBuildItems; override; final;
      public
        constructor Create(ACatalogId: Integer; const AName: String; ASetType: PTypeInfo; ADefault: Integer = 0);
      end;

     

    and then I can use it like this:

    type
      TBSColumnType = (ctUnknown, ctBoolean, ctBytes, ctDateTime, ctFloat, ctInteger, ctMemo, ctString, ctGuid);
    
    begin
      myColumnTypeCatalog = TBSCatalogBuilderSetType.Create(1, 'Column Types', TypeInfo(TBSColumnType), Ord(ctString));
    end;

    This works and it does what I want, but the TypeInfo() and the Ord() just don't look nice and I was wondering if there is not a way to avoid these. And I didn't want to define a generic class because like this the code is duplicated for every catalog. But maybe this is the only way to do it:

      TBSCatalogBuilderSet<T> = class(TBSCatalogBuilder)
      public
        constructor Create(ACatalogId: Integer; const AName: String; ADefault: T);
      end;

    Christian
     


  15. I have a generic method that requires an enumeration type and a value in this enumeration. Right now my function definition looks like this:

    procedure Foo(ASetType: PTypeInfo; ADefault: Integer);

    This works fine. With GetTypeData() I can extract what I need (number of elements in enumeration). However, in the usage of this function I have to add TypeInfo() and Ord() each time:

    type
      TSet1 = (a11, a12, a13);
      TSet2 = (a21, a22, a23)
    

    then I can call:

      Foo(TypeInfo(TSet1), Ord(a12));
      Foo(TypeInfo(TSet2, Ord(a23));

    My question is, how to define the two parameters in order to avoid the TypeInfo() and the Ord() each time?

     

    Christian


  16. I have two applications that communicate in a LAN using UDP. Both applications have TIdUDPClient and a TIdUDPServer. Until now the user must configure the port and the IP address. Now I would like a solution where client computers "find each other" automatically. What is the best way to do that?

     

    Regards
    Christian


  17. In my Windows applications I use VirtualTreeview for grids with inplace editors. Sometimes I load up to several 10'000 rows (nodes). For this I use rather light node classes.

     

    When I look at TTreeviewItem in FMX, then each node is a control and I don't think, this will work the same way.

     

    So are there alternatives. I need

    - tree check functionality (simple, tristate)

    - multiple columns

    - inplace editors

    - lazy load for child nodes

     

    Christian


  18. Hi,

     

    I have an application that still uses an MS Access as database. With the library I have I can run it on other databases as well since all requests are pure SQL (no TTable or things like that).

    Now I'm looking for a replacement and I wonder, if there is a solution that could be used for cloud and offline.

     

    The databases are not that big (about 25 tables, maximum 25'000 records each). But each customer may have up to 50-100 databases.

     

    Ideal would be a solution where the database could be in the cloud, but there is an offline copy in case the internet connection is broken, so I can continue to work. When I'm back online, all changes would be synchronized to the server.

     

    Now I know there is probably no easy solutions, but I'm open for ideas on how to find a good solution.

     

    Christian


  19. Hi,

     

    I just fixed an error in the following function. I had to add the last line where I assign tmp to Result.

    Now I wonder, why I didn't get a compiler warning like "return value of function might be undefined.". I get such errors in the same project with other methods, so the options are set correctly.

     

    Christian

     

    class function TLnxResult.GenForEvent(AData: TLnxObject; AEventId, AAgegroupId: Integer): IBSEnumerable<IBSSwLenexResult>;
    var
      data  : TLnxAbstractObject;
      clubs : TLnxCollection;
      tmp   : IBSList<IBSSwLenexResult>;
    begin
      tmp := TBSGenerics.GenList<IBSSwLenexResult>;
      data := AData;
      while Assigned(data) and (data.Tag <> lxCOMPETITION)
        do data := data.Parent;
      clubs := TLnxCollection(TLnxObject(data).ChildObject[lxCLUBS]);
      if Assigned(clubs) then for var ixC := 0 to clubs.ChildCount - 1 do begin
        for var e in TLnxEntryResult.GenForClub(lxRESULTS, TLnxCollection(clubs.Objects[ixC].ChildObject[lxPERSONS]), AEventId, AAgegroupId)
          do tmp.Add(e as IBSSwLenexResult);
        for var e in TLnxEntryResult.GenForClub(lxRESULTS, TLnxCollection(clubs.Objects[ixC].ChildObject[lxRELAYS]), AEventId, AAgegroupId)
          do tmp.Add(e as IBSSwLenexResult);
      end;
      tmp.Sort(function(const AResult1, AResult2: IBSSwLenexResult): Integer
               begin
                 Result := AResult1.Place(nil) - AResult2.Place(nil);
                 if Result.IsZero and (AResult1.Place(nil) = BSSW_MAX_PLACE)
                   then Result := AResult1.Status.AsInteger - AResult2.Status.AsInteger;
               end);
      Result := tmp; // <<<< this line was missing.
    end;

     


  20. 5 hours ago, Stefan Glienke said:

    If there is a bug with that please link to the QP entry and/or code to repro wrong behavior.

    The problem is to create a reproducable case. The order of initialization sections and class constructor is at least partial random. So things work fine but if you add a unit or change one of the uses in a project, the order is different. So I don't use class constructor anymore.

×