Document._pike
Go to the documentation of this file.
1 /* Copyright (C) 2000-2006 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: Document.pike,v 1.3 2010/08/18 20:32:45 astra Exp $
18  */
19 inherit "/classes/Object" : __object;
20 inherit "/base/content" : __content;
21 #include <attributes.h>
22 #include <classes.h>
23 #include <macros.h>
24 #include <events.h>
25 #include <types.h>
26 #include <config.h>
27 #include <database.h>
28 //! A Document is an object with content (bytes). The content is stored
29 //! in the persistency layer of steam. When content is changed the
30 //! EVENT_UPLOAD and EVENT_DOWNLOAD events are trigered.
31 class Document : public Object,content{
32 public:
33 
34 
35 
36 
37 
38 
39 
40 private:
41  void init_document() { }
42 
43 public:
44 
45 /**
46  * Init callback function.
47  *
48  */
49 protected:
50 final void
51 init()
52 {
53  __object::init();
54  __content::init_content();
55  init_document();
56 }
57 
58 public:
59 
60 /**
61  * Called after the document was loaded by database.
62  *
63  */
64 protected:
65  void load_document()
66 {
67 }
68 
69 public:
70 
71 /**
72  * Called after the document was loaded by database.
73  *
74  */
75 protected:
76  void load_object()
77 {
78  load_document();
79 }
80 
81 public:
82 
83 /**
84  * Duplicate the Document and its content.
85  *
86  * @return the duplicate of this document.
87  */
88 mapping do_duplicate(void|mapping params)
89 {
90  // DocumentFactory deals with content_obj variable
91  if ( !mappingp(params) )
92  params = ([ ]);
93 
94 
95  if ( params->content_id )
96  params->content_obj = 0;
97 
98  params->mimetype = do_query_attribute(DOC_MIME_TYPE);
99  // do not copy thumbnail attribute - thumbnails are
100  // generated on the fly
101  m_delete(params, DOC_IMAGE_THUMBNAIL);
102 
103  return ::do_duplicate( params );
104 }
105 
106 /**
107  * Destructor function of this object removes all references
108  * and deletes the content.
109  *
110  */
111 protected:
112  void
113 delete_object()
114 {
115  mixed err = catch {
116  if ( mappingp(mReferences) ) {
117  foreach(indices(mReferences), object o) {
118  if ( !objectp(o) ) continue;
119 
120  o->removed_link();
121  }
122  }
123  // delete all versions, try to make it as atomic as possible:
124  mapping versions = do_query_attribute(DOC_VERSIONS);
125  if ( mappingp(versions) ) {
126  // gather all versions (recursively):
127  array all_versions = ({ });
128  foreach ( values(versions), object v ) {
129  if ( !objectp(v) ) continue;
130  mapping v_versions = v->query_attribute(DOC_VERSIONS);
131  if ( !mappingp(v_versions) ) continue;
132  foreach ( values(v_versions), object v2 ) {
133  // add v's old version if it is valid and has not already been added:
134  if ( !objectp(v2) ) continue;
135  if ( search(all_versions, v2) >= 0 ) continue;
136  all_versions |= ({ v2 });
137  }
138  }
139  // check if there is any broken version reference:
140  foreach ( all_versions, object v ) {
141  if ( !objectp(v) || v->status() == PSTAT_DELETED )
142  continue; // already deleted
143 
144  object v_versionof = v->query_attribute(OBJ_VERSIONOF);
145  if ( !objectp(v_versionof) ) {
146  steam_error( "Version mismatch: old version %O doesn't seem to be a "
147  "version of any object.", v );
148  return;
149  }
150  (search( all_versions, v_versionof ) < 0) )
151  steam_error( "Version mismatch: old version %O is a version of a "
152  "different object: %O.", v, v_versionof );
153  }
154  // delete old versions, prevent recursions:
155  foreach ( all_versions, object v ) {
156  if ( !objectp(v) || v->status() == PSTAT_DELETED )
157  continue; // already deleted
158 
159  v->set_attribute( DOC_VERSIONS, ([ ]) );
160  v->set_attribute( OBJ_VERSIONOF, 0 );
161  v->delete();
162  }
163  }
164  };
165  if (err)
166  FATAL("Error while deleting document %O\n%O", err[0], err[1]);
167  err=catch(::delete_content());
168  if (err)
169  FATAL("Error while deleting document content %O\n%O", err[0], err[1]);
170  ::delete_object();
171 }
172 
173 public:
174 
175 /**
176  * Adding data storage is redirected to objects functionality.
177  *
178  * @param function a - store function
179  * @param function b - restore function
180  * @return whether adding was successfull.
181  */
182 protected:
183  bool
184 add_data_storage(string a,function b, function c, int|void d)
185 {
186  return __object::add_data_storage(a,b,c,d);
187 }
188 
189 public:
190 
191 /**
192  * Get the content size of this document.
193  *
194  * @return the content size in bytes.
195  */
196 int get_content_size()
197 {
198  return __content::get_content_size();
199 }
200 
201 /**
202  * Returns the id of the content inside the Database.
203  *
204  * @return the content-id inside database
205  */
206 final int get_content_id()
207 {
208  return __content::get_content_id();
209 }
210 
211 /**
212  * Callback function when a download has finished.
213  *
214  */
215 protected:
216  void download_finished()
217 {
218  run_event(EVENT_DOWNLOAD_FINISHED, CALLER);
219 }
220 
221 public:
222 
223 /**
224  * give status of Document similar to file->stat()
225  *
226  * @param none
227  * @return ({ \o700, size, atime, mtime, ctime, uid, 0 })
228  * @see file_stat
229  */
230 array stat()
231 {
232  int creator_id = get_creator() ? get_creator()->get_object_id() : -1;
233 
234  return ({
235  33279, // -rwx------
236  get_content_size(),
237  do_query_attribute(OBJ_CREATION_TIME),
238  do_query_attribute(DOC_LAST_MODIFIED) ||
239  do_query_attribute(OBJ_CREATION_TIME),
240  do_query_attribute(DOC_LAST_ACCESSED),
241  creator_id,
242  creator_id,
243  query_attribute(DOC_MIME_TYPE), // aditional, should not be a prob?
244  });
245 }
246 
247 protected:
248  void update_mimetype(string mime)
249 {
250  object typeModule = get_module("types");
251  if ( objectp(typeModule) ) {
252  string classname = typeModule->query_document_class_mime(mime);
253  // document classes are changed, but never to 'Document',
254  // because all derived classes only include some special functionality
255  if ( classname != get_class() && classname != "Document" ) {
256  werror("updating mimetype for %s to %s (class=%s, new_class=%s\n",
257  get_identifier(), mime, get_class(), classname);
258  }
259  object fulltext = get_module("fulltext");
260  if (objectp(fulltext))
261  }
262 }
263 
264 public:
265 
266 mixed set_attribute(string index, mixed data)
267 {
268  mixed res = ::set_attribute(index, data);
269 #if 0
270  object caller = CALLER;
271  if ( functionp(caller->get_object_class) &&
272  caller->get_object_class() & CLASS_FACTORY )
273  return res;
274 #endif
275  if ( index == OBJ_NAME )
276  {
277  if ( do_query_attribute(DOC_MIME_TYPE) == MIMETYPE_UNKNOWN )
278  {
279  // try to find mimetype by new name ...
280  object typeModule = get_module("types");
281  if ( objectp(typeModule) && stringp(data) && strlen(data) > 0 )
282  {
283  string ext;
284  sscanf(data, "%*s.%s", ext);
285  string mime = typeModule->query_mime_type(ext);
286  if ( mime != MIMETYPE_UNKNOWN )
287  {
288  do_set_attribute(DOC_MIME_TYPE, mime);
289  update_mimetype(mime);
290  }
291  }
292  }
293  }
294  else if ( index == DOC_MIME_TYPE ) {
295  update_mimetype(data);
296  }
297  return res;
298 }
299 
300 int get_object_class() { return ::get_object_class() | CLASS_DOCUMENT; }
301 final bool is_document() { return true; }
302 
303 /**
304  * content function used for download, this function really resides in
305  * base/content and this overridden function just runs the appropriate event
306  *
307  * @return the function for downloading (when socket has free space)
308  * @see receive_content
309  */
310 function get_content_callback(mapping vars)
311 {
312  object obj = CALLER;
313 
314  if ( functionp(obj->get_user_object) && objectp(obj->get_user_object()) )
315  obj = obj->get_user_object();
316 
317  check_lock("read");
318  try_event(EVENT_DOWNLOAD, obj);
319 
320  do_set_attribute(DOC_LAST_ACCESSED, time());
321 
322  run_event(EVENT_DOWNLOAD, obj);
323 
324  return __content::get_content_callback(vars);
325 }
326 
327 /**
328  * Get the content of this document as a string.
329  *
330  * @param int|void len - optional parameter length of content to return.
331  * @return the content or the first len bytes of it.
332  * @see get_content_callback
333  */
334 string get_content(int|void len)
335 {
336  string content;
337  object obj = CALLER;
338 
339  check_lock("read");
340 
341  try_event(EVENT_DOWNLOAD, obj);
342  content = ::get_content(len);
343 
344  do_set_attribute(DOC_LAST_ACCESSED, time());
345 
346  run_event(EVENT_DOWNLOAD, obj);
347  return content;
348 }
349 
350 object get_content_file(string mode, mapping vars, string|void client)
351 {
352 }
353 
354 
355 void check_lock(string type, void|string lock)
356 {
357  if ( type == "write" ) {
358  // if lock is available and we get lock for token everything is fine
359  if ( stringp(lock) )
360  return;
361 
362  if ( mappingp(locked) )
363  steam_error("Unable to write locked object, unlock first ! (#%d)",
364  get_object_id());
365  }
366 }
367 
368 
369 /**
370  * Lock the content of this object.
371  *
372  * @param object group - the locking group.
373  * @param string type - the type of the lock, "read" or "write"
374  * @return the content or the first len bytes of it.
375  * @see get_content_callback
376  */
377 mapping lock_content(object group, string type, int timeout)
378 {
379  mapping data;
380  try_event(EVENT_LOCK, CALLER, group, type, timeout);
381  if ( type == "write" ) {
382  steam_error("Cannot lock locked resources !");
383  object locks = do_query_attribute(OBJ_LOCK) || ([ ]);
384  locks[data->token] = data;
385  do_set_attribute(OBJ_LOCK, locks);
386 
387  }
388  run_event(EVENT_LOCK, CALLER, group, type, timeout);
389  return data;
390 }
391 
392 void unlock_content(void|string token)
393 {
394  try_event(EVENT_UNLOCK, CALLER);
395  mapping locks = do_query_attribute(OBJ_LOCK) || ([ ]);
396  if ( token ) {
397  m_delete(locks, token);
398  }
399  else
400  locks = ([ ]);
401  do_set_attribute(OBJ_LOCK, locks);
402  run_event(EVENT_UNLOCK, CALLER);
403 }
404 
405 
406 /**
407  * Callback function called when upload is finished.
408  *
409  */
410 protected:
411  void content_finished()
412 {
413  __content::content_finished();
414  run_event(EVENT_UPLOAD, this_user(), get_content_size());
415 }
416 
417 public:
418 
419 /**
420  * content function used for upload, this function really resides in
421  * base/content and this overridden function just runs the appropriate event
422  *
423  * @return the function for uploading (called each time a chunk is received)
424  * @see get_content_callback
425  */
426 function receive_content(int content_size, void|string lock)
427 {
428  prepare_upload(content_size, lock);
429  return __content::receive_content(content_size);
430 }
431 
432 protected:
433  void prepare_upload(int content_size, void|string lock)
434 {
435  object obj = CALLER;
436  if ( objectp(obj) &&
437  (obj->get_object_class() & CLASS_USER) &&
438  (functionp(obj->get_user_object) ) &&
439  objectp(obj->get_user_object()) )
440  obj = obj->get_user_object();
441 
442  check_lock("write", lock);
443 
444  try_event(EVENT_UPLOAD, obj, content_size);
445 
446  int version = do_query_attribute(DOC_VERSION);
447  if ( !version )
448  version = 1;
449  else {
450  seteuid(get_creator());
451 
452  mixed err = catch {
453  object oldversion = duplicate( ([ "content_id": get_content_id(),
454  oldversion->set_attribute(DOC_VERSION, version);
455  oldversion->set_attribute(DOC_LAST_MODIFIED, do_query_attribute(DOC_LAST_MODIFIED));
456  oldversion->set_attribute(DOC_USER_MODIFIED, do_query_attribute(DOC_USER_MODIFIED));
457  oldversion->set_attribute(OBJ_CREATION_TIME, do_query_attribute(OBJ_CREATION_TIME));
458  mapping versions = do_query_attribute(DOC_VERSIONS);
459  oldversion->set_attribute(DOC_VERSIONS, copy_value(versions));
460  if ( !mappingp(versions) )
461  versions = ([ ]);
462  versions[version] = oldversion;
463 
464  version++;
465  do_set_attribute(DOC_VERSIONS, versions);
466  };
467  if ( err ) {
468  FATAL("Failed to create old version of document: %O\n%O",
469  err[0], err[1]);
470  }
471  seteuid(0);
472  }
473  do_set_attribute(DOC_VERSION, version);
474 
475  set_attribute(DOC_LAST_MODIFIED, time());
476  set_attribute(DOC_USER_MODIFIED, this_user());
477 }
478 
479 public:
480 
481 
482 /**
483  * See whether the content is locked by someone or not.
484  *
485  * @return the locking object.
486  */
487 mapping is_locked()
488 {
489 }
490 
491 object get_previous_version()
492 {
493  mapping versions = do_query_attribute(DOC_VERSIONS);
494  int version = do_query_attribute(DOC_VERSION);
495  if ( objectp(versions[version-1]) )
496  return versions[version-1];
497  while ( version-- > 0 )
498  if ( objectp(versions[version]) )
499  return versions[version];
500  return 0;
501 }
502 
503 string get_etag()
504 {
505  int lm = do_query_attribute(DOC_LAST_MODIFIED);
506  string etag = sprintf("%018x",iObjectID + (lm<<64));
507  if ( sizeof(etag) > 18 ) etag = etag[(sizeof(etag)-18)..];
508  return etag[0..4]+"-"+etag[5..10]+"-"+etag[11..17];
509 }
510 
511 
512 string describe()
513 {
514  return get_identifier()+"(#"+get_object_id()+","+
515  master()->describe_program(object_program(this_object()))+","+
516  get_object_class()+","+do_query_attribute(DOC_MIME_TYPE)+")";
517 }
518 
519 
520 };