Container._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: Container.pike,v 1.5 2009/05/06 19:23:10 astra Exp $
18  */
19 inherit "/classes/Object";
20 #include <attributes.h>
21 #include <macros.h>
22 #include <assert.h>
23 #include <events.h>
24 #include <classes.h>
25 #include <database.h>
26 #include <types.h>
27 //! A Container is an object that holds other objects (no users).
28 class Container : public Object{
29 public:
30 
31 
32 
33 
34 
35 private array oaInventory; // the containers inventory
36 
37 /**
38  * init this object.
39  *
40  * @see create
41  */
42 protected:
43  void
44 init()
45 {
46  ::init();
47  oaInventory = ({ });
48  add_data_storage(STORE_CONTAINER, store_container, restore_container);
49 }
50 
51 public:
52 
53 /**
54  * This function is called by delete to delete this object.
55  *
56  */
57 protected:
58  void
59 delete_object()
60 {
61  ::delete_object();
62  array inventory = copy_value(oaInventory);
63 
64  foreach( inventory, object inv ) {
65  // dont delete Users !
66  if ( objectp(inv) )
67  {
68  mixed err;
69  if ( inv->get_object_class() & CLASS_USER) {
70  err = catch {
71  inv->move(inv->query_attribute(USER_WORKROOM));
72  };
73  }
74  else {
75  err = catch {
76  inv->delete();
77  };
78  }
79  }
80  }
81 }
82 
83 public:
84 
85 /**
86  * Duplicate an object - that is create a copy, the permisions are
87  * not copied though.
88  *
89  * @param recursive - should the container be copied recursively?
90  * @return the copy of this object
91  * @see create
92  */
93 object duplicate(void|bool recursive)
94 {
95  return ::duplicate(([ "recursive": recursive, ]));
96 }
97 
98 mapping copy(object obj, mapping vars)
99 {
100  mapping copies = ::copy(obj, vars);
101  if ( mappingp(vars) && vars->recursive ) {
102  foreach( obj->get_inventory(), object inv ) {
103  if ( !objectp(inv) )
104  continue;
105  if ( inv->get_object_class() & CLASS_USER)
106  continue;
107  object new_inv;
108  mapping copied;
109  mixed err = catch {
110  copied = inv->do_duplicate(vars);
111  copies |= copied;
112  new_inv = copied[inv];
113  };
114  if ( err != 0 ) {
115  FATAL("Duplication of #" + inv->get_object_id());
116  FATAL("Error while duplicating recursively !\n"+
117  sprintf("%O", err[0])+"\n"+sprintf("%O",err[1]));
118  }
119  }
120  }
121  return copies;
122 }
123 
124 void update_path()
125 {
126  ::update_path();
127  foreach(oaInventory, object inv) {
128  if ( objectp(inv) ) {
129  mixed err = catch(inv->update_path());
130  if ( err ) {
131  FATAL("Error while updating path: %O\n%O", err[0], err[1]);
132  }
133  }
134  }
135 }
136 
137 /**
138  * Check if it is possible to insert the object here.
139  *
140  * @param object obj - the object to insert
141  * @return true or false
142  * @see insert_obj
143  */
144 protected:
145  bool check_insert(object obj)
146 {
147  if ( obj->get_object_class() & CLASS_ROOM )
148  steam_user_error("Unable to insert a Room-Object into Container !");
149 
150  if ( obj->get_object_class() & CLASS_USER )
151  steam_user_error("Unable to insert User into Container !");
152 
153  if ( obj->get_object_class() & CLASS_EXIT )
154  steam_user_error("Unable to insert Exits into Container !");
155 
156  return true;
157 }
158 
159 public:
160 
161 void check_environment(object env, object obj)
162 {
163  if ( !objectp(env) )
164  return;
165 
166  if ( env == obj )
167  steam_error("Recursion detected in environment!");
168  env = env->get_environment();
169  check_environment(env, obj);
170 }
171 
172 /**
173  * Insert an object in the containers inventory. This is called by
174  * the move function - don't call this function myself.
175  *
176  * @param obj - the object to insert into the container
177  * @return true if object was inserted
178  * @see remove_obj
179  */
180 bool
181 insert_obj(object obj)
182 {
183  ASSERTINFO(IS_PROXY(obj), "Object is not a proxy");
184 
185  if ( !objectp(obj) )
186  return false;
187 
188  if ( CALLER != obj->get_object() ) // only insert proxy objects
189  steam_error("Container.insert_obj() failed to insert non-proxy !");
190  if ( !arrayp(oaInventory) )
191  oaInventory = ({ });
192 
193  check_insert(obj); // does throw
194 
195  steam_error("Cannot insert object into itself !");
196  check_environment(get_environment(), obj);
197 
198  if ( search(oaInventory, obj) != -1 ) {
199  FATAL("Inserting object %O twice into %O! ----\n%s---\n", obj,
200  return true;
201  }
202 
203  try_event(EVENT_ENTER_INVENTORY, obj);
204 
205  // do not set, if user is added to inventory
206  if ( !(obj->get_object_class() & CLASS_USER) ) {
207  do_set_attribute(CONT_USER_MODIFIED, this_user());
208  do_set_attribute(CONT_LAST_MODIFIED, time());
209  }
210  oaInventory += ({ obj });
211 
212  require_save(STORE_CONTAINER, UNDEFINED, SAVE_INSERT, ({ obj }));
213 
214  run_event(EVENT_ENTER_INVENTORY, obj);
215  return true;
216 }
217 
218 
219 
220 /**
221  * Remove an object from the container. This function can only be
222  * called by the object itself and should only be called by the move function.
223  *
224  * @param obj - the object to insert into the container
225  * @return true if object was removed
226  * @see insert_obj
227  */
228 bool remove_obj(object obj)
229 {
230  if ( !objectp(obj) ||
231  return false;
232 
233  ASSERTINFO(arrayp(oaInventory), "Inventory not initialized!");
234  try_event(EVENT_LEAVE_INVENTORY, obj);
235 
236  do_set_attribute(CONT_LAST_MODIFIED, time());
237  oaInventory -= ({ obj });
238 
239  require_save(STORE_CONTAINER, UNDEFINED, SAVE_REMOVE, ({ obj }));
240  run_event(EVENT_LEAVE_INVENTORY, obj);
241  return true;
242 }
243 
244 /**
245  * Get the inventory of this container.
246  *
247  * @param void|int from_obj - the starting object
248  * @param void|int to_obj - the end of an object range.
249  * @return a list of objects contained by this container
250  * @see move
251  * @see get_inventory_by_class
252  */
253 array get_inventory(int|void from_obj, int|void to_obj)
254 {
255  oaInventory -= ({0});
256  try_event(EVENT_GET_INVENTORY, CALLER);
257  run_event(EVENT_GET_INVENTORY, CALLER);
258 
259  if ( to_obj > 0 )
260  return oaInventory[from_obj..to_obj];
261  else if ( from_obj > 0 )
262  return oaInventory[from_obj..];
263  return copy_value(oaInventory);
264 }
265 
266 /**
267  * Get the content of this container - only relevant for multi
268  * language containers.
269  *
270  * @return content of index file
271  */
272 string get_content(void|string language)
273 {
274  if ( do_query_attribute("cont_type") == "multi_language" ) {
275  mapping index = do_query_attribute("language_index");
276  if ( objectp(index[language]) )
277  return index[language]->get_content();
278  if ( objectp(index->default) )
279  return index["default"]->get_content();
280  }
281  return 0;
282 }
283 
284 
285 /**
286  * Returns the inventory of this container, optionally filtered by object
287  * class, attribute values or pagination.
288  * The description of the filters and sort options can be found in the
289  * filter_objects_array() function of the "searching" module.
290  *
291  * Example:
292  * Return all documents with keywords "urgent" or "important" that the user
293  * has read access to, that are no wikis and that have been changed in the
294  * last 24 hours, sort them by modification date (newest first) and return
295  * only the first 10 results:
296  * get_inventory_filtered(
297  * ({ // filters:
298  * ({ "-", "!access", SANCTION_READ }),
299  * ({ "-", "attribute", "OBJ_TYPE", "prefix", "container_wiki" }),
300  * ({ "-", "attribute", "DOC_LAST_MODIFIED", "<", time()-86400 }),
301  * ({ "-", "attribute", "OBJ_KEYWORDS", "!=", ({ "urgent", "important" }) }),
302  * ({ "+", "class", CLASS_DOCUMENT })
303  * }),
304  * ({ // sort:
305  * ({ ">", "attribute", "DOC_LAST_MODIFIED" })
306  * }), );
307  *
308  * @param filters (optional) an array of filters (each an array as described
309  * in the "searching" module) that specify which objects to return
310  * @param sort (optional) an array of sort entries (each an array as described
311  * in the "searching" module) that specify the order of the items
312  * @param offset (optional) only return the objects starting at (and including)
313  * this index
314  * @param length (optional) only return a maximum of this many objects
315  * @return a mapping ([ "objects":({...}), "total":nr, "length":nr,
316  * "start":nr, "page":nr ]), where the "objects" value is an array of
317  * objects that match the specified filters, sort order and pagination.
318  * The other indices contain pagination information ("total" is the total
319  * number of objects after filtering but before applying "length", "length"
320  * is the requested number of items to return (as in the parameter list),
321  * "start" is the start index of the result in the total number of objects,
322  * and "page" is the page number (starting with 1) of pages with "length"
323  * objects each, or 0 if invalid).
324  */
325 mapping get_inventory_paginated ( array|void filters, array|void sort, int|void offset, int|void length )
326 {
327  oaInventory -= ({ 0 });
328  try_event(EVENT_GET_INVENTORY, CALLER);
329  run_event(EVENT_GET_INVENTORY, CALLER);
330  return get_module( "searching" )->paginate_object_array( oaInventory,
331  filters, sort, offset, length );
332 }
333 
334 /**
335  * Returns the inventory of this container, optionally filtered, sorted and
336  * limited by offset and length. This returns the same as the "objects" index
337  * in the result of get_inventory_paginated() and is here for compatibility
338  * reasons and ease of use (if you don't need pagination information).
339  *
340  * @see get_inventory_paginated
341  */
342 array get_inventory_filtered ( array|void filters, array|void sort, int|void offset, int|void length )
343 {
344  return get_inventory_paginated( filters, sort, offset, length )["objects"];
345 }
346 
347 
348 /**
349  * Get only objects of a certain class. The class is the bit id submitted
350  * to the function. It matches only the highest class bit given.
351  * This means get_inventory_by_class(CLASS_CONTAINER) would not return
352  * any CLASS_ROOM. Also it is possible to do
353  * get_inventory_by_class(CLASS_CONTAINER|CLASS_EXIT) which would return
354  * an array of containers and exits, but still no rooms or links -
355  * Room is derived from Container and Exit inherits Link.
356  *
357  * @param int cl - the classid
358  * @param void|int from_obj - starting object
359  * @param void|int to_obj - second parameter for an object range.
360  * @return list of objects matching the given criteria.
361  */
362 array get_inventory_by_class(int cl, int|void from_obj,int|void to_obj)
363 {
364  try_event(EVENT_GET_INVENTORY, CALLER);
365  run_event(EVENT_GET_INVENTORY, CALLER);
366 
367  array arr = ({ });
368  array bits= ({ });
369  for ( int i = 0; i < 32; i++ ) {
370  if ( cl & (1<<i) )
371  bits += ({ 1<<i });
372  }
373  int cnt = 0;
374  foreach(bits, int bit) {
375  foreach(oaInventory, object obj) {
376  if ( !objectp(obj) )
377  continue;
378  int ocl = obj->get_object_class();
379  if ( (ocl & bit) && (ocl < (bit<<1)) ) {
380  cnt++;
381  if ( to_obj != 0 && cnt > to_obj ) break;
382  if ( from_obj < cnt && (to_obj == 0 || cnt < to_obj ) )
383  arr += ({ obj });
384  }
385  }
386  }
387  return arr;
388 }
389 
390 /**
391  * Restore the container data. Most importantly the inventory.
392  *
393  * @param data - the unserialized object data
394 private:
395  * @see store_container
396  */
397 private:
398 void restore_container(mixed data)
399 {
400  if (CALLER != _Database )
401  THROW("Caller is not Database !", E_ACCESS);
402 
403  oaInventory = data["Inventory"];
404  if ( !arrayp(oaInventory) )
405  oaInventory = ({ });
406 
407 }
408 
409 public:
410 
411 /**
412  * Stores the data of the container. Returns the inventory
413  * of this container.
414  *
415  * @return the inventory and possible other important container data.
416 private:
417  * @see restore_container
418  */
419 private:
420 mixed store_container()
421 {
422  if (CALLER != _Database )
423  THROW("Caller is not Database !", E_ACCESS);
424  return ([ "Inventory": oaInventory, ]);
425 }
426 
427 public:
428 
429 /**
430  * Get the content size of this object which does not make really
431  * sense for containers.
432  *
433  * @return the content size: -2 as the container can be seen as an inventory
434  * @see stat
435  */
436 int get_content_size()
437 {
438  return -2;
439 }
440 
441 /**
442  * Get the size of documents in this container only or including sub-containers
443  *
444  * @param void|bool calculateContainers - also get size of container
445  * @return size of documents in container
446  */
447 int get_content_size_inventory(void|bool calculateContainers)
448 {
449  int size = 0;
450  foreach(oaInventory, object inv) {
451  if ( objectp(inv) ) {
452  if ( inv->get_object_class() & CLASS_DOCUMENT )
453  size += inv->get_content_size();
454  else if ( calculateContainers &&
455  inv->get_object_class() & CLASS_CONTAINER )
456  size += inv->get_content_size_inventory();
457  }
458  }
459  return size;
460 }
461 
462 /**
463  * Get the number of objects in this container
464  *
465  * @return number of objects in this container
466  */
467 int get_size()
468 {
469  return sizeof(oaInventory);
470 }
471 
472 /**
473  * This function returns the stat() of this object. This has the
474  * same format as statting a file.
475  *
476  * @return status array as in file_stat()
477  * @see get_content_size
478  */
479 array stat()
480 {
481  int creator_id = objectp(get_creator())?get_creator()->get_object_id():0;
482 
483  return ({ 16895, get_content_size(),
484  do_query_attribute(OBJ_CREATION_TIME),
485  do_query_attribute(CONT_LAST_MODIFIED)||
486  do_query_attribute(OBJ_CREATION_TIME),
487  time(),
488  creator_id, creator_id, "httpd/unix-directory" });
489 }
490 
491 /**
492  * The function returns an array of important events used by this
493  * container. In order to observe the actions inside the container,
494  * the events should be heared.
495  *
496  * @return Array of relevant events
497  */
498 array observe()
499 {
500  return ({ EVENT_SAY, EVENT_LEAVE_INVENTORY, EVENT_ENTER_INVENTORY });
501 }
502 
503 /**
504  * This function sends a message to the container, which actually
505  * means the say event is fired and we can have a conversation between
506  * users inside this container.
507  *
508  * @param msg - the message to say
509  */
510 bool message(string msg)
511 {
512  /* does almost nothing... */
513  try_event(EVENT_SAY, CALLER, msg);
514  run_event(EVENT_SAY, CALLER, msg);
515  return true;
516 }
517 
518 /**
519  * Swap the position of two objects in the inventory. This
520  * function is usefull for reordering the inventory.
521  * You can sort an inventory afterwards or use the order of
522  * objects given in the list (array).
523  *
524  * @param int|object from - the object or position "from"
525  * @param int|object to - the object or position to swap to
526  * @return if successfull or not (error)
527  * @see get_inventory
528  * @see insert_obj
529  * @see remove_obj
530  */
531 bool swap_inventory(int|object from, int|object to)
532 {
533  int sz = sizeof(oaInventory);
534 
535  if ( objectp(from) )
536  from = search(oaInventory, from);
537  if ( objectp(to) )
538  to = search(oaInventory, to);
539 
540  ASSERTINFO(from >= 0 && from < sz && to >= 0 && to < sz,
541  "False position for inventory swapping !");
542  if ( from == to ) return true;
543  object from_obj = oaInventory[from];
544  object to_obj = oaInventory[to];
545  oaInventory[from] = to_obj;
546  oaInventory[to] = from_obj;
547  require_save(STORE_CONTAINER, UNDEFINED, SAVE_ORDER);
548  return true;
549 }
550 
551 /**
552  * Changes the order of the inventory by passing an order array,
553  * the standard pike sort function is used for this and sorts
554  * the array the same way order is sorted (integer values, by numbers).
555  *
556  * @param array order - the sorting order.
557  * @return whether sorting was successfull or not.
558  */
559 bool order_inventory(array order)
560 {
561  int size = sizeof( oaInventory );
562  ASSERTINFO( sizeof(order) == size, "Size of order array does not match !" );
563 
564  array sorter = allocate( size );
565  for ( int i=0; i<size; i++ ) {
566  if ( order[i] != i ) sorter[ (int)order[i] ] = i;
567  else sorter[i] = i;
568  }
569  sort(sorter, oaInventory);
570  require_save(STORE_CONTAINER, UNDEFINED, SAVE_ORDER);
571  return true;
572 }
573 
574 bool order_inventory_objects ( array objects )
575 {
576  if ( !arrayp(objects) )
577  THROW( "Not an array !", E_ERROR );
578  array old_indices = ({ });
579  array tmp_objects = ({ });
580  foreach ( objects, mixed obj ) {
581  if ( !objectp(obj) ) catch( obj = find_object( (int)obj ) );
582  if ( !objectp(obj) )
583  THROW( "Invalid object/id !", E_ERROR );
584  int index = search( oaInventory, obj );
585  if ( index < 0 )
586  THROW( sprintf( "Object %d not in inventory !", obj->get_object_id() ),
587  E_ERROR );
588  old_indices += ({ index });
589  tmp_objects += ({ obj });
590  }
591  array new_indices = sort( old_indices );
592  for ( int i=0; i<sizeof(old_indices); i++ )
593  oaInventory[ new_indices[i] ] = tmp_objects[i];
594  require_save(STORE_CONTAINER, UNDEFINED, SAVE_ORDER);
595  return true;
596 }
597 
598 /**
599  * Get an object by its name from the inventory of this Container.
600  *
601  * @param string obj_name - the object to get
602  * @return 0|object found by the given name
603  * @see get_inventory
604  * @see get_inventory_by_class
605  */
606 object get_object_byname(string obj_name, object|void o)
607 {
608  oaInventory -= ({ 0 });
609 
610  if ( !stringp(obj_name) )
611  return 0;
612 
613  foreach ( oaInventory, object obj ) {
614  if ( objectp(o) && o == obj ) continue;
615 
616  obj = obj->get_object();
617  if ( !objectp(obj) ) continue;
618  if ( obj->get_object_class() & CLASS_USER )
619  continue; // skip user objects
620  if (functionp(obj->get_identifier) && obj_name==obj->get_identifier())
621  }
622  return 0;
623 }
624 
625 bool contains(object obj, bool rec)
626 {
627  if ( search(oaInventory, obj) >= 0 )
628  return true;
629  if ( rec ) {
630  foreach(oaInventory, object o) {
631  if ( objectp(o) && o->get_object_class() & CLASS_CONTAINER )
632  if ( o->contains(obj, true) )
633  return true;
634  }
635  }
636  return false;
637 }
638 
639 /**
640  * Get the users present in this Room. There shouldnt be any User
641  * inside a Container.
642  *
643  * @return array of users.
644  */
645 array get_users()
646 {
647  array users = ({ });
648  foreach(get_inventory(), object inv) {
649  if ( inv->get_object_class() & CLASS_USER )
650  users += ({ inv });
651  }
652  return users;
653 }
654 
655 void lowAppendXML(object rootNode, void|int depth)
656 {
657  ::lowAppendXML(rootNode, depth);
658  if ( depth > 0 ) {
659  foreach(oaInventory, object o) {
660  if ( objectp(o) ) {
661  object objNode = xslt.Node("Object", ([ ]));
662  rootNode->add_child(objNode);
663  o->lowAppendXML(objNode, depth-1);
664  }
665  }
666  }
667 }
668 
669 /**
670  * Get the object class of Container.
671  *
672  * @return the object class of container. Check with CLASS_CONTAINER.
673  */
674 int get_object_class()
675 {
676  return ::get_object_class() | CLASS_CONTAINER;
677 }
678 
679 /**
680  * Is this an object ? yes!
681  *
682  * @return true
683  */
684 final bool is_container() { return true; }
685 
686 
687 string describe()
688 {
689  return get_identifier()+"(#"+get_object_id()+","+
690  master()->describe_program(object_program(this_object()))+","+
691  get_object_class()+","+sizeof(oaInventory)+" objects)";
692 }
693 
694 
695 
696 
697 };