Jump to content

Lars Fosdal

Administrators
  • Content Count

    3303
  • Joined

  • Last visited

  • Days Won

    110

Everything posted by Lars Fosdal

  1. 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
  2. Lars Fosdal

    Search -> Go to Lne Number dialog

    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.
  3. Lars Fosdal

    Best components for creating windows service apps

    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?
  4. Lars Fosdal

    Best components for creating windows service apps

    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.
  5. 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.
  6. https://quality.embarcadero.com/browse/RSP-23056 Debugger watch / evaluate shows wrong values for inline declared local variables with duplicate names within same routine. Put a breakpoint on each of the lines with breakpoint in the comment. Run at A, hover over variable s, or evaluate s shows value A at C, hover over variable s, or evaluate s shows value A - should have been C The actual value is C - so it is a debugger bug program ReusedLocalVariables; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils; type TTest = class public procedure Check(const aValue: String); procedure Test; end; procedure TTest.Check(const aValue: String); begin Writeln(aValue); end; procedure TTest.Test; begin begin var s := 'A'; Check(s); // Breakpoint: S shows 'A' - Output in check = 'A' end; begin var s := 'C'; Check(s); // Breakpoint: S shows 'A' - should have been 'C' - Output in check = 'C' end; end; begin try var Test := TTest.Create; try Test.Test; Test.Free; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; finally Write('Press Enter: '); Readln; end; end.
  7. The official recruitment ad is now out (in Norwegian) https://www.finn.no/job/fulltime/ad.html?finnkode=137676747
  8. Sorry, no code to share as it is embedded in a larger framework. I do use the queues in Omni Thread Library from @Primož Gabrijelčič down in the core, though. As for Synchronize: Just say no.
  9. Lars Fosdal

    Resolve a uses clause unit path?

    I sometimes miss the output from the old compilers which showed the path and name of the units being compiled in the sequence they were built, in a complete log.
  10. Lars Fosdal

    [out] and [in] in records

    VCL.Forms, VCL.Printers and VCL.StdCtrls has a few of these, but for only for function parameters in CLR code. {$IF DEFINED(CLR)} type TMonitorEnumerator = class FList: TList; FEnumProc: TMonitorEnumProc; // reference to the delegate so the garbage collector doesnt free it constructor Create(List: TLIst); function EnumMonitorsProc(hm: HMONITOR; dc: HDC; [in] var r: TRect; Data: LPARAM): Boolean; end; {$ENDIF} and on the topic of CLR: https://stackoverflow.com/questions/2210122/why-are-there-so-many-if-definedclr-in-the-vcl-rtl Yeah... that stuff really should be made to go away.
  11. Lars Fosdal

    formatting HTML code

    What about the https://www.delphihtmlcomponents.com/ from @Alexander Sviridenkov ? I don't use them, but I've seen them getting a lot of praise.
  12. Lars Fosdal

    his control requires version 4.70 or great of COMCTL32.DLL

    It has to be one of the units you use that pulls in non-VCL stuff, then?
  13. Lars Fosdal

    his control requires version 4.70 or great of COMCTL32.DLL

    What version is your comctl32.dll? Can an ancient version have snuck into your path somewhere?
  14. Lars Fosdal

    August 2019 Roadmap released

    10.4 has "language improvements". Some of the stuff I'd want to see here - although I expect only the two top ones to actually be candidates... - parameterless record constructors - nullable types - proper record constants that are treated as constants - generic support for nullable types - generic constraint for enumerated types so that you can for a Value:T use Ord(Value), Set of T, Value in Set, etc. - generic constraint for number, so that you can use mathematical operators on a value of T - generic record helpers - TArray<T> / record helper for TArray<T> - helper aggregation/overload/scoping so that one helper does not have to replace another - lambda expressions - ternary operator to shorten those lambdas
  15. Lars Fosdal

    August 2019 Roadmap released

    Infradestructure?
  16. Lars Fosdal

    New RoadMap Delphi 2019/2020

    Duplicate of
  17. Lars Fosdal

    TWebBrowser + dynamic JS question

    Are you certain that JS is enabled and triggered on page load? Have you tried the embedded Chrome browser to see if that makes any difference? https://github.com/salvadordf/CEF4Delphi I think your last sentence already is in effect for a large number of sites as it makes harder to crawl content tags and references if they are generated dynamically by JS.
  18. Lars Fosdal

    Relaxed JSON

    I prefer regular Json as defined by Ecmascript ES5 / https://json.org When exchanging data, I prefer rigid formats over flexible formats.
  19. I prefer using thread safe queues aka the mailslot principle. Normally I let my worker threads have an workqueue and a reference to the mainthread responsequeue. I post a task to the workqueue, getting a task ticket, and the background thread posts a response/result package to the responsequeue, marked with that task ticket. I can either poll the response queue with the ticket, or I can have the thread fire off a windows message to the main thread that basically says "check the queue for this task ticket" Benefits: 100% async and isolated. No concurrent access to data. All updates can happen at the leisure of the main thread.
  20. Lars Fosdal

    parsing Google search results

    Although DuckDuckGo does not offer a full cover API, it seems that their DOM is easier to parse which could give you easier access to the data you need - unless you are specifically looking for Google data? https://stackoverflow.com/questions/37012469/duckduckgo-api-getting-search-results It depends a lot on what data you are trying to gather.
  21. Lars Fosdal

    Datasnap server updating error image blob column

    Is there a particular reason that the params are numbered 0, 1, 10 ? On the server side - have you inspected the stream after you fill it from the request? Does the length of the stream match the size of the image?
  22. How many of these are spammers?
  23. Lars Fosdal

    FireDAC TFDMetaDataQuery default field value

    Which database? For SQL Server, there is this: https://stackoverflow.com/questions/3817885/sql-server-find-out-default-value-of-a-column-with-a-query
  24. Lars Fosdal

    How best to update from 10.3.1 to 10.3.2?

    If you installed 10.3.1 in your VM with the webinstaller, you can simply download the new webinstaller for 10.3.2 and run it. It will still automatically uninstall the previous version, but if you select to keep the configuration, it will remain configured for 10.3.2. If you installed 10.3.1 in your VM from the ISO image, I recommend uninstalling it before installing the 10.3.2 from either the new ISO image or the new webinstaller. Personally, I always use the webinstaller. /off-topic: Considering the number of new features in 10.3.2, I am a little miffed that you can't have both 10.3.1 and 10.3.2 installed at the same time.
×