-
Content Count
1264 -
Joined
-
Last visited
-
Days Won
26
Everything posted by David Schwartz
-
You don't HAVE TO generate an exception. Maybe you're talking about what most error loggers are used for. You could also use something like MadExcept that would trap the exceptions and log them for you automatically, send an email, and suppress the exception alerts if you want -- which is useful if you can't modify the source code.
-
Well, I guess it's a matter of perspective. OGs like me who still have boxes of 3-1/2" floppies stashed away remember using them to set up rotations for backups. Sure, they failed, which is why we set them up in batched rotations! Ditto with tapes. I was looking around for some backups of work I did at one point back in the early 90's. It was between the "floppy-disk era" and the "writable CDs era" when I used one of them "super-high density and high-reliability" tape drives. Now I've got a few years of backup tapes with stuff on them and no way in the world to read them. I can still read those old DOS floppies and the CDs. But backup tapes? <shrug> Whatever you might think of SDs, I guarantee they won't decompose the way old magnetic media does over time. There's not much room to write on them, to be sure, and with them getting so cavernous that it'll be impossible to use them for long-term storage without taping them to a large printout that says what's there. But I don't know anybody who does that the way we used to mark-up floppies. Besides, you really couldn't put much on floppies, so it wasn't that big of a deal. CDs started to become a problem in that respect. As for online backups ... there seem to be three options: smaller free accounts that piggy-back on larger subscription models (like Dropbox offers); monthly subscription offers, which tells me the company will probably be around; and single-payment "lifetime" offers, which will eventually disappear for lack of revenue. I've already been bitten by one of them that shut down 6 months after a huge surge of initial customers. You've gotta have a way to keep the lights on, if nothing else; people forget about that. The problem with the monthly subscription deals is ... when you stop paying, everything will evaporate (sooner or later). I'll trust a box filled with ANY kind of media, including SDs, far longer than ANY online service after I stop paying their bills. SDs _are_ a very cost-effective one-time payment for a huge amount of storage, and you're in 100% control of them forever, unlike 3rd-party cloud storage where you really have no idea how it's being used or even of it will be there when you need it. Consider how much storage a bunch of microSDs could hold laid out to cover the same surface area as one writable CD/DVD. A CD is 700MB; a DVD is 4 or 8 GB. I'm guessing that 1TB microSDs laid out to cover the same surface area as a CD would probably yield 50 TB or more of storage. I don't know who'd need that much on a regular basis, but using, say, 60 x 1TB microSDs for a rotating backup would mean you write each one a total of 6 times per year if you make a daily backup of ALL your data and it doesn't exceed 1TB. Anyway, unless you're creating or editing videos and large graphics files (or humongous databases) on a daily basis, only a very tiny percentage of your data changes day-to-day. So most of the data you capture with full backups from one day to the next will be >99% redundant and remain static going forward. The likelihood of "losing" stuff is going to be more depending on your ability to actually FIND IT rather than physical data loss. So there's a great market gap opportunity: a search tool that lets you index your backup media and keep it independent of the media so you can search for stuff "offline" as it were.
-
Yup, that would be it. And if that SSD drive dies before you do for some reason, you can change it to have Delphi look somewhere else.
-
When you install git on a Windows machine, it installs MINGwin or MINGw64 as well as a bash shell and a gui app. I'm building this thing that I'm calling a "workbench". It's built around our service tickets. So you enter a ticket# and some related info, and it goes to a folder and displays the files in it. Then it helps you manage the files and do your work. When you're finished you move on. I'd like to execute some git commands from a Delphi app. Like, when you enter the ticket#, it runs something like "git branch <ticket#>". At some point, you can tell it to add some modified files to the branch. And when you're done it will switch back to "git branch master" or whatever. I know how to spawn command line EXEs from Delphi. In this case, I'm curious about a couple of things. First, is there a DLL or API or something that can be used? Second, I know git.exe is accessible from a regular command prompt; is this sufficient, or do I need the support found in the bash shell's environment?
-
how to run git commands from Delphi app
David Schwartz replied to David Schwartz's topic in General Help
This system was designed in the 2004-2008 timeframe by people who are long gone. It's a legacy project that nobody dares fiddle with. It has been running day-in and day-out for 15 years and the overall system is quite stable and robust. Resources are not anything anybody cares much about, and cutting them by 25%-50% yields no useful ROI. Reliability is paramount. I'd love to redesign and rebuild this from the ground-up, but management here is of the same opinion I've encountered everywhere else I've worked since 2007 -- if they're going to rebuild it, it won't be in Delphi. The ones who DID undertake rebuilds under .NET ran WAY over budget and schedule, and the resulting systems were big, fat, slow, and unreliable. Meanwhile, the Delphi-based software just kept chugging along, solid as a rock and reliable as ever. Most places do not want to replace legacy Delphi apps because they're so solid and reliable. But they don't give that much consideration when it comes to new product development. The only work for old Delphi hacks seems to be working on maintaining old legacy systems. I'd love to find a new major project being developed in Delphi, but I haven't heard of any (in the USA anyway) in years. -
For average use as a backup drive (as opposed to replacing your HDD in your main computer that you use daily) the MTBF would be around 100 years. Spinning HDDs are <5 yrs, esp. if they're in a NAS that runs continuously. Flash storage doesn't "spin" and really don't do anything if they're not being accessed, unless your hardware logic is constantly reorganizing them and running tests, which seems silly for SSDs. Personally speaking, I've found most traditional HDDs fail after about 3 years. I have SSDs in my 2014 Mac Book Pro and they're still going strong. I put them in my 2014 Mac Mini that I use daily and so far no problems. My last 3 employers over 10 years had SSDs in our work laptops that we used regularly. I never had any trouble, but a colleague at one place had his SSD fail after just 3 months of use. The IT guy who fixed it said it was only the 2nd failure he'd seen since they started using them a few years earlier, and this one happened to be brand spanking new. It was a Dell laptop. Honestly, the price of SD cards is getting so low that you could make rotating backups just from a handful of them. I see Walmart is advertising 64GB Class 10 microSDs for $5. I see an outfit named Wish.com that's selling 1TB Class 10 UHS-1 TF microSDs for $7.64. SD cards aren't nearly the speed of an SSD like Samsung T5's but for backing up source files on a daily basis I don't think the speed differences would even be noticeable. If you got 10 of them and rotated them daily, you'd use each one 36 times a year. For devices rated in the 7-figures, I wouldn't be terribly worried about long-term reliability at that rate. You'll be dead and gone by the time one failed.
-
The question I have is, how can I get something like a TListBox or TListView where the Items is a list of TPanel rather than strings or TListItems, with analogous behavior? I don't need what a TFrame offers. I'd imagine you're going to have the same problem adding either panels or frames to a larger container object if you want them stacked nicely in a list.
-
Not sure exactly where this goes, but it's regarding the standard Windows Font Dialog. I dug up some old code that used TFontDialog and when I ran the demo it looks like it's from Windows XP era. When I implemented something similar in my own code, I was a little shocked to see that this ugly-ass dialog is apparently the current Font Dialog in Windows 10! Only when I run Word and other apps that have similar font options, their dialogs are much different. This is the first time I've used a Font Dialog in ages, which is why this seems to strange to me. Hasn't the Windows code for this been updated since 1995 or so? Are there better-looking Font Dialogs anywhere?
-
To quote Charley Brown ... oh good grief.
-
how to run git commands from Delphi app
David Schwartz replied to David Schwartz's topic in General Help
not so different ... we don't use C:\ as the root for git as you do. Instead, we map V: and I: to folders below C:\ and treat them as virtual drives. V: --> C:\development and I: --> C:\imports All of the libraries and server apps are in V: and I: has 800+ folders that contain import handlers (EXEs) for different client data files. The stuff in V: hasn't changed in years. Most of our day-to-day work is in the I: drive area. But there's stuff in there that goes back to 2010 or so, and maybe half of it is obsolete. (Nobody seems to care, because in theory the clients could come back and pick up sending us data in the same formats they were using years ago.) The problem is, we have clients that manage other clients of their own. Think of a property management company that would manage a bunch of apartment complexes. The property mgt company is OUR client; they send stuff to us to process for THEIR clients. Each of them is in a separate folder. If we call the mgt company 'ABC' then we'd have ABC001, ABC002, and so forth, for their clients. All separate folders. But some of these guys will all use the same data formats, so we only have ABC000 and in that folder we have a single import EXE that imports data for ALL of THEIR clients, because it's all in the same format. (They'd typically use their system to generate the export data files, and they're just big flat data files saved as CSV data.) Anyway, here's the rub ... ABC says, "We need the logo changed on cust123" and we have, say, a week to get that done. Then the next day, they say, "We need to add a new customer as cust456" and that takes a week. Then they say, "We need to change the message on a statement for cust890". All three of these tickets overlap in time. Depending on priorities and who is available to do the work, they can get done in any order and work can occur a little here and a little there. It's not too bad when these customers have different data import needs, so they're in separate folders. But when their code is all in the same folder, and we have multiple people working on them, it's problematic. Also, when someone works on one of the clients in another folder and checks it into git, and then I do a "git pull", it updates that other folder, which is usually completely unrelated to what I'm working on. I've done a git pull and it tells me 25 files were added, modified, and deleted, yet none of them are in the folder where I'm doing my work. It's just noise to me and doesn't affect my work at all. When you're working all alone, this isn't an issue. You work on one thing at a time and it works out perfectly well. With a team of people working on different parts of an elephant, everybody is supposed to have the entire elephant on their own drive in its current state -- so everybody is working with the exact same elephant. Otherwise, someone's elephant might be missing a trunk, another might only have half a tail, and so forth. We want everybody to have the same "model" in our disk so we can assign anything to anybody at any time. that's the theory, anyway. -
how to run git commands from Delphi app
David Schwartz replied to David Schwartz's topic in General Help
That's cool. The challenge we have is we've got all of our production folders mapped to a couple of local drives (I: and V:) and each of those folders acts as the git base (whatever it's called). Each of the folders has a Delphi project in it that builds an EXE. Some of them share files because they're related, but they can't all be in the same folder for various reasons. So 'git pull' and 'git push" and so on act on the entire virtual drive. It can get rather convoluted when someone updates a file elsewhere that has nothing to do with what you're working on and it creates a conflict elsewhere that stops you in your tracks and you have to go off and resolve that conflict to continue work on what you're REALLY trying to do (that's not related at all). I'm not very well-versed in git, so I hope this makes sense... I'm trying to build a 'workbench' app that manages a bunch of resources, including the ability to set up a branch, do necessary pulls, sidestep unrelated conflicts, let you do your work, then wrap everything up with adds, commits, and pushes, then unwind the unrelated conflicts that may have come up earlier. This is feasible because all of our work is driven by specific work tickets that only affect code in a specific folder. We can have multiple tickets that affect things in the same folder. They may deal with the same files, but very rarely are there real conflicts. (A common conflict scenario is two different tickets that require us to add a line to the end of the same file. they're not related, but all git knows is there's a conflict with the last line. We just need to accept both; the line position they're at is irrelevant.) Everybody seems to use slightly different ways of dealing with stuff, and I'd like to implement a consistent process in this tool that just lets us focus on the task at hand without having to get distracted by unrelated stuff that git insists we handle RIGHT NOW. -
how to run git commands from Delphi app
David Schwartz replied to David Schwartz's topic in General Help
Would you mind sharing that? I'm curious what all is involved. 🙂 I have a process that a colleague documented that he uses, and I'd like to implement that to make it more of a one-click option. -
how to run git commands from Delphi app
David Schwartz replied to David Schwartz's topic in General Help
From 2012 ... 😮 (I'm not a fan of DLLs, TBH, and I know that the base code has evolved quite a bit since 2012. If I need to have an external dependency, I'd rather use the EXE than a DLL. But this is good to know.) -
how to run git commands from Delphi app
David Schwartz replied to David Schwartz's topic in General Help
So if I run a git command the same way I run anything else on the "command line" (spawning a child process), it'll work fine? I'm mainly concerned about dependencies that may exist on features in the bash shell that don't exist in cmd. (Or do I need to run bash.exe and then provide git.exe as an argument to that, along with its parameters?) -
Boolean short-circuit with function calls
David Schwartz replied to Mike Torrettinni's topic in Algorithms, Data Structures and Class Design
Nullables are in the works for Delphi, according to one or two published roadmaps now. Trinary operators ... nowhere to be seen. (I guess "trinary" is the correct term, not "ternary".) -
I'd like to find a simple RTF editor, like Notepad, but that lets you select fg and bg colors, fonts, styles, and so on. Not HTML. Short of that, I'd like to find a library that I can use to build it. (I have TRichView myself, but it's for something I'm noodling with at work, and they won't pay for anything for that purpose.) Any ideas?
-
Boolean short-circuit with function calls
David Schwartz replied to Mike Torrettinni's topic in Algorithms, Data Structures and Class Design
Nullable values and associated operators are a very long way around getting the equivalent of a trinary operator for getting a value through an object reference only if it's defined, and not having to deal with an exception otherwise. -
I searched in Google for an example of something built with a TRichEdit in Delphi, but didn't have much luck. I know I've seen several over the years, but now when I want one, I can't seem to find one. I found a very cursory one from a guy in Italy, and all of the comments are in Italian. But even though it has a Load and Save function, it doesn't save or load RTF data. I did, however, figure that part out. 🙂 I do not need a full-featured word processor. I basically want to cannibalize parts of it for use in another application -- mostly font features so I can highlight things, change fonts and/or sizes, stuff like that.
-
Boolean short-circuit with function calls
David Schwartz replied to Mike Torrettinni's topic in Algorithms, Data Structures and Class Design
In general, if you have a function that takes arguments, and you supply functions to get values for those arguments, then by necessity all of the parameter functions will be evaluated before the function itself is called. This is a point of contention with people who would like to see a trinary operator added to Delphi similar to what's in C/C++ (and many other languages). boolean rslt = a ? b : (c ? d : (.....) ); vs Result := func( a, b, c, d, ..... ) The trinary operator will only evaluate the parameters as they are needed, while Delphi will always evaluate ALL of the functions given as parameters to func. (rslt = a ? b : c) in c/c++ does not always produce the same result as rslt := IfThen( a, b, c ) in Delphi. In the former, 'c' is not evaluated if 'a' <> 0 (ie., true), and 'b' is not evaluated if 'a' = 0. But in the latter, a, b, and c are ALL evaluated BEFORE IfThen is called. This is particularly relevant if you want to use a construct like this to test if a reference is assigned or not: rslt := IfThen( Assigned(obj_ref), obj_ref.xyz, NIL ) will throw an exception when it attempts to evaluate the second parameter when obj_ref = NIL. That's exactly what you're trying to avoid! rslt = (Assigned(obj_ref) : obj_ref.xyz ? NIL) does what you'd expect all the time. Interestingly, we have to suffer through this mess with lengthy if...then...else tests waiting for "nullable" values to be added to the language (after 25+ years) rather than stoop so low as to implement a simple construct that solves the problem very elegantly and simply, but is regarded as too offensive to "Pascal purists" who think it's sacreligious to steal such a construct from c/c++ for ANY reason! Yes, we'll see nullable values added to Delphi long before a trinary operator. -
Enums and generics
David Schwartz replied to Mike Torrettinni's topic in Algorithms, Data Structures and Class Design
Ahh, mapping between enums and strings. Who needs to do THAT? Sadly, what you're pointing at is a very common problem that php solves reasonably well, but not Pascal nor even C or C++. I don't know about C#. The syntactical requirements to define a structure (record or class) then initialize it are such that it can't be done all in the same place. The closest you can get in Delphi is butt-ugly and convoluted. In php, if you stuff a bunch of things into a variable, the language infers that it's an array, and the syntax isn't much different if it's a list. And you can define data in a structured way that lets you stuff it into a variable that just "knows" it's structured data. Maybe from type inferencing, but php is an interpreted language, so it's a lot more flexible than compiled languages that need you to define structures to receive data, and then separately assign values to that data even if it's just static initializers that run when the program starts up. Anyway, helpers were created as a way of extending records and classes that you cannot otherwise touch. Using helpers to "extend" enums does not make much sense. I can see WHY you'd want to do it, but that's not what helpers were designed for. Honestly, this is such a common thing (in my world, anyway) that I don't see why they haven't added something to the language to support it. It was nice for about 5 minutes when they added the ability to get the string representation of an enum name, but you don't want to show users things like "ptMain" or "ptExternal" -- you want something more relatable. But you can only give them a numeric value; you should be able to assign a string that can be accessed for "people" to read. You end up forced to set up a completely separate data structure for that, and then initializing it requires a third chunk of code. Or you need a case statement that maps the enums to strings, which means if you add more stuff to the enum, the compiler won't tell you that you forgot to extend your mappings to include alternate descriptions for them. To make matters worse, since case statements in Delphi don't accept strings as discriminants (like most other modern-day languages) you cannot simply invert the case statement for mapping from the string back to the enum. In fact, you need a totally different mechanism. So you'll end up with either two TDictionaries (one to map each way) or one that lets you index off of any value in the tuples. Or a case to map one way plus a TDictionary to go the other way. A butt-ugly solution for such a common problem, IMHO. You can use TStringLists with their ability to manage <name=value> pairs, but they're strings. <enum=string> isn't supported. -
Class fields as parameters or refer to fields directly?
David Schwartz replied to Mike Torrettinni's topic in Algorithms, Data Structures and Class Design
I prefer using parameters just to make it clear what variable is being passed. I've had situations where I moved methods around and the implied variables change and suddenly things break. When I pass in a variable to a method as a parameter, it's a lot easier to see that I need to change it if I move something than if I let the method use an implied variable. A lot of times when debugging, it's easy to miss WHICH variable of the same name is being used. An example where this happens for me frequently is when I set up a nested method inside of an existing method, and then for some reason I decide to move it outside of that method (no longer nested) and the variable is also declared as a class variable, so there's no compiler error, but at run-time it suddenly does weird things because the value is coming from another scope now. (When I make nested methods, I want them to be really short and sweet, even if that increases coupling. It often comes back to bite me in the ass later on.) -
When can Class.Create fail?
David Schwartz replied to Mike Torrettinni's topic in Algorithms, Data Structures and Class Design
There are situations where the ctor can fail, for sure. So you put it in a try...except. The try...finally is partly to ensure that properly constructed instances get freed in case an exception is thrown. But more than that, if you call Exit at any point between the 'try' and 'finally', the Exit goes to the Finally clause as well. It helps to think of things in your mind as always composed as three parts: * set-up or initialization * operation * tear-down For example: * You set up some scaffolding on a wall to do some work on second-story windows. * You do whatever it was you intended to do * You tear down the scaffolding, pack up and go home. If you fall off the scaffolding (like throwing an exception) an ambulance comes and takes you away. But someone has to tear things down and clean up, right? Now say you get a call from someone saying you won the lottery. You probably want to rush off and claim your money (like calling Exit). You still want to be sure that everything gets torn down and cleaned up as well. This is a frequently repeating pattern in any program. When you start to see it, you can't un-see it, and it makes organizing things far easier. Ditto for finding stuff. It's how classes are structured, how you can organize units, how you organize chunks of code inside of a unit, and even within methods. Classes make it explicit with the notion of a "constuctor" and "destructor". Try...Finally and Try...Except blocks do the same thing, but the "constructor" part is above the Try. The difference is that the 'except' is provided specifically to trap exceptions while 'finally' is to act more like a destructor to clean things up. -
Yeah, I was just looking at that.... I've changed it to be more clear.
-
Forms are objects. They behave just like objects. And IMHO, they should be treated just like any other objects. All TObjects have a Constructor and Destructor. What's normally done is: myObj := TMyObj.Create; try myObj.abc := 123; // initialize things . . . myObj.DoSomething; rslt1 := myObj.GetResult; // get stuff out of the object . . . finally myObj.Free; end; TForms are TObjects, and they also have: * OnCreate and OnDestroy. * OnShow and OnHide. * OnClose Things you create in the OnCreate method should be freed in OnDestroy. Things you create and enable in OnShow often need to be freed / disabled in OnHide. OnClose is a quirk. It's useful for some purposes, but it can often cause more problems than it's worth. Forms can be created and destroyed automatically, so they're persistent throughout the life of the application. You just use normal properties and methods to access things on the form (although most people don't bother with defining properties to interact with forms). For typical dynamic form use, this approach can be used: myForm := TMyForm.Create(NIL); try // OnCreate is called here myForm.abc := 123; // initialize things . . . myForm.Show; // OnShow ... do stuff ... OnHide // do some stuff here before calling Free . . . -- or use ShowModal -- if (myForm.ShowModal = mrOK) then // OnShow ... do stuff ... OnHide begin rslt1 := myForm.GetResult; // get stuff out of the object . . . end; finally myForm.Free; // OnDestroy is called here end; This is really clean. The structure is just like any other object whether you need to add parameters to initialize the object (form) before use or get result data afterwards. The only times I've run into issues is when I rely on the OnClose method with caFree. People don't like using 'with' because of its implicit references. Well, relying on OnClose with caFree presents similar implicit actions, and can be equally problematic. If you rely on caFree in the OnClose handler, and you decide to get some result data from the form later, you have a lot more work to do because the object myForm pointed to has been freed after ShowModal returns.
-
Where I work right now, we build apps to import all manner of data supplied to us by clients. A lot of the data is numeric, and the code uses functions like StrToInt or StrToCurr or StrToFloat or StrToDate or StrToTime to get the binary values to manipulate ... you get the idea. Here's the rub: there's no Try ... finally fence anywhere in the code that handles these conversions. So if there's a single empty or non-numeric field passed to one of these conversion routines, an exception gets thrown and the import process aborts right then and there. Here's my question .................... I suggested that all of these functions should be replaced with ones that don't throw exceptions, like StrToXxxDef(...), and that at least there should be Try...finally fencing around each method anyway just in case something unexpectedly throws an exception. Then I offhandedly made a comment like, "these are all programmer errors, and they should be fixed". My work colleague took strenuous exception to that statement. (No pun intended) His rebut was that clients give us the specs for their data, and they don't always (usually?) tell us what to do if any of the fields are empty or invalid. My position is that it's the programmer's job to ASK! He disagreed. I spent the first 5-1/2 years of my career working on real-time embedded systems and machine control software. Un-trapped errors (today's unhandled exceptions) can cause damage. Sometimes they cost lives. It's irresponsible to shrug your shoulders and say, "Well, the client knows best, and we shouldn't question them!" That's my thinking, anyway. What do YOU think?