Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

File Choosing Manipulation (Conceptual Design) 1

Status
Not open for further replies.

Scott24x7

Programmer
Jul 12, 2001
2,828
JP
Hi All,
So I've reached an important stage in the build of my application, one that I've been putting off for a while, because I knew it would be a bit complex, potentially messy, but I thought I would seek the guidance of the Tek-Tips community on this subject, as I am sure others have done this kind of thing before.

Let me start off by mentioning that, years ago, after a great deal of experience in OLE embedding (especially graphical items, though these days I have a lot bigger issues), found that Fox tables were exceedingly subject to corruption. So that issue still sits with me. I spent months working with tables that had embedded image data into General fields and they just corrupted all the time. Particularly if you were moving the files around.

I eventually went to a linked model using Memo fields to contain the path and file name to a file saved in the working directory. That meant copying the file into the directory first and then "Picking" the file (image only) data which it would just embed the path to the file (which were all saved under the \<application Name>\Graphics\<source of image>\<Filename.ext>

As I am now planning to associate "lose documents" with various sources within the database (and I mean that) I want a new strategy.

The idea is to "pick" the filename from where ever it sits (assuming regular directory navigations, or networked drives, etc) and when the file name is "Picked" it's actually copied into a single directory which will be: \<Application>\DOCUMENTS.

Since I just need a "container" for documents that will get referenced by Memo field as a linked object, I don't really care that they are "cleanly" grouped into different directories. I understand there could be conflict between matching file names, but for now I don't want to manage those in the application, I will force users to make the name unique at the time they select it for including if it already exists.

The question is... how to best go about this process? I'm open to someone's "library" if they have such a thing, or 90% solution where the codes is still open to modification so I could tailor it. I'm also fully willing to write the full thing, but some of that manipulation (especially the "copy a file from one place to another, and then link the directory/filename to Memo file" seems a bit sketchy. (Lot's of FOPEN() calls...)

Is there an elegant solution to this?
Suggestions?
Is ok to tell me to go figure it out myself. Just hoping to cut my development time and learn curve as much as possible.
Thanks to you all in advance.

Best Regards,
Scott
ATS, CDCE, CTIA, CTDC

"Everything should be made as simple as possible, and no simpler."[hammer]
 
Look further. Which VFP version do you really have? VFP9? VFP5?

Talking of VFP9:
Going in the search tab of the help REPLACE command comes right before REPLACE FROM ARRAY.
In the index only REPLACE FROM ARRAY turns up, yes. A flaw of the index, it seems. Nothing stopping you from using the search, though.

Aside of that one further helpful tip:
In the contents tab of the help go to Reference/Language Reference and you have a "book" about Commands and another about Functions which are the main two types of VFP language.

Bye, Olaf.

Edit: The most importatn thing, it's simply

Code:
REPLACE field WITH value IN tablealias

The IN clause is even optional, if you want to replace in the current workarea, but is helpful to avoid replacing elsewhere. When using the in clause you specify workarea number or alias name, not DBF file name, ie no .dbf extension like SQL statements allow (another SQL advantage, once a DBC is open you can work on closed tables, too, when specifying full path even without the DBC opened before).

There also is a scope of the REPLACE, which is current record by default, but you can also explicitly specify that as NEXT 1, which means current one record, NEXT N means next N records starting from the current one. Other scopes are REST and ALL and RECORD No. Then you also can combine that with WHILE, especially using REST scope and of course you can add a FOR clause to further filter it, WHILE is checked in consecutive records and can end the scope before itself would end, FOR clause is checked in all records visited by the scope and may skip single records and still continue further down the table. And in the long run it's very helpful to know, that a replace at EOF does neither work nor error.

In your case the simply syntax would work. Or an index on CONTACTID, as I showed analyzing the UPDATE behaviour.

Bye, Olaf.
 
Thanks Olaf,
I do have Index on that table to CONTACTID, so that's why I was surprised when the record pointer moved.
Thanks for the note on the omission from the online help file. It's weird. My original VFP reference manuals and other VFP dev books are in storage. I will be able to get to them later this year, maybe around October or November, so I will get them and bring them back. In the meantime, I'm relying on the somewhat awkward help files.

So all these options we have discussed: UPDATE, REPLACE, INSERT while I get INSERT at least has some different use, is there a specific advantage or disadvantage to using UPDATE vs REPLACE? I seem to remember from years ago, there are some circumstance where some commands are better than others, even though there are more than one way to accomplish the same thing? (I thought Memo fields were one of those, but I could be wrong, or resolved in later versions). I started VFP with 3.0 (which was first version as I recall) back around 1995 or 96... I don't remember for sure. I was at DevCon in Phoenix that year, when they released it they actually gave a copy to everyone who attended as part of your conference fee. Then I went again the following year when it was in San Diego, I think went to VFP 5 around that time.

After that I progressed with VFP7 and VFP8, and eventually when VFP9 became available, I got it. That is the current version I am using, with SP1, SP2, SP2 Hotfix and Sedna patches applied.

So given all that, is there some advantage? I'm sorry my memory is hazy of the past parts, and I may be "living in fear" of previous gotcha that has been resolved in my current version.


Best Regards,
Scott
ATS, CDCE, CTIA, CTDC

"Everything should be made as simple as possible, and no simpler."[hammer]
 
The index is obviously not used, does it have a collation other than MACHINE? That prevents it from being used by rushmore. Is it an IDX and not a CDX? Is it a tag of a CDX, but not the main CDX? Then it also isn'T automatically used. Aside of that Set Optimize has to be On (default). You can check whether an index and what index is used via SYS(3054).

You might check out the help file of VFPX, it might have that fix, it sure has some fixes. I haven't installed it, my VFP is official standard build 9....5815 again for the software policies of my main and only VFP customer.

There are some differences I already talked about, but there is no general better or worse. Both variants write to the buffer, for example, there is no difference in the DBF file in the end. SQL has the main advantage to be portable to other databases and languages better than xbase commands, that's the main thing about it. I still use a lot of replaces in the situation to already have loaded data into a buffered updatable cursor like an updatable spt cursor or cursoradapter cursor. It obviously spares to readjust the record pointer in simple cases. UPDATE can do subqueries and some of that could also be done with relations, but SQL is more generally used today, so you get better help about sql statements not only from the VFP community.

There are very detailed reasons why to use what in which situation eg when it comes to locking behaviour, but that's beyond what I can write right now. In this case the REPLACE surely is simpler. I would still go with a general files table you only add new files to, there is no need for update nor replaces, the thing to change then is all tables having a memo for a path to an int id field to refer to a file record, which makes things just a bit more complicated to get to the file, when you need to display an image, for example, but it's just one relation or join and you have a single place for all the file names to maintain and monitor about broken links. It's far easier to manage that aspect, thuogh it does not automatically update with direct folder manipulations like file renaming in the folder or deletions or added files. If you keep users off that folder they also can't get file from it through your application, that's one of the problematic things, but that is getting off topic in regard to sql vs xbase, of course.

Bye, Olaf.
 
Olaf,
You are right.
It is a .CDX (I never use IDX). But it was not the primary key, and I realize now, not the relation set in the data environment either.
But as you pointed out before, REPLACE was an alternative, and I've now switched to that. Far cleaner in my view as well, at least for this instance.

My case is a little weird in that, I learned a long time ago when using memo field for storing file references, that it gets messy fast if you move the application (or if a drive letter changes), you lose ALL your references in that go along with it, and you then have to do some string manipulation to get them working again. That is a hassle I don't want to deal with.

So, to combat that, I place the files into known directories instead. In the case of my "CONTACTS", I put their business card images into a directory called "CARDS". Now I just right click the image (it defaults to a "white" blank image if there is none), and grab it using the GETFILE() you introduced me to.

I created a new procedure called "ONLYTHEPATHWENEED" which lops off all the directory path prior to the directory I've placed the files in. (This is why I needed the "SELECT AND COPY" method we talked about before.) If they are picking the file from another location then it needs to be copied into the "correct" directory first, depending on what function they are in. (Contacts -> CARDS, Clients -> DOCUMENTS, etc.) which is a destination I can set ahead of time.

I don't have this fully working yet. And there are times where it may be fine to pick an existing file from the existing directory, but if it's picked from a directory other than the destination then it needs to be copied to the destination too, and then use THAT ONLYTHEPATHWENEED(filename) to be stored.

That function doesn't do anything fancy, just figures out where and how many "\" there are, and then cuts off what we don't need, keeping the full filename.extension + the preceding part of the directory.
I have a framework that allows me to utilize just that within the application directory (i.e. directories within the application path don't need to be fully qualified, as the application frame work manages that, and even if you copy the whole thing to another directory on a different drive, the first thing it does is determine where it is "now" so that all the underlying directory structures remain unbroken.

This is all prior stuff aside from the "pick and copy" file stuff now, that was part of my "decoupling" general fields with VFP, and using Memo fields instead to store references to files.

Probably, you think I'm crazy for doing this, but it really does work nice especially when we have so many different file types, VFP ultimately doesn't care, and I associate the types with applications, so when they are "picked" from the records, it shellexecutes to the corresponding application that launches the file type.

Best Regards,
Scott
ATS, CDCE, CTIA, CTDC

"Everything should be made as simple as possible, and no simpler."[hammer]
 
You still don't see my code solves the problem of testing whether the file is already in the destination folder. That also solves the problem of the file being picked from there.
You don't need a function you just need JUSTFNAME() and you get only the file name part, it's also used in my code. I prepend it with ccBaseDir, but it's up to you to store the full path or JUSTFNAME part.

Bye, Olaf.


 
Scott,

let me try a slow motion explanation:
Code:
#Define ccDocumentBaseDir "D:\Yourapp\Documents\"
lcUserpickedFile = GETFILE()
First of all the constant ccDocumentBaseDir is the destination database directory to store picked files into
Now assume a user pics a file from there, then lcUserPickedFile could be "D:\YOURAPP\DOCUMENTS\SAMPLE.JPG"
Code:
If !Empty(lcUserpickedFile) AND ADIR(laDummy,lcUserpickedFile)=1
Both these conditions are fulfilled, so we go into the IF branch, as the comment states:
* The user picked an existing file and no CANCEL or ESC was used out of the GetFile dialog
Code:
lcDestinationFile = Addbs(ccDocumentBaseDir)+JustFName(lcUserpickedFile)
If ADIR(laDummy,lcDestinationFile)=0
Now lcDestinationFile is "computed". It concatenates just the filename of the user picked file "SAMPLE.JPG" to the basedir "D:\Yourapp\Documents\", so lcDestinationFile is "D:\Yourapp\Documents\SAMPLE.JPG" - The case of the file name doesn't matter. That is now checked again with ADIR. As it exists already (the user picked that file from the database dir!) the result is 1. The same applies, if the user picked any SAMPLE.JPG from elsewhere as per your demand. So we continue in ELSE:
Code:
Messagebox("That file already was put into the database documents section and therefore is rejected. Try to add it renamed.")
At this place you can do something else of course, you can skip the copy process (this already is skipped by jumping to ELSE) and still write the file into the customers credit card picture field.
But the concept and your needs about the database directory is fulfilled by my code unchanged.

And if you don't understand something, please ask. I could also use FILE instead of the ADIR calls, but FILE can give false positives on file names you have embedded into your EXE, ADIR checks for the fileskeleton given in parameter 2 on hdd. I don't need the array it generates, therefore name it laDummy, it's just junk. I just need the count of found files, the direct return value of ADIR. It's a quite known workaround of the FILE() problem and when that's unwanted ADIR is preferred.

Bye, Olaf.
 
Hi Olaf,
I fully understood each line of your code, although I think I missed the idea that the #Define could be something other than the fully qualified path. And I can even pass it a parameter so "\CARDS\" could be passed for business card images, "\DOCS\" could be a parameter for docs, etc. So I think if I make that more "dynamic", then it's a good/easy option. The ADDBS was a little confusing to me, since it looked like all the backslashes were already there. Did you include this as a "safety measure"? (Just to make sure the \ were all in place before concatenating the filename to it? Or is the last \ missing, and you add it before putting on the filename with JUSTFILNAME?

I see your point about the second message block now, it wasn't immediately obvious that I could have done something else (that's just stupidity on my part, I realize now, it wasn't an "Error" condition which is what I was reading it as, but instead, as you mention a "Do something else" point.

Let me try to play with it a bit more. The INSERT thing was also a problem, but now if I pass as well parameters for DBF and FIELD I should be able to make it work for any table, any file pick, and any copy/no copy condition.

Remember, I was once very very skilled at this, so it's a bit like having amnesia, and some parts come back slowly, but "conceptually" you know what you want to do, but we frequently default to "the things we know", especially when we don't know what we don't know. (i.e. I did not know of the GETFILE() function in VFP9... even from before, as I did not use it in other implementations I was doing... it's actually kind of good in a way coming back now, I'm stronger in some areas, but weaker in others. I generally take the approach that I know when faced with the "unknown" we tend to overcomplicate it. So I first get it working, and then look for more elegant solution. That may mean it takes me a little longer initially, but I go much faster later.
Thanks.


Best Regards,
Scott
ATS, CDCE, CTIA, CTDC

"Everything should be made as simple as possible, and no simpler."[hammer]
 
Oh,
One question on the:

#Define ccDocumentBaseDir "D:\Yourapp\Documents"

Can you use macrosubstition with a parameter?

So if I build this as a Procedure with:

PROCEDURE COPYORUPDATE
*
PARAMETERE lcBaseDir

#DEFINE ccDocumentBaseDir &lcBaseDir

Or would I pass the PARAMETER as ccDocumentBaseDir as:

PARAMETER ccDocumentBaseDir

#Define &ccDocumentBaseDir

Will either of these work? Or is #Define literal no matter what follows it? I really never use this.

Best Regards,
Scott
ATS, CDCE, CTIA, CTDC

"Everything should be made as simple as possible, and no simpler."[hammer]
 
Re: ADDBS:
Yes, it's a safetey measure, if you'd change the base dir without adding in the last backslash. ADDBS does more, it also removes trailing spaces, if eg a path comes from a C(100) field, even if it has a final backslash something like "C:\dir\ ..up to 100 space... sample.jpg" will not exist, when indeed C:\dir\sample.jpg was meant.

Of course you can also make ccBaseDir a parameter, but I'd not make it a relative path, that would always depend in the current path. If you like it so, then you may go, but I'd always add the current path via ? Sys(5)+Sys(2003), so you have an absolute base path. It indeed does not have to be hardcoded as a constant.

Bye, Olaf.
 
Simply skip teh #DEFINE and make it an LPARAMETER you work with ccBaseDir is just a constant name working in the same way as a variable name, so the rest of the code only needs adjustment, if you plan on using the normal naming convention of tc prefix for character parameters instead of cc for constant char values (against the norm to have all upper case constant names only). Why think about macro substitution in #DEFINEs? #DEFINE is a preprocessor directive, it's done at compile time, it's nothing in the runtime code, as macro substitution is. ccBaseDir is just a name, and if you remove the define and add LPARAEMTERS ccBaseDir, it's still the same name and the code will now work with a variable of that name isntead of a constant of that name.

Bye, Olaf.
 
Olaf,
Well, you're missing one vital point I was making before, which is, all file links that get "saved" to a memo file will be under the application directory. Here's why;

If you store fully qualified path in the Memo field, such as: D:\Development\Test\MyAPP\DOCS\Design Document.DWG
When I move the application to a Production enveironment (and my "test data" will include some real data that I don't want to have to reload), when the application gets installed the new destination for that file might be:

T:\MyApp\DOCS\Design Document.DWG

So now that reference is orphaned, because the memo still says D:\Development\Test\MyAPP\DOCS\Design Document.DWG

Which would force me to do something like

REPLACE ALL WITH (somecode to just get the new DIR and file type and manipulate it to take off the old parts) (paraphrasing).


My solution to that is, my framework establishes at the start what the path for the application will be. My "STARTUP" DBF file has:

gLOCALDRV = "\DEVELPMENT\TEST\MYAPP\"
gSYSPATH = "\DEVELPMENT\TEST\MYAPP\"
gSYSDEFA = "\DEVELPMENT\TEST\MYAPP\"
gDATADRIVE = "\DEVELPMENT\TEST\MYAPP\DBFS\"

etc.

The first thing my startup code does is actually determines WHERE the application is residing at that moment, and UPDATES all of these.
So if I move MyAPP to the root of a drive, these would update automatically to:

gLOCALDRV = "\MYAPP\"
gSYSPATH = "\MYAPP\"
gSYSDEFA = "\MYAPP\"
gDATADRIVE = "\MYAPP\DBFS\"

We always ignore drive letter, because they are irrelevant to the operating directory.
Then when we reference things like: \DOCS\Design Drawing.DWG

It knows to look under "\MYAPP\DOCS\Desgin Drawing.DWG" by default. And there are NO updates that need to be made to the Memo files then. You can install, copy, move, change drive letters, and it has 0 impact on the application. So this is a "design philosophy" I have chosen. And I have found it to make things infinitely easier to work with, especially when moving between DEV, TEST and PROD locations.

Let me rework the SELECTNCOPY procedure, and I'll post up what I have after.
Cheers,


Best Regards,
Scott
ATS, CDCE, CTIA, CTDC

"Everything should be made as simple as possible, and no simpler."[hammer]
 
Common, I never said to store full paths, but work them in code while checking files etc. You can always finally strip off the path before storing it via JUSTFNAME. That's what I also said already.
But use a full path when using ADIRs file skeleton or COPY FILE or anything else working in that moment.

So later on, when the database dir changed, you prepend another base dir, but when you open files, adir, copy, etc. you best work with absolute paths valid at that moment.

And of course things can move, therefore you have config meta data.

Bye, Olaf.

PS: And by the way the application directory would be a bad choice for database and documents, as it will normally be within Program Files and that's readonly, even more enforced since Vista introduced UAC. You have to store your database and document directoreis somewhere all application users can write there are several sepcific system folders for a local application and the normal choice is a network share.
 
Olfa,
Understand your point about directory locations, but in this case, this is a tightly controlled application, and there are only a few who can (and will ever) be able to get to it. In those cases, they already have write permissions to these directories. So I get your point.

Now, that said, here's where my issue stands at the moment, and I'm not sure what is happening...

But I think your point about ADIR is now a clue, so let me try to figure it out before I post the question.. I have to learn (teach the man to fish) rather than beg (give the man a fish).


Best Regards,
Scott
ATS, CDCE, CTIA, CTDC

"Everything should be made as simple as possible, and no simpler."[hammer]
 
Well, all application users might be few, nevertheless the PRogram Files means normally none but admins and even those are only allowed during elevated processes like setups.

Bye, Olaf.
 
I understand, and agree.
But remember, I'm my own customer in this case. :)

Best Regards,
Scott
ATS, CDCE, CTIA, CTDC

"Everything should be made as simple as possible, and no simpler."[hammer]
 
Olaf,
Success at last! And I finally get it all. You might be able to make this a bit more elegant, but here is how I have it put together as a procedure:

Code:
PROCEDURE SELECTNCOPY

LPARAMETERS lcTableField, lcBaseDir, llUpdateOnly, lcFileTypes

IF EMPTY(lcBaseDir)
	MESSAGEBOX("Parameter ccDocumentBaseDir can not be left blank.  Please Fix and Retry")
	RETURN .F.
ELSE
	lcDocumentBaseDir = SYS(2003)+ALLTRIM(lcBaseDir)
ENDIF
*
IF EMPTY(lcFileTypes)
	lcFileTypes = ""
ENDIF
*
lcUserpickedFile = GETFILE(lcFileTypes)
*
IF NOT EMPTY(lcUserpickedFile) AND ADIR(laDummy, lcUserpickedFile) = 1
	* The user picked an existing file and no CANCEL or ESC was used out of the GetFile dialog
	lcDestinationFile = ADDBS(lcDocumentBaseDir) + JUSTFNAME(lcUserpickedFile)
	lcSaveFile = ADDBS(lcBaseDir) + JUSTFNAME(lcUserpickedFile)
	IF ADIR(laDummy, lcDestinationFile) = 0
		* destination file name is not yet used, so we can copy
		llStore = .F.
		TRY
			COPY FILE (lcUserpickedFile) TO (lcDestinationFile)
			llStore = .T.
		CATCH
			* something went wrong in copying the file. llStore is still .F.
		ENDTRY
*
		If llStore
			REPLACE &lcTableField WITH lcSaveFile
			MESSAGEBOX("The file you picked was added to the database.")
		ELSE
			MESSAGEBOX("Something went wrong, try adding that file again later.")
			RETURN .F.
		ENDIF
	Else
		IF llUpdateOnly
			*Just updating an existing item
			REPLACE &lcTableField WITH lcSaveFile
			MESSAGEBOX("An Existing Record has been Updated.")
		ELSE
			MESSAGEBOX("File Already Exists in Destination.  Please Check and Rename if Needed.")
			RETURN .F.
		ENDIF
	ENDIF
ENDIF

RETURN .T.

It is called with the simple single line call from any place I need it with:
Code:
llReplaceImage = SELECTNCOPY("CONTACT.BUSINESSCARDFRONT","\CARDSS\",.T.,"JPG, PNG, BMP")

OR

llAddFile = SELECTNCOPY("DATAHALL.HALLFLOORPLAN","\FLOORPLANS\",.F.,"DWG")

OR simply:

llAddSomething = SELECTNCOMPY("DOCUMENTS.DOCUMENTFILE","\DOCS\")

I added the Logical "llUpdateOnly" parameter to guide whether to fail on an existing file or just let it pick it anyway. (With business cards, many times the backs of cards are the same, so I don't mind having only 1 instance of that image in the directory, rather than cluttering it up.)

So what do you think?



Best Regards,
Scott
ATS, CDCE, CTIA, CTDC

"Everything should be made as simple as possible, and no simpler."[hammer]
 
1. SYS(2003)+ALLTRIM(lcBaseDir)

As you want to have directories relative to a DBC you'd rather make use of DBC() than SYS(5)+SYS(2003), the latter SYS() functions would only help if lcBAsedir is relative to the current dir. Also take a look at Fullpath().

The good thing is, GETFILE() gives you a full path and you just need to subtract the path and prefix another base path you want. As I understand you the lcBasePath should be relative to the DBC() and you also want to store that, then the full path lcDocumentBaseDir is given by Fullpath(lcBaseDir,<<The DBC Directory>>). For the latter you may use AddBS(Justpath(DBC())) or make it a parameter, too.

2. Parameter ccDocumentBaseDir can not be left blank

Instead of using a Messagebox to denote an error, use ERROR "Parameter tcBaseDir can not be left blank. Please Fix and Retry".

3. computing file names

Code:
lcDestinationFile = ADDBS(lcDocumentBaseDir) + JUSTFNAME(lcUserpickedFile)
lcSaveFile = ADDBS(lcBaseDir) + JUSTFNAME(lcUserpickedFile)

I agree with this as far as you want to use it that way. As you want a minimum path here, you could make use of SYS(2014).

4. File already exists (outer ELSE branch)
Your deeds are good here. It may be wrong to say [highlight #FCE94F]"An Existing Record has been Updated."[/highlight]. The record you update via the REPLACE may be old or new, all the procedure knows at this point is the picked file is already in the documents directory, not whether it was picked for an old record or a new record. That would only be true, if the table you maintain with the routine would be the databases filetable table. But you don't want such a thing.

That said, as you're not maintaining such a filetable you're mixing two concerns of putting the file to the DBC documents folder, when needed, and also setting that file path in some table, besides picking the file also is not the job of this function, it's already a function not needing much wrapping around. Also the file you want to store at the dbc may be known already, then you don't want GETFILE to run.

Your routine could rather take in a file name and just return the info that's to be stored instead somewhere. And the responsibility of that is the caller, not this function.

Overall with the separation concerns this could be your new function DatabaseFilename() with GetFile being used outside of it:
Code:
Create Database (Addbs(Getenv("TEMP"))+"testdata.dbc")
Mkdir (Addbs(Getenv("TEMP"))+"dbcdocs")
*.\dbcdocs or short dbcdocs is the documents base dir, a subdir of the DBC directory
*you can of course add more base dirs for other files

Cd C:
? DatabaseFilename(Getfile(),"dbcdocs")
Create Cursor curCreditCards(mPic M)
Insert Into curCreditCards (mPic) Values (DatabaseFilename(Getfile("png,bmp,jpg"),"dbcdocs"))
Replace mPic With DatabaseFilename(Getfile("png,bmp,jpg"),"dbcdocs")
* pick a file from the dbcdocs here to test the warning:
Cd (Addbs(Getenv("TEMP"))+"dbcdocs")
? DatabaseFilename(Getfile(),"dbcdocs",.T.)
* and test the error for non existing base directory
? DatabaseFilename(Getfile(),"nonexistingdir",.T.)

Function DatabaseFilename()
   Lparameters tcFile, tcBaseDir, tlWarnAboutExistingFile, tcDBC

   Local lcDocumentBaseDir, lcDestinationFile, lcSaveFile, llSuccess, lcDatabaseBaseDir
   lcDatabaseBaseDir = Addbs(Justpath(Evl(tcDBC,Dbc())))

   If Empty(tcBaseDir)
      Error "Parameter tcBaseDir can not be left blank. Please Fix and Retry"
      lcSaveFile = ""
   Else
      lcDocumentBaseDir = Fullpath(tcBaseDir, lcDatabaseBaseDir)
      If !Directory(lcDocumentBaseDir)
         Error "Wrong Basedir "+tcBaseDir+". The Directory "+lcDocumentBaseDir+" does not exist."
         lcSaveFile = ""
      Endif
   Endif
   *
   *
   If Directory(lcDocumentBaseDir) AND Not Empty(tcFile) And Adir(laDummy, tcFile) = 1
      * The given file and the destination directory exist
      lcDestinationFile = Addbs(lcDocumentBaseDir) + Justfname(tcFile)
      lcSaveFile = Sys(2014, lcDestinationFile, lcDatabaseBaseDir)
      If Adir(laDummy, lcDestinationFile) = 0
         * destination file name is not yet used, so we can copy
         llSuccess = .F.
         Try
            Copy File (tcFile) To (lcDestinationFile)
            llSuccess = .T.
         Catch
            * something went wrong in copying the file.
         Finally
            If Not llSuccess
               * Messagebox("Something went wrong, try adding that file again later.")
               lcSaveFile = ""
            Endif
         Endtry
      Else
         If tlWarnAboutExistingFile
            lcSaveFile = ""
            Messagebox("File Already Exists in Destination.  Please Check and Rename if Needed.")
         Else  
            * nothing to do here, file already exists, lcSaveFile can be returned.
            * in fact two files of same name could differ, there are lots of pic1.jpg out there...
         Endif
      Endif
   Endif

   Return lcSaveFile
Endproc

Pardon me for restructuring the base dir behaviour with DBC, you may fix this, if you want to store relative to the application directory using SYS(16,0) or something else as the base base path, the base path where all further base paths are located in. The DBC() function call and the tcDBC parameters are all you need to concentrate on to change this.

Bye, Olaf.
 
We may be using slightly different understanding of where the files are going.

For the application the structure is like this (in production).

\MyApp\ - Where the .EXE is
\MyApp\DBFS\ - Where the DBC and DBFS are kept
\MyApp\DOCS\ - Where Docs of any type are kept
\MyAPP\CARDS\ - Where Business Card Images are kept
\MyAPP\REPORTS\ - Where Reports are kept

Etc...

So not sure if that was the structure you're expecting?

Also, the "success" messageboxes I actually took out, as they are really only there for testing. The "Failure" boxes are there so users know something happened, but the success is visible in the form right away. So they are unnecessary "stopping" points for users.

I think this is ok for now... If I find it problematic going forward, I will revisit it.


Best Regards,
Scott
ATS, CDCE, CTIA, CTDC

"Everything should be made as simple as possible, and no simpler."[hammer]
 
SYS(16,0) is where the exe is and if all docs go into the \Docs\ subdir, there is no need for the tcBaseDir parameter, as it will always be ADDBS(JUSTPATH(SYS(16,0))+"DOCS"

AS you wanted something for several base dirs, I thought of different folders for docs, pics, perhaps even more specific for credit card pics as OS encrypted folder, or whatever else you need all within the DBC directory, as it's all still data, just stored as files. This is for the tek-tips community, not just for you.

Bye, Olaf.




 
Hi Olaf,
I was on a flight from Tokyo to Singapore tonight, and I was thinking about a similar concept. So this will actually help a lot. I like the simplicity of it, though the "vagueness" of VFP SYS commands still kind of baffles me. Wish they had come up with a more readable way of expressing them. I always have to start to type SYS(... and then let the Interactive Help guide me. Some you get used to quickly, like SYS(5) and SYS(2003) but others take some time.

I think the saved files will have more value by grouping them in related directories, but I was thinking I might base sub directories off of the primary key of whatever record they come from. This would make it easier to tell even at the DIR level who the files belong to, especially if some data corruption happens and you have to "reconnect" them. Having a simple method like you show is advantageous in this case, ad it would be something like:

ADDBS(JUSTPATH(SYS(16,0))+"DOCS\"+ALLTRIM(STR(<table.primarykey>)

Where I always have my primary keys as an INT value that starts unique in each cascading relationship.
So Company = 1000001
Building = 2000001
Contact = 3000001
etc.

I get that it then has a limit of 1 million records, and that may be flawed, I could set the sizes larger, but this is not the kind of data set that gets a million records.... If it did, I'd probably hand this off as a prototype to someone else to build the new on, as it would mean my business is now madly successful, so I'll take the risk. :)

Best Regards,
Scott
ATS, CDCE, CTIA, CTDC

"Everything should be made as simple as possible, and no simpler."[hammer]
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top