content._pike
Go to the documentation of this file.
1 /* Copyright (C) 2000-2004 Thomas Bopp, Thorsten Hampel, Ludger Merkens
2  *
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.
7  *
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.
12  *
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
16  *
17  * $Id: content.pike,v 1.6 2010/08/18 20:32:45 astra Exp $
18  */
19 #include <macros.h>
20 #include <config.h>
21 #include <assert.h>
22 #include <database.h>
23 #include <classes.h>
24 //! Basic class to support Objects with content (Documents). Content
25 //! is stored in the persistency layer of sTeam.
26 class content {
27 public:
28 
29 
30 
31 
32 
33 private Thread.Mutex writeMutex = Thread.Mutex();;
34 
35 
36 
37 private int iContentID;
38 private int iContentSize = -1;
39 
40 private object oLockWrite;
41 private object oLockRead;
42 private int iDownloads=0;
43 
44  bool add_data_storage(string s,function a, function b,int|void d);
45  void download_finished();
46  void require_save(string|void a, string|void b) { _Persistence->require_save(a,b); }
47 int get_object_id();
48 
49 /**
50  * This callback function is registered via add_data_storage to provide
51  * necessary data for serialisation. The database object calls this function
52  * to save the values inside the database.
53  *
54  * @param none
55  * @return a mixed value containing the new introduced persistent values
56  * for content
57 private:
58  * @see restore_content_data
59  * @see add_data_storage
60  */
61 private:
62 mixed retrieve_content_data(string|void index)
63 {
64  if ( CALLER != _Database )
65  THROW("Illegal call to retrieve_content_data()", E_ACCESS);
66  if (index) {
67  switch(index) {
68  case "CONTENT_SIZE": return iContentSize;
69  case "CONTENT_ID": return iContentID;
70  }
71  }
72  else
73  return ([ "CONTENT_SIZE": iContentSize,
74  "CONTENT_ID": iContentID ]);
75 }
76 
77 public:
78 
79 /**
80  * This callback function is used to restore data previously read from
81 private:
82  * retrieve_content_data to restore the state of reading
83  *
84 private:
85  * @param a mixed value previously read via retrieve_content_data
86  * @return void
87 private:
88  * @see retrieve_content_data
89  * @see add_data_storage
90  */
91 private:
92 void restore_content_data(mixed data, string|void index)
93 {
94  if ( CALLER != _Database )
95  THROW("Illegal call to restore_content_data()", E_ACCESS);
96 
97  if (index) {
98  switch (index) {
99  case "CONTENT_SIZE" : iContentSize = data; break;
100  case "CONTENT_ID": iContentID = data; break;
101  }
102  }
103  else if (arrayp(data)) {
104  [ iContentSize, iContentID ] = data;
105  }
106  else {
107  iContentSize = data["CONTENT_SIZE"];
108  iContentID = data["CONTENT_ID"];
109  }
110 }
111 
112 public:
113 
114 
115 /**
116  * Initialize the content. This function only sets the data storage
117  * and retrieval functions.
118  *
119  */
120 private:
121  void init_content()
122 {
123 private:
124  add_data_storage(STORE_CONTENT, retrieve_content_data,
125  restore_content_data, 1);
126 }
127 
128 
129 class DownloadHandler {
130 public:
131  object odbhandle;
132  object ofilehandle;
133  void create(object oDbHandle, object oFileHandle) {
134  odbhandle = oDbHandle;
135  ofilehandle = oFileHandle;
136  }
137  /**
138  * This function gets called from the socket object associated with
139  * a user downloads a chunk. It cannot be called directly - the
140  * function get_content_callback() has to be used instead.
141  *
142  * @param int startpos - the position
143  * @return a chunk of data | 0 if no more data is present
144  * @see receive_content
145  * @see get_content_callback
146  */
147  string send_content(int startpos) {
148  if ( !objectp(odbhandle) && !objectp(ofilehandle) )
149  return 0;
150 
151  string buf;
152  if ( _Persistence->is_storing_content_in_filesystem() &&
153  objectp(ofilehandle) )
154  buf = ofilehandle->read( DB_CHUNK_SIZE );
155  else if ( _Persistence->is_storing_content_in_database() &&
156  objectp(odbhandle) )
157  buf = odbhandle->read( DB_CHUNK_SIZE );
158 
159  if ( stringp(buf) )
160  return buf;
161 
162  if (objectp(odbhandle) || objectp(ofilehandle))
163  {
164  iDownloads--;
165  if (iDownloads == 0) // last download finished?
166  destruct(oLockRead); // release writing lock
167 
168  if ( objectp(ofilehandle) ) destruct(ofilehandle);
169  if ( objectp(odbhandle) ) destruct(odbhandle);
170  }
171  // callback to notify about finished downloading
172  download_finished();
173 
174  return 0;
175  }
176 
177  void destroy() {
178  if ( objectp(odbhandle) || objectp(ofilehandle) )
179  {
180  iDownloads --;
181  if (iDownloads ==0)
182  destruct(oLockRead);
183  if ( objectp(odbhandle) ) destruct(odbhandle);
184  if ( objectp(ofilehandle) ) destruct(ofilehandle);
185  }
186  }
187  string _sprintf() { return "DownloadHandler"; }
188  string describe() { return "DownloadHandler"; }
189 }
190 
191 public:
192 
193 
194 class UploadHandler {
195 public:
196  object odbhandle;
197  object file;
198  int iWrittenSize;
199  function content_finished;
200 
201  void create(object oDbHandle, function cfinished, object f) {
202  odbhandle= oDbHandle;
203  content_finished = cfinished;
204  content_begin();
205  file = f;
206  }
207  /**
208  * save_chunk is passed from receive_content to a data storing process,
209  * to store one chunk of data to the database.
210  *
211  * @param string chunk - the data to store
212  * @param int start - start position of the chunk relative to complete
213  * data to store.
214  * @param int end - similar to start
215  * @return void
216  * @see receive_content
217  */
218  void save_chunk(string chunk) {
219  if ( !stringp(chunk) )
220  {
221  local_content_finished();
222  return;
223  }
224  if ( _Persistence->is_storing_content_in_database() )
225  odbhandle->write(chunk);
226  if ( _Persistence->is_storing_content_in_filesystem() && objectp(file) )
227  file->write(chunk);
228  iWrittenSize += strlen(chunk);
229  }
230 
231  /**
232  * This function gets called, when an upload is finished. All locks
233  * are removed and the object is marked for the database save demon
234  *
235  * @see save_chunk
236  */
237  void local_content_finished() {
238  odbhandle->flush();
239  int iWrittenID = odbhandle->dbContID();
240  if ( objectp(file) )
241  file->close();
242 
243  // clean old content?
244  iContentID = iWrittenID;
245  iContentSize = iWrittenSize;
246  require_save(STORE_CONTENT);
247  odbhandle->close(remote_content_finished);
248  return;
249  }
250 
251  void remote_content_finished() {
252  destruct(odbhandle);
253  content_finished();
254  }
255 
256  void destroy() {
257  if (odbhandle)
258  content_finished();
259  }
260  string _sprintf() { return "UploadHandler"; }
261  string describe() { return "UploadHandler"; }
262 
263 }
264 
265 /**
266  * The function returns the function to download the content. The
267  * object is configured and locked for the download and the
268  * returned function send_content has to be subsequently called
269  * in order to get the data.
270  *
271  * @param none
272  * @return function "send_content" a function that returns the content
273  * of a given range.
274  * @see send_content
275  */
276 function get_content_callback(mapping vars)
277 {
278  if ( iContentID == 0 )
279  LOG_DB("get_content_callback: missing ContentID");
280 
281  if (iDownloads == 0)
282  {
283  object lock = writeMutex->lock(); // wait for content_cleanup
284  oLockRead = lock;
285  }
286  iDownloads++;
287 
288  object oDbDownloadHandle, oFileDownloadHandle;
289  if ( _Persistence->is_storing_content_in_filesystem() )
290  oFileDownloadHandle = _Persistence->open_content_file( iContentID, "r" );
291  if ( _Persistence->is_storing_content_in_database() )
292  oDbDownloadHandle = _Database->new_db_file_handle( iContentID, "r" );
293  object oDownloadHandler =
294  DownloadHandler( oDbDownloadHandle, oFileDownloadHandle );
295  ASSERTINFO( objectp(oDbDownloadHandle) || objectp(oFileDownloadHandle),
296  "No file handle found !" );
297  if ( objectp(oDbDownloadHandle) )
298  LOG( "db_file_handle() allocated, now sending...\n" );
299  if ( objectp(oFileDownloadHandle) )
300  LOG( "file_handle() allocated, now sending...\n" );
301  return oDownloadHandler->send_content;
302 }
303 
304 /**
305  * This function gets called to initialize the download of a content.
306  * The returned function has to be called subsequently to write data.
307  * After the upload is finished the function has to be called with
308  * the parameter 0.
309  *
310  * @param int content_size -- the size of the content that will be
311  * passed in chunks to the function returned
312  * @return a function, that will be used as call_back by the object
313  * calling receive_content to actually store the data.
314  * @see save_chunk
315  * @see send_content
316  */
317 function receive_content(int content_size)
318 {
319  object oHandle = _Database->new_db_file_handle(0,"wtc");
320  if ( !iContentID )
321  iContentID = oHandle->dbContID();
322  object file = _Persistence->open_content_file( iContentID, "wct" );
323  object oUploadHandler = UploadHandler(oHandle, content_finished, file);
324  return oUploadHandler->save_chunk;
325 }
326 
327 protected:
328  void prepare_upload(int content_size, void|string lock)
329 {
330 }
331 
332 public:
333 
334 object get_upload_handler(int content_size)
335 {
336  object oHandle = _Database->new_db_file_handle(0,"wtc");
337  if ( !iContentID )
338  iContentID = oHandle->dbContID();
339  object file = _Persistence->open_content_file( iContentID, "wct" );
340  return UploadHandler(oHandle, content_finished, file);
341 }
342 
343 /**
344  * update_content_size - reread the content size from the database
345  * this is a hot-fix function, to allow resyncing with the database tables,
346  * this function definitively should be obsolete.
347  *
348  * @param none
349  * @return nothing
350  */
351 void update_content_size()
352 {
353  iContentSize = _Persistence->get_content_size( iContentID );
354  require_save(STORE_CONTENT);
355 }
356 
357 /**
358  * evaluate the size of this content
359  *
360  * @param none
361  * @return int - size of content in byte
362  */
363 int
364 get_content_size()
365 {
366  if (!iContentID) // no or unfinished upload
367  return 0;
368 
369  if ( iContentSize <= 0 )
370  update_content_size();
371 
372  return iContentSize;
373 }
374 
375 
376 /**
377  * Get the ID of the content in the database.
378  *
379  * @return the content id
380  */
381 int get_content_id()
382 {
383  return iContentID;
384 }
385 
386 // versioning
387 void set_content_id(int id)
388 {
389  if ( CALLER != get_factory(CLASS_DOCUMENT) )
390  steam_error("Unauthorized call to set_content_id() !");
391  iContentID = id;
392  require_save(STORE_CONTENT);
393 }
394 
395 /**
396  * Get the content of the object directly. For large amounts
397  * of data the download function should be used. It is possible
398  * to pass a len parameter to the function so only the first 'len' bytes
399  * are being returned.
400  *
401  * @param int|void len
402  * @return the content
403  */
404 protected:
405  string
406 _get_content(int|void len)
407 {
408  string buf;
409  mixed cerr;
410  LOG_DB("content.get_content() of " + iContentID);
411 
412  if (iDownloads == 0)
413  {
414  object lock;
415  catch(lock = writeMutex->trylock());
416  if ( !objectp(lock) )
417  THROW("no simultanous write access on content", E_ACCESS);
418  oLockWrite = lock;
419  }
420  iDownloads++;
421 
422  cerr = catch {
423  buf = _Persistence->get_content( iContentID, len );
424  };
425 
426  iDownloads--;
427  if (iDownloads == 0)
428  destruct(oLockWrite);
429 
430  if (cerr)
431  throw(cerr);
432 
433  return buf;
434 }
435 
436 public:
437 
438 string get_content(int|void len)
439 {
440  return _get_content(len);
441 }
442 
443 /**
444  * set_content, sets the content of this instance.
445  *
446  * @param string cont - this will be the new content
447  * @return int - content size (or -1?)
448  * @see receive_content, save_content
449  *
450  */
451 int
452 set_content(string cont)
453 {
454  if ( !stringp(cont) )
455  error("set_content: no content given - needs string !");
456 
457  // save directly! (use receive content and upload functionality
458  // for large amount of data)
459  prepare_upload(strlen(cont));
460 
461  iContentID = _Persistence->set_content( cont );
462 
463  // store content id and size
464  iContentSize = strlen(cont);
465  require_save(STORE_CONTENT);
466 
467  // call myself - write_now() finishes writting directly
468  content_finished();
469  return strlen(cont);
470 }
471 
472 /**
473  * When the object is deleted its content has to be removed too.
474  *
475  */
476 protected:
477 final void
478 delete_content()
479 {
480  if (iContentID)
481  {
482  object lock = writeMutex->lock();
483  _Persistence->delete_content( iContentID );
484  destruct(lock);
485  }
486 }
487 
488 public:
489 
490 
491 protected:
492  void content_finished() {
493  // call for compatibility reasons
494 }
495 
496 public:
497 
498 protected:
499  void content_begin() {
500 }
501 
502 public:
503 
504 
505 };