-
Content Count
3565 -
Joined
-
Last visited
-
Days Won
120
Everything posted by Lars Fosdal
-
TJson - Strip TDateTime property where value is 0?
Lars Fosdal posted a topic in Network, Cloud and Web
Consider this pseudo code uses Rest.Json; TDateClass = class private FHasDate: TDateTime; FNoDate: TDateTIme; public constructor Create; property HasDate: TDateTime read FHasDate write FHasDate; property NoDate: TDateTime read FNoDate write FNoDate; end; constructor TDateClass.Create; begin HasDate := Now; NoDate := 0; end; var Json: String; DateClass: TDateClass; begin DateClass := TDateClass.Create; Json := TJson.ObjectToJsonString(Self, [joIgnoreEmptyStrings, joIgnoreEmptyArrays, joDateIsUTC, joDateFormatISO8601]); which results in the Json string looking like { "HasDate":"2019-02-14T06:09:00.000Z", "NoDate":"1899-12-30T00:00:00.000Z", } while the ideal result would be { "HasDate":"2019-02-14T06:09:00.000Z", } Q:Is there a way to make TDateTime properties with a zero value be output as an empty string - and hence be stripped? -
TJson - Strip TDateTime property where value is 0?
Lars Fosdal replied to Lars Fosdal's topic in Network, Cloud and Web
I guess we've avoided the problem since we only have used Zulu timestamps. "departureDate":"2019-10-28T06:00:00.000Z", The problem is at the bottom of procedure DecodeISO8601Time in System.DateUtils- That is where the assumption is made that there will always be a time separator if there is a minutes section AHourOffset := StrToInt(LOffsetSign + GetNextDTComp(P, PE, InvOffset, TimeString, 2)); AMinuteOffset:= StrToInt(LOffsetSign + GetNextDTComp(P, PE, '00', STimeSeparator, True, True, InvOffset, TimeString, 2)); Please make a QP. -
TJson - Strip TDateTime property where value is 0?
Lars Fosdal replied to Lars Fosdal's topic in Network, Cloud and Web
@Attilla, from the theorist side if a time zome colon is mandatory in a ISO8601 date, then the problem is really that php API, isn't it? While if a colon is optional, the Delphi implementation of the conversion seems lacking, hence a QP would be a starting point. From the more practical side - Is using class based Json conversion out of the question? - Would pre-processing be an option to correct for the format deviation, f.x. by reg.exp searching for the dddd-dd-ddTdd:dd:dd+dddd and injecting the missing colon -
Is it just me, or does it seem to be slightly different behavior for Focus and BringToFront in Windows 10 1809 and later? I've had several experiences with Application modal windows popping up behind the main window, and it has not only been my own Delphi apps. Starting a Delphi App in the IDE for debugging, would leave the app running behind the BDS now and then. I've had MSSQL Server Management Studio 17.x become unusable with a popup locked behind its main window when left running overnight, on several occasions.
-
Parsing Google Search Results redux
Lars Fosdal replied to David Schwartz's topic in Network, Cloud and Web
I've been wondering about those... Where do I download/install these? Can't see them in GetIt. -
Sorry, no idea. This was courtesy of Google search, but judging from the description, it should be a reasonably standard plugin for WIC, so the indentifiers should be discoverable through the WIC interfacees.
-
https://www.copytrans.net/copytransheic/ installs a driver for Windows Imaging Component that allows conversion with WIC and is, as far as I can tell, free.
-
We already had something else doing the job for us, so we had no need to transition to it. The fragile bit can be only be eliminated when we get a NameOf compiler magic function.
-
Interesting article about dying languages
Lars Fosdal replied to David Schwartz's topic in General Help
Jack of all trades, master of none - seems to be my modus operandi, which is more or less the same as sucking equally at everything 😉 -
Live bindings were dead to me after a short test. Fragile and slow was not a winning combo.
-
TArray vs TList with VirtualStringTree
Lars Fosdal replied to Mike Torrettinni's topic in Algorithms, Data Structures and Class Design
It probably disappeared when they introduced TListHelper. GetList returns arrayofT(FListHelper.FItems); -
Feel free to vote for https://quality.embarcadero.com/browse/RSP-26400 if you need Azure Key Vault support for your Delphi/C++Builder projects.
-
It is truly tragic that Linux is not available for the Pro SKU. It should even have been in the Community version IMO. Imagine the potential plethora of code coming from open source projects.
-
Interesting article about dying languages
Lars Fosdal replied to David Schwartz's topic in General Help
https://larsfosdal.blog/2019/10/10/most-popular-programming-languages-1965-2019/ Stumbled on a nice video showing the rise and fall of Pascal and Delphi. That said, like Cobol, Delphi will take a looong time disappearing. -
Add a system-menu item to all applications?
Lars Fosdal replied to PeterPanettone's topic in Windows API
Adulthood is overrated. -
Class methods and the effects of the static keyword
Lars Fosdal posted a topic in RTL and Delphi Object Pascal
The output of the test app below surprised me. Can someone point me to the documentation that says that if a static class method calls a virtual class method, only the base class virtual method will be called, and not the override? TParent.HasOverride: I said no to overrides TParent.HasOverride Static: I said no to overrides TChild.HasOverride: I haz overridez TChild.HasOverride Static: I said no to overrides Press Enter: program StaticClassMethodCallingVirtualClassMethod; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils; type TParent = class public class function OverrideMe: Boolean; virtual; class function HasOverride: string; class function HasOverrideStatic: string; static; class procedure Test<T: TParent, constructor>; end; TChild = class(TParent) public class function OverrideMe: Boolean; override; end; { TParent } class function TParent.HasOverride: string; begin if OverrideMe then Result := 'I haz overridez' else Result := 'I said no to overrides'; end; class function TParent.HasOverrideStatic: string; begin if OverrideMe then Result := 'I haz overridez' else Result := 'I said no to overrides'; end; class function TParent.OverrideMe: Boolean; begin Result := False; end; class procedure TParent.Test<T>; begin Writeln(T.ClassName, '.HasOverride: '^I^I, T.HasOverride); Writeln(T.ClassName, '.HasOverride Static: '^I, T.HasOverrideStatic); end; { TChild } class function TChild.OverrideMe: Boolean; begin Result := True; end; begin try try TParent.Test<TParent>; TParent.Test<TChild>; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; finally Write('Press Enter: '); Readln; end; end. -
I wish I had RTTI for constants and/or a compiler magic NameOf()
Lars Fosdal posted a topic in RTL and Delphi Object Pascal
< wishful > I keep running into cases where I have to manually duplicate the name of a constant to register it for lookup, or associate the constant with a numeric ID which then is used for the lookup. Either method means double book keeping and is error prone. Some of it would be nicely handled by having a compiler magic NameOf method that could give me name of a constant, field or property as a string at compile time. In other cases it would have been awesome to be able to enumerate a class or a record and get the declared constant names, types and values. This particularly goes for types I want to document for f.x. a JsonRPC API. Instead, I have to manually emit that information, constant for constant, which means someone will eventually forget to do that. If only the compiler gods where listening... < / wishful > -
Class methods and the effects of the static keyword
Lars Fosdal replied to Lars Fosdal's topic in RTL and Delphi Object Pascal
OK, that makes sense. But - it is a bit of a pitfall, since you can call virtual methods from the static one - and not get a warning about it. -
https://community.idera.com/developer-tools/b/blog/posts/gm-update?utm_source=Article&utm_medium=email&utm_content=Article-190924-GMUpdate See also Jim's blog https://community.idera.com/developer-tools/b/blog/posts/the-future-of-codecentral
-
I wish I had RTTI for constants and/or a compiler magic NameOf()
Lars Fosdal replied to Lars Fosdal's topic in RTL and Delphi Object Pascal
I considered XMLDoc, but compile and runtime validation won out. -
I wish I had RTTI for constants and/or a compiler magic NameOf()
Lars Fosdal replied to Lars Fosdal's topic in RTL and Delphi Object Pascal
Pawel, Enums are not the problem. Using helper classes, these are trivial to deal with. Constants that are strings or records are, as is the fact that constants don't have RTTI info. Stefan, the problem with the parser is that it would be near impossible to catch everything since we are talking about a class hierarchy with multiple inheritance branches, and I'd spend more time tinkering with the parser, than manually maintaining explicit output. The current approach is not perfect, but it does make it nearly automatic to keep the API docs up to date and document the source code at the same time. The best formal way would probably be to define it all in Swagger, but ... yeah... not intended for public consumption. An example of how I use RTTI to produce documentation from types and attributes to the various fields, here is a sample of one of the API calls for one of our JsonRPC servers, which is generated (once) at runtime from code. All Json samples are generated using the actual objects, so that changes are reflected whenever something is added/removed/changed. procedure TDoc_TPGMission.Login; begin Method(TLoginRequest); BuildExample<TLoginRequest, TLoginResult>( procedure (const Json:TLoginRequest) begin Json.params.step := TPGStep.S01_Login.AsString; Json.params.username := 'picavi'; Json.params.password := ' ... '; Json.params.clientId := 'Glass-1'; Json.params.deviceType := 'Glass'; Json.params.truckId := 'T011'; end, procedure (const Json: TLoginResult) begin Json.sessionIdent := SessIdent; json.configuration.language := 'NO'; json.configuration.mode := 'professional-superuser'; end, ['Fully qualified login. See below for partially qualified logins.'], -32990, 'Login failed', 'Wrong username [<username>]'); BuildExample<TLoginRequest, TLoginResult>( procedure (const Json:TLoginRequest) begin Json.params.step := TPGStep.S01_Login.AsString; Json.params.clientId := 'Glass-1'; end, procedure (const Json: TLoginResult) begin Json.sessionIdent := SessIdent; json.userList.Add('usrid1', 'foslar', 'Lars Fosdal'); json.userList.Add('usrid2', 'krujon', 'Jonas Krueckels'); json.truckList.Add('T011', 'Truck 11', 'Four slot pick truck'); json.truckList.Add('T012', 'Truck 12', 'Four slot pick truck'); json.truckList.Add('ADIDAS', 'Walking', 'Picker walks without truck'); json.missionList.Add('PICKX', 'X Pick', 'Regular picking at Area X'); json.missionList.Add('PICKY', 'Y Pick', 'Regular picking at Area Y'); json.missionList.Add('SMALL', 'Bag Picking', 'Pick small bag deliveries'); json.Result := False; end, ['Partially qualified login. Missing id(s) will cause an error, but will instead return result.result as False.', 'This means each of the lists has to be inspected to see what was missing', 'and the appropriate input or selection must be done by the user.', 'As long as the login request is missing information, it will "loop" until the login is qualified.', 'Erroneous information should yield an error.'], -32990, 'Login failed', 'Wrong username [<username>]'); end; I use attributes in the Json objects to add a short description of each property. TLoginParams = class(TSessionParams) strict private FdeviceType: string; FclientId: string; Fusername: string; [Encrypted] Fpassword: string; FmissionId: string; Flanguage: string; Fmode: string; Fprotocol: integer; FareaId: string; public class function RPCName:String; override; class function PropDoc(const aPropName: string): string; override; constructor Create; override; function DeviceClient:String; [Doc('Device Type')] property deviceType: string read FdeviceType write FdeviceType; [Doc('Client identifier ')] property clientId: string read FclientId write FclientId; [Doc('User short name')] property username: string read Fusername write Fusername; [Doc('Password')] property password: string read Fpassword write Fpassword; [Doc('UI Language, supported languages: "EN" for English, "NO" for Norwegian, "SE" for Swedish')] property language: string read Flanguage write Flanguage; [Doc('UI Mode - deprecated - should always be "professional"')] property mode: string read Fmode write Fmode; // 'Professional' 'Normal' 'Professional-SuperUser' 'Normal-SuperUser' [Doc('Mission Identity')] property missionId: string read FmissionId write FmissionId; [Doc('Pick area identity (Pick zone)')] property areaId: string read FareaId write FareaId; [Doc('Supported device protocol level')] /// <summary> Supported device protocol level </summary> property protocol: Integer read Fprotocol write Fprotocol; end; TLoginRequest = class(TJsonRPCRequest<TLoginParams>); One exception is an Error code table, which has to be handcrafted and manually updated, since the numerical error codes must be constant. Here is an excerpt: TDispatchError = record // NB! Update procedure TDoc_TPGMission.ErrorCodes in TPG.JsonRPC.Dispatcher when adding error codes private const LoginError = -32900; TUError = -33100; LocationError = -33200; ArticleError = -33300; SaldoError = -33400; MissionError = -33500; SystemError = -33600; MethodError = -33700; SwapError = -33800; DBError = -40000; public const UnknownSession = LoginError - 1; NoDeviceType = LoginError - 2; UnsupportedDeviceType = LoginError - 3; NoDevice = LoginError - 4; InvalidDevice = LoginError - 5; InvalidTruck = LoginError - 6; InvalidMission = LoginError - 7; MethodNotImplemented = LoginError - 8; InvalidArea = LoginError - 70; OtherUserHasTruck = LoginError - 80; LoggedIntoAnotherTruck = LoginError - 81; InvalidUser = LoginError - 90; UserAlreadyLoggedIn = LoginError - 91; CallSuperUser = LoginError - 93; InvalidPassword = LoginError - 94; InvalidPINCode = LoginError - 95; StoragePositionFull = TUError - 1; TUNotFound = TUError - 2; MultipleTUsFound = TUError - 3; OrderSave = MissionError - 1; OrderLineSave = MissionError - 2; OrderRead = MissionError - 3; OrderLineRead = MissionError - 4; AlreadyAllocated = MissionError - 5; This is where I wish I could use NameOf(TDispatchError.Whatever) to get 'Whatever" and put attributes in front of the constant to add a description that I could extract with RTTI. procedure TDoc_TPGMission.ErrorCodes; begin Chapter('TPG Error Codes ', 'Error_Codes', 1); Title := 'All error codes are negative numbers and they are grouped into the following ranges.'; DocTableBegin(html.divStyle.detail); DocTableHeaders(['Error name', 'value', 'description']); DefTitle('Login Errors'); Def('UnknownSession', TDispatchError.UnknownSession); Def('NoDeviceType', TDispatchError.NoDeviceType); Def('UnsupportedDeviceType', TDispatchError.UnsupportedDeviceType); Def('NoDevice', TDispatchError.NoDevice); Def('InvalidDevice', TDispatchError.InvalidDevice); Def('InvalidTruck', TDispatchError.InvalidTruck); Def('InvalidMission', TDispatchError.InvalidMission); Def('MethodNotImplemented', TDispatchError.MethodNotImplemented); Def('InvalidArea', TDispatchError.InvalidArea); The TOC -
Since I don't have an eidetic memory, I rarely use it, but I won't say that I never use it. I prefer bookmarks if I need to frequently jump between sections in a file.
-
That said, based on Remy's post - TService could do with a sibling: TServiceEx that handles the new control codes. Is there a QP I can vote for?
-
We do something similar to what Stefan does. We have a number of services, all implemented around a server core class which in itself is unaware of if it is running as a service or as an application. We wrap TService using a small "helper" class to set up the appropriate server core descendant and hook the svc events that we care about. The test application simply instantiates the same core object and hooks in a few handlers that f.x. will show certain config info and log outputs in a list view. Using that approach, writing and testing service code has pretty much become trivial. We did run into one snag the other day, when we moved a file share URL from a server drive share to a DFS share. Turns out that Local System accounts cannot access DFS file shares, so you either have to give the machine access to the DFS through AD, or run the service under an AD user account that can be given access. We chose the latter.
-
Windows messages are lost when using SetTimer
Lars Fosdal replied to Clément's topic in Algorithms, Data Structures and Class Design
I use a queue, Each thread writes log data to the queue which basically is a ring buffer. The log thread periodically (every two seconds) flush the data asynchronously from the ring buffer, writing multiple entries at a time. I really don't like relying on messages for inter-thread communication - with one exception: the background threads notifying the foreground thread that there is data to fetch from the thread out-queues.