'=========================================================================== ' Subject: QWK LAYOUT Date: 12-15-94 (23:57) ' Author: John McTaggart Code: QB, QBasic, PDS ' Origin: charlie@charlie.seanet.com Packet: FAQS.ABC '=========================================================================== '>Say I have had no luck so far in tracking down to obtain the most '>recent version of QWKLAYxx.zip. Currently I have QWKLAY13.zip but the info '>is about 2 years old.. can anyone provide me with BBS or Internet FTP site '>location of a more updated file for the QWK layout? Long distance is not a '>factor, I will call anywhere for the information.. and BTW any SYSOPS out '>there utilizing the QWKE format for their mail? ' ' Here it is, pretty well broken down. I hacked this out a year ago ' or more. I made a lttle utility to extract useless messages from ' Messages.Dat files. It could use a couple of well placed functions ' but will shread a 1 meg dat file surprisingly fast! I don't think ' it's all here, but the main parts are. I also have the routines for ' the Control.Dat file hanging around here collecting dust. If you ' can make some improvements on this twisted little mess, I enjoy ' seeing them. I hope this helps some. ' 'Here are a couple of type structures and actual useful stuff that may 'prompt some of you to write mail readers, and/or utilities... 'For the novice, this is an example of data driven programming where 'the program will accomodate different files more or less on the 'fly. The trick is to produce algorythms that will be reused on 'files whose actual format is already known and doesn't change... 'The examples are in pure Basic, but can be improved upon greatly 'with some of the existing toolboxes that are on the market including 'those from Crescent and MicroHelp... 'This is the actual Messages.Dat header type and consists of 128bytes TYPE QWKheader Status AS STRING * 1 'Message Status MessageNum AS STRING * 7 'Actual message # MessageDate AS STRING * 8 'Message date MessageTime AS STRING * 5 'Message time MessageTo AS STRING * 25 'Who's it to MessageFrom AS STRING * 25 'Who's it from Subject AS STRING * 25 'What are we talking about Password AS STRING * 12 'Password, never actually seen it used Reference AS STRING * 8 'Convoluted way to reference a message NumBlocks AS STRING * 6 'How many 128byte blocks in message KillFlag AS STRING * 1 'Self explanatory Conference AS INTEGER 'Conference # NotUsed AS STRING * 2 ' NetWorkTag AS STRING * 1 'Net tag flag END TYPE DIM QwkInfo AS QWKheader 'Each record in the Messages.Dat file is referenced by a record # 'held in an NDX file. In other words an NDX file named 001.NDX will 'hold record pointers to all the messages in conference #1. The format 'used is in MKS$ format and in my opinion should be converted to long 'integer in one file instead of scattered across multiple ones. 'All the high level languages including C, Pascal, and Basic would 'benefit from not having to convert these pointers back and forth. By 'using one file it would save considerable hard drive space when 'taking cluster size into consideration, and would save the program 'from having to open several files to get the pointer information. 'Simply put, it would be much faster... 'The NDX files type structure... TYPE NDXInfo Pointer AS STRING * 4 'Record pointer Conf AS STRING * 1 'Conference # END TYPE DIM NDX AS NDXInfo 'Here are the neccesary conversions... 'If retreiving them from an NDX file try this... 'You're path will probably be different so adjust accordingly... 'Try the biggest NDX file you can... '---------------------------------------------------------------------- OPEN "C:\Temp\Work\006.NDX" FOR BINARY AS #1 HowMany& = LOF(1) \ 5 '--How many records are we getting? FOR X = 1 TO HowMany& '--Each NDX is 5 bytes long, and the NDX file is always divisable by 5 GET #1, , NDX '--Get them to LOF(1) \ 5 RecPtr = CVSMBF(NDX.Pointer) '--Convert them PRINT ASC(NDX.Conf); RecPtr; '--Print it to prove it worked, print conference and message pointer NEXT CLOSE 'These pointers actually point to the message in the messages.dat file 'in order to find the location to seek to in Messages.Dat simply 'multiply by 128... 'Notice we get them in 5 byte chunks, via the NDXInfo type struc... 'As a note, notice that the conference in the QWKheader is in integer 'format and the NDX struct uses a string. Hey, I didn't invent it! 'Since the pointer is a MKS$ 4 byte string you need to use a string 'length of 1 for the conference. If we used an integer it would be '6 bytes instead of 5... 'If outputting to the NDX file you need to know the LOC of the record 'and divide it by 128. Using binary IO works very nicely... 'Doing the first record outside the DO/LOOP allows you not to have 'to worry about checking for the first record with IFs... 'Simply by adding another DO/LOOP to check for the actual conferences 'in the Messages.Dat file first you can actually ignore the NDX files 'altogether. The advantage to this approach would be mail doors that 'allow the user to not include the NDX files in the actual QWK packet. 'OLX is a good example of this. If the NDX files don't exist it will 'rebuild them when you open the packet. The benefit is less time when 'downloading and smaller QWK packets... OPEN "Messages.Dat" FOR BINARY AS #1 '--Open the messages file OPEN "NDX.NDX" FOR BINARY AS #2 'Our new index file for whole thing, not each conference 'This will build a single NDX file for the entire Messages.Dat 'file. After trying it, it really made it simple...Why have 10 'different file. Anyone know why this is? Am I missing something... SEEK #1, 129 '--Seek past the mail door header GET #1, , QwkInfo '--Get the record header W$ = MKSMBF$(2) '--Record #2 is always first record CON$ = CHR$(QwkInfo.Conference) '--Convert Integer to String PUT #2, , W$ PUT #2, , CON$ '--Put to NDX file SEEK #1, 129 '--Seek back to beginning of messages, redundant but works... DO GET #1, , QwkInfo '--Get the whole header with get, then pull out what we need Length& = VAL(QwkInfo.NumBlocks) '--This is the number of 128 byte blocks in the message CON$ = CHR$(QwkInfo.Conference) '--Convert the QwkInfo.Conference Integer IF Length& = 0 THEN EXIT DO '--If the Value of QwkInfo.NumBlocks is 0 then we're done. '--A message can't have a length of 0 WhereAmI& = LOC(1) '--Store our current file offset for later OnTheFly& = Length& * 128 - 128 '--This is the actual message length the header info '--If you want to display the message you would assign a '--string with OnTheFly& length... 'Mess$ = SPACE$(OnTheFly&) 'GET #1, , Mess$ '--All the other info is still in QwkInfo SEEK #1, WhereAmI& + OnTheFly& + 1 '--Chew our way through the file, incrementing as we go Where$ = MKSMBF$(LOC(1) \ 128 + 1) '--Convert our offset to the proper format for the NDX file PUT #2, , Where$ PUT #2, , CON$ '--Put them to a file LOOP WHILE NOT EOF(1) '--Loop until the whole files been read. CLOSE ' You now will have a file named NDX.NDX that holds the conference ' number and record pointer for all the messages in the Messages.Dat ' file... ' John, Here is some stuff to help handle the Control.Dat file... ' It's not terribly efficient, but it does work. 'Since Control.Dat is basically a text file 'the first thing we need to do is find the # of lines it has... OPEN "C:\Temp\Work\Control.Dat" FOR INPUT AS #1 'Open Control.Dat LineCount% = 0 'Set the linecounter to 0 DO 'Start a loop LINE INPUT #1, Line$ 'And line input each LineCount% = LineCount% + 1 'Add it to the counter LOOP UNTIL EOF(1) 'Until the end of the file 'LineCount% should now hold our total # of lines in the 'Control.Dat file. Knowing this information will allow us to 'dimension an array to hold the info... REDIM ControlDat$(1 TO LineCount%) 'Dimension a string array for that many lines. 'Start at the beginning of the file SEEK #1, 1 'Start at beginning FOR Y = 1 TO LineCount% 'Loop through the Control file and LINE INPUT #1, Ours$ 'Input the lines, one at a time ControlDat$(Y) = Ours$ 'Load our array with the contents of the file NEXT Y 'We're done with the file so close it... CLOSE 'Now lets see if it worked! CLS PRINT "Board Name.........."; ControlDat$(1) NumConferences = VAL(ControlDat$(11)) + 1 PRINT "Total Conferences..."; LTRIM$(STR$(NumConferences)) 'ControlDat$(11) is the total # of conferences - 1 'If anybody knows why its not the actual # I'd be curious as to why Steps = 10 + NumConferences * 2 'The above (Steps) is pretty important for finding the total 'conferences and there proper numbers. Of course again its only 'one way. There may be a better way but the above works. Print 'the total conferences FOR C = 12 TO Steps STEP 2 PRINT ControlDat$(C + 1); " "; ControlDat$(C) NEXT 'Notice the print statement actually extracts the name first 'and then the number from the array. Ideally you would 'seperate the conferences and conference numbers and put them 'in a scrollable picklist. 'I hope this might help someone. I found most of this by fiddling 'around and looking at a couple of FAQs on QWK files. Some of 'them didn't go into to much detail and some were just wrong. 'if anybody happens to find some better methods that are in 'pure Basic form, I'd love to see them. Of course some well 'placed assembly goes along way with these routines, but for 'this I took a purest approach. Thanks...