1 /* Copyright (C) 2000-2007 Thomas Bopp, Thorsten Hampel, Ludger Merkens
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 * $Id: db_file.pike,v 1.2 2009/08/07 15:22:36 nicke Exp $
28 * this is the database file emulation, which stores a binary file
29 * in a sequence of 64k blobs in a SQL database.
34 private int iNextRecNbr; /* last current read */
35 private int iCurrPos; /* current position */
36 private int iMaxRecNbr; /* last rec_order block */
37 private int iMinRecNbr; /* first rec_order block */
38 private string sMode; /* read/write */
39 private string sReadBuf="";
40 private string sWriteBuf="";
41 private int iFileSize=-1; /* not set otherwise >=0 */
42 private object readRecord;
43 private int iStopReader=0;
44 private int iPrefetch=1;
46 private int iLastAccess;
48 array get_database_handle(int id)
50 return _Database->connect_db_file(id);
53 void create(int ID, string mode) {
60 * open a database content with given ID, if ID 0 is given a new ID
63 * @param int ID - (an Content ID | 0)
64 * @param string mode -
65 * 'r' open file for reading
66 * 'w' open file for writing
67 * 'a' open file for append (use with 'w')
68 * 't' truncate file at open (use with 'w')
69 * 'c' create file if it doesn't exist (use with 'w')
70 * 'x' fail if file already exist (use with 'c')
72 * How must _always_ contain exactly one 'r' or 'w'.
73 * if no ID is given, mode 'wc' is assumed
74 * 'w' assumes 'a' unless 't'
77 * @return On success the ID (>1) -- 0 otherwise
81 int open(int ID, string mode) {
84 Sql.sql_result odbResult; // db = iID >> OID_BITS;
88 [fdb, iID] = get_database_handle(iID);
90 // LOG("opened db_file for mode "+sMode+" with id "+iID);
93 if (search(sMode, "r")!=-1)
96 fdb()->big_query("select min(rec_order), max(rec_order) "+
97 "from doc_data where "+
99 array res= odbResult->fetch_row();
100 iMinRecNbr= (int) res[0];
101 iMaxRecNbr= (int) res[1]; // both 0 if FileNotFound
102 iNextRecNbr = iMinRecNbr;
105 fdb()->big_query("select rec_data from doc_data where doc_id="+iID+
106 " and rec_order="+iMinRecNbr);
107 if (odbResult->num_rows()==1)
109 [sReadBuf] = odbResult->fetch_row();
110 sReadBuf = fdb()->unescape_blob(sReadBuf);
111 if (strlen(sReadBuf)<MAX_BUFLEN) // we got the complete file
112 iFileSize = strlen(sReadBuf);
114 iPrefetch = 1; // otherwise assume prefetching
120 if (search(sMode, "w")==-1) // neither read nor write mode given
123 // Append to database, calculate next RecNbr
124 odbResult = fdb()->big_query("select max(rec_order) from "+
125 "doc_data where doc_id = "+iID);
126 if (!objectp(odbResult))
129 iNextRecNbr = ((int) odbResult->fetch_row()[0])+1;
131 if (search(sMode, "c")!=-1)
133 if ((search(sMode,"x")!=-1) && (iNextRecNbr != -1))
136 if (iNextRecNbr == -1)
140 if (search(sMode, "t")!=-1)
143 fdb()->big_query("delete from doc_data where doc_id = " + iID);
147 if (iNextRecNbr == -1) // 'w' without 'c' but file doesn't exist
154 private void write_buf(string data)
156 _Database->write_into_database(iID, iNextRecNbr, data);
157 iMaxRecNbr=iNextRecNbr;
165 private int write_buf_now(string data)
168 string line = "insert into doc_data values('"+
169 fdb()->escape_blob(data)+"', "+ iID +", "+iNextRecNbr+")";
170 iMaxRecNbr = iNextRecNbr;
172 mixed err = catch{fdb()->big_query(line);};
174 FATAL("Fatal error while writting FILE into database: %O\n%O",err[0],err[1]);
185 if (search(sMode,"w")!=-1)
187 if (strlen(sWriteBuf) > 0)
188 write_buf(sWriteBuf);
189 iFileSize = (((iMaxRecNbr - iMinRecNbr)-1) * MAX_BUFLEN) +
194 int close(void|function close_callback)
196 if (functionp(close_callback))
197 _Database->write_into_database(iID, 0, close_callback);
207 int get_last_access() { return iLastAccess; }
210 void check_status(object record)
213 mlock = record->fullMutex->lock();
214 if ( functionp(record->restore) ) {
215 record->restore(record);
224 string read(int|void nbytes, int|void notall)
229 Sql.sql_result odbData;
232 if ( search(sMode,"r") == -1 )
235 iLastAccess = time();
238 if (!nbytes) // all the stuff -> no queuing
240 odbData = fdb()->big_query("select rec_data from doc_data "+
242 " order by rec_order");
243 while (line = odbData->fetch_row())
244 lbuf += ({ fdb()->unescape_blob(line[0]) });
247 else if ( !objectp(readRecord) &&
248 (iFileSize == -1 || iFileSize> MAX_BUFLEN ) && iPrefetch )
250 readRecord = _Database->read_from_database(iID,
256 iSumLen = strlen(sReadBuf);
257 lbuf = ({ sReadBuf });
259 while ( iSumLen < nbytes && stringp(line) )
261 if ( readRecord ) // check for Prefetched Content
263 check_status(readRecord);
264 line = readRecord->contFifo->read();
266 else if (iNextRecNbr < iMaxRecNbr) // large files + seek
269 readRecord = _Database->read_from_database(iID,
273 check_status(readRecord);
274 line = readRecord->contFifo->read();
277 line = 0; // small files
282 iSumLen += strlen(line);
290 sReadBuf = lbuf * "";
292 if (!strlen(sReadBuf))
295 if (strlen(sReadBuf) <= nbytes) // eof or notall
299 iCurrPos += strlen(line);
300 sendByte += strlen(line);
304 line = sReadBuf[..nbytes-1];
305 sReadBuf = sReadBuf[nbytes..];
306 iCurrPos += strlen(line);
307 sendByte += strlen(line);
311 int write_now(string data)
313 int written = low_write(data, write_buf_now);
314 written += write_buf_now(sWriteBuf);
318 int write(string data)
320 return low_write(data, write_buf);
324 int low_write(string data, function write_buf) {
327 if (search(sMode, "w")==-1)
331 while (strlen(sWriteBuf) >= MAX_BUFLEN)
333 write_buf(sWriteBuf[..MAX_BUFLEN-1]);
334 sWriteBuf = sWriteBuf[MAX_BUFLEN..];
335 iWritten += MAX_BUFLEN;
337 iCurrPos += iWritten;
345 object s = Stdio.Stat();
352 if (iFileSize!=-1) // already calculated
358 if (search(sMode, "w")!=-1)
361 iLastChunkLen = strlen(sWriteBuf);
363 erg = ((iMaxRecNbr-iMinRecNbr) * MAX_BUFLEN) + iLastChunkLen;
368 res = fdb()->big_query(
369 "select length(rec_data) from doc_data "+
370 "where doc_id ="+iID+" and rec_order="+iMaxRecNbr);
372 mixed row = res->fetch_row();
374 iLastChunkLen = ((int)row[0]);
379 iFileSize = ((iMaxRecNbr-iMinRecNbr) * MAX_BUFLEN) + iLastChunkLen;
389 private void stop_reader()
391 if ( objectp(readRecord) ) {
392 //werror("Trying to stop read operation on "+ readRecord->iID+"\n");
393 readRecord->stopRead = 1;
400 * seek in an already open database content to a specific offset
401 * If pos is negative it will be relative to the start of the file,
402 * otherwise it will be an absolute offset from the start of the file.
404 * @param int pos - the position as described above
405 * @return The absolute new offset or -1 on failure
408 * @caveats The old syntax from Stdio.File->seek with blocks is not
416 Sql.sql_result odbResult;
419 //werror("Seek(%d)\n", pos);
422 SeekPos = iCurrPos-SeekPos;
425 SeekBlock = SeekPos / MAX_BUFLEN;
426 SeekBlock += iMinRecNbr;
428 stop_reader(); // discard prefetch and stop read_thread
429 odbResult = fdb()->big_query("select rec_data from doc_data where doc_id="+
430 iID+" and rec_order="+SeekBlock);
431 if (odbResult->num_rows()==1)
433 [sReadBuf] = odbResult->fetch_row();
434 sReadBuf = sReadBuf[(SeekPos % iMinRecNbr)..];
436 iNextRecNbr = SeekBlock+1;
443 * tell the current offset in an already open database content
444 * @return The absolute offset
454 return "kernel/db_file(id="+iID+", stopped="+iStopReader+")";