OpenSync  0.22
osengine/osengine_mapcmds.c
00001 /*
00002  * libosengine - A synchronization engine for the opensync framework
00003  * Copyright (C) 2004-2005  Armin Bauer <armin.bauer@opensync.org>
00004  * 
00005  * This library is free software; you can redistribute it and/or
00006  * modify it under the terms of the GNU Lesser General Public
00007  * License as published by the Free Software Foundation; either
00008  * version 2.1 of the License, or (at your option) any later version.
00009  * 
00010  * This library is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013  * Lesser General Public License for more details.
00014  * 
00015  * You should have received a copy of the GNU Lesser General Public
00016  * License along with this library; if not, write to the Free Software
00017  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
00018  * 
00019  */
00020  
00021 #include "engine.h"
00022 #include "engine_internals.h"
00023 
00028 
00029 static OSyncMappingEntry *_osync_find_next_diff(OSyncMapping *mapping, OSyncMappingEntry *orig_entry)
00030 {
00031         GList *e;
00032         for (e = mapping->entries; e; e = e->next) {
00033                 OSyncMappingEntry *entry = e->data;
00034                 if (osync_change_get_changetype(entry->change) == CHANGE_UNKNOWN)
00035                         continue;
00036                 if ((entry->change != orig_entry->change) && osync_change_compare(orig_entry->change, entry->change) != CONV_DATA_SAME)
00037                         return entry;
00038         }
00039         osync_debug("MAP", 3, "Could not find next diff");
00040         return NULL;
00041 }
00042 
00043 static OSyncMappingEntry *_osync_find_next_same(OSyncMapping *mapping, OSyncMappingEntry *orig_entry)
00044 {
00045         GList *e;
00046         for (e = mapping->entries; e; e = e->next) {
00047                 OSyncMappingEntry *entry = e->data;
00048                 if (osync_change_get_changetype(entry->change) == CHANGE_UNKNOWN)
00049                         continue;
00050                 if ((entry->change != orig_entry->change) && osync_change_compare(orig_entry->change, entry->change) == CONV_DATA_SAME)
00051                         return entry;
00052         }
00053         osync_debug("MAP", 3, "Could not find next diff");
00054         return NULL;
00055 }
00056 
00057 static OSyncMappingEntry *_osync_change_clone(OSyncEngine *engine, OSyncMapping *new_mapping, OSyncMappingEntry *comp_entry)
00058 {
00059         OSyncMappingEntry *newentry = osengine_mappingentry_new(NULL);
00060         newentry->change = osync_change_new();
00061         newentry->client = comp_entry->client;
00062         osengine_mapping_add_entry(new_mapping, newentry);
00063         osengine_mappingview_add_entry(comp_entry->view, newentry);
00064         osengine_mappingentry_update(newentry, comp_entry->change);
00065         osync_change_set_uid(newentry->change, osync_change_get_uid(comp_entry->change));
00066         osync_flag_set(newentry->fl_has_data);
00067         osync_flag_set(newentry->fl_mapped);
00068         osync_flag_set(newentry->fl_has_info);
00069         osync_flag_set(newentry->fl_dirty);
00070         osync_flag_unset(newentry->fl_synced);
00071         osync_change_save(newentry->change, TRUE, NULL);
00072         return newentry;
00073 }
00074 
00075 osync_bool osync_change_elevate(OSyncEngine *engine, OSyncChange *change, int level)
00076 {
00077         osync_debug("MAP", 3, "elevating change %s (%p) to level %i", osync_change_get_uid(change), change, level);
00078         int i = 0;
00079         for (i = 0; i < level; i++) {
00080                 if (!osync_change_duplicate(change))
00081                         return FALSE;
00082         }
00083         osync_debug("MAP", 3, "change after being elevated %s (%p)", osync_change_get_uid(change), change);
00084         osync_change_save(change, TRUE, NULL);
00085         return TRUE;
00086 }
00087 
00088 osync_bool osync_change_check_level(OSyncEngine *engine, OSyncMappingEntry *entry)
00089 {
00090         GList *c;
00091         osync_debug("MAP", 3, "checking level for change %s (%p)", osync_change_get_uid(entry->change), entry);
00092         for (c = engine->clients; c; c = c->next) {
00093                 OSyncClient *client = c->data;
00094                 OSyncMappingView *view = osengine_mappingtable_find_view(engine->maptable, client->member);
00095                 if (!osengine_mappingview_uid_is_unique(view, entry, TRUE))
00096                         return FALSE;
00097         }
00098         return TRUE;
00099 }
00100 
00101 static int prod(int n)
00102 {
00103         int ret;
00104         for (ret = 0; n > 0; n--)
00105                 ret += n;
00106         return ret;
00107 }
00108 
00109 void osengine_mapping_multiply_master(OSyncEngine *engine, OSyncMapping *mapping)
00110 {
00111         osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, engine, mapping);
00112         g_assert(engine);
00113         
00114         OSyncMappingTable *table = engine->maptable;
00115         OSyncMappingEntry *entry = NULL;
00116         OSyncMappingEntry *master = NULL;
00117 
00118         master = mapping->master;
00119         g_assert(master);
00120         if (osync_flag_is_not_set(master->fl_dirty))
00121                 osync_flag_set(master->fl_synced);
00122         else
00123                 osync_flag_attach(master->fl_committed, table->engine->cmb_committed_all);
00124                 
00125         //Send the change to every source that is different to the master source and set state to writing in the changes
00126         GList *v;
00127         for (v = table->views; v; v = v->next) {
00128                 OSyncMappingView *view = v->data;
00129                 //Check if this client is already listed in the mapping
00130                 entry = osengine_mapping_find_entry(mapping, NULL, view);
00131                 if (entry == master)
00132                         continue;
00133                 if (entry && (osync_change_compare(entry->change, master->change) == CONV_DATA_SAME)) {
00134                         if (osync_flag_is_not_set(entry->fl_dirty))
00135                                 osync_flag_set(entry->fl_synced);
00136                         continue;
00137                 }
00138                 if (!entry) {
00139                         entry = osengine_mappingentry_new(NULL);
00140                         entry->change = osync_change_new();
00141                         entry->client = view->client;
00142                         osengine_mappingview_add_entry(view, entry);
00143                         osengine_mappingentry_update(entry, master->change);
00144                         osync_change_set_uid(entry->change, osync_change_get_uid(master->change));
00145                         osync_change_set_member(entry->change, view->client->member);
00146                         osengine_mapping_add_entry(mapping, entry);
00147                 } else {
00148                         osync_bool had_data = osync_change_has_data(entry->change);
00149                         osengine_mappingentry_update(entry, master->change);
00150                         if (osync_change_get_changetype(entry->change) == CHANGE_ADDED || osync_change_get_changetype(entry->change) == CHANGE_UNKNOWN) {
00151                                 osync_change_set_changetype(entry->change, CHANGE_MODIFIED);
00152                         }
00153                         
00154                         if (osync_member_get_slow_sync(view->client->member, osync_objtype_get_name(osync_change_get_objtype(entry->change))) && !had_data) {
00155                                 osync_change_set_changetype(entry->change, CHANGE_ADDED);
00156                         }
00157                 }
00158                 if (osync_flag_is_set(view->client->fl_sent_changes)) { 
00159                         //osync_change_flags_attach(change, mapping);
00160                         osync_flag_set(entry->fl_dirty);
00161                         osync_flag_set(entry->fl_has_data);
00162                         osync_flag_set(entry->fl_mapped);
00163                         osync_flag_set(entry->fl_has_info);
00164                         osync_flag_unset(entry->fl_synced);
00165                         OSyncError *error = NULL;
00166                         osync_change_save(entry->change, TRUE, &error);
00167                         osync_flag_attach(entry->fl_committed, table->engine->cmb_committed_all);
00168                 }
00169         }
00170         
00171         OSyncError *error = NULL;
00172         osync_change_save(master->change, TRUE, &error);
00173         
00174         osync_flag_set(mapping->fl_multiplied);
00175         osync_trace(TRACE_EXIT, "%s", __func__);
00176 }
00177 
00178 
00179 
00180 void osengine_mapping_check_conflict(OSyncEngine *engine, OSyncMapping *mapping)
00181 {
00182         osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, engine, mapping);
00183         GList *e;
00184         GList *n;
00185         osync_bool is_conflict = FALSE;
00186         int is_same = 0;
00187         OSyncMappingEntry *leftentry = NULL;
00188         OSyncMappingEntry *rightentry = NULL;
00189         
00190         g_assert(engine != NULL);
00191         g_assert(mapping != NULL);
00192         g_assert(!mapping->master);
00193         
00194         for (e = mapping->entries; e; e = e->next) {
00195                 leftentry = e->data;
00196                 if (osync_change_get_changetype(leftentry->change) == CHANGE_UNKNOWN)
00197                         continue;
00198                 mapping->master = leftentry;
00199                 for (n = e->next; n; n = n->next) {
00200                         rightentry = n->data;
00201                         if (osync_change_get_changetype(rightentry->change) == CHANGE_UNKNOWN)
00202                                 continue;
00203                         
00204                         if (osync_change_compare(leftentry->change, rightentry->change) != CONV_DATA_SAME) {
00205                                 is_conflict = TRUE;
00206                                 goto conflict;
00207                         } else {
00208                                 is_same++;
00209                         }
00210                 }       
00211         }
00212         
00213         conflict:
00214         if (is_conflict) {
00215                 //conflict, solve conflict
00216                 osync_debug("MAP", 2, "Got conflict for mapping %p", mapping);
00217                 osync_status_conflict(engine, mapping);
00218                 osync_flag_set(mapping->fl_chkconflict);
00219                 osync_trace(TRACE_EXIT, "%s: Got conflict", __func__);
00220                 return;
00221         }
00222         g_assert(mapping->master);
00223         osync_flag_set(mapping->fl_chkconflict);
00224         
00225         //Our mapping is already solved since there is no conflict
00226         osync_flag_set(mapping->fl_solved);
00227         
00228         if (is_same == prod(g_list_length(engine->maptable->views) - 1)) {
00229                 osync_trace(TRACE_INTERNAL, "No need to sync. All entries are the same");
00230                 osync_flag_set(mapping->cmb_synced);
00231                 osync_flag_set(mapping->fl_multiplied);
00232         }
00233 
00234         send_mapping_changed(engine, mapping);
00235         osync_trace(TRACE_EXIT, "%s: No conflict", __func__);
00236 }
00237 
00238 static OSyncMapping *_osengine_mapping_find(OSyncMappingTable *table, OSyncMappingEntry *orig_entry)
00239 {
00240         GList *i;
00241         GList *n;
00242         osync_bool mapping_found = FALSE;
00243 
00244         for (i = table->mappings; i; i = i->next) {
00245                 OSyncMapping *mapping = i->data;
00246                 //We only need mapping where our member isnt listed yet.
00247                 if (!osengine_mapping_find_entry(mapping, NULL, orig_entry->view)) {
00248                         mapping_found = TRUE;
00249                         for (n = mapping->entries; n; n = n->next) {
00250                                 OSyncMappingEntry *entry = n->data;
00251                                 if (osync_change_compare_data(entry->change, orig_entry->change) == CONV_DATA_MISMATCH) {
00252                                         mapping_found = FALSE;
00253                                         continue;
00254                                 }
00255                         }
00256                         if (mapping_found)
00257                                 return mapping;
00258                 }
00259         }
00260         return NULL;
00261 }
00262 
00263 void osengine_change_map(OSyncEngine *engine, OSyncMappingEntry *entry)
00264 {
00265         osync_trace(TRACE_ENTRY, "osengine_change_map(%p, %p)", engine, entry);
00266         OSyncMapping *mapping = NULL;
00267         if (!(mapping = _osengine_mapping_find(engine->maptable, entry))) {
00268                 mapping = osengine_mapping_new(engine->maptable);
00269                 osync_flag_unset(mapping->fl_chkconflict);
00270                 osync_flag_unset(mapping->fl_multiplied);
00271                 mapping->id = osengine_mappingtable_get_next_id(engine->maptable);
00272                 osync_trace(TRACE_INTERNAL, "No previous mapping found. Creating new one: %p", mapping);
00273         }
00274         osengine_mapping_add_entry(mapping, entry);
00275         osync_flag_set(entry->fl_mapped);
00276         osync_change_save(entry->change, FALSE, NULL);
00277         osync_trace(TRACE_EXIT, "osengine_change_map");
00278 }
00279 
00289 
00296 void osengine_mapping_duplicate(OSyncEngine *engine, OSyncMapping *dupe_mapping)
00297 {
00298         osync_trace(TRACE_ENTRY, "osengine_mapping_duplicate(%p, %p)", engine, dupe_mapping);
00299         g_assert(dupe_mapping);
00300         int elevation = 0;
00301         OSyncMappingEntry *orig_entry = NULL;
00302         OSyncMappingEntry *first_diff_entry = NULL;
00303         OSyncMappingEntry *next_entry = NULL;
00304         OSyncMapping *new_mapping = NULL;
00305         
00306         //Remove all deleted items first.
00307         GList *entries, *e;
00308         entries = g_list_copy(dupe_mapping->entries);
00309         for (e = entries; e; e = e->next) {
00310                 OSyncMappingEntry *entry = e->data;
00311                 if (osync_change_get_changetype(entry->change) == CHANGE_DELETED) {
00312                         osync_change_delete(entry->change, NULL);
00313                         osengine_mappingentry_free(entry);
00314                 }
00315         }
00316         g_list_free(entries);
00317         
00318         //Choose the first modified change as the master of the mapping to duplicate
00319         GList *i = dupe_mapping->entries;
00320         do {
00321                 orig_entry = i->data;
00322                 i = i->next;
00323         } while (osync_change_get_changetype(orig_entry->change) != CHANGE_MODIFIED && osync_change_get_changetype(orig_entry->change) != CHANGE_ADDED);
00324         dupe_mapping->master = orig_entry;
00325         osync_change_set_changetype(orig_entry->change, CHANGE_MODIFIED);
00326         
00327         /* Now we go through the list of changes in the mapping to
00328          * duplicate and search for the next entry that is different
00329          * to our choosen master. This entry then has to be moved to a
00330          * new mapping along with all changes to are the same as this next
00331          * different change
00332          */
00333         while ((first_diff_entry = _osync_find_next_diff(dupe_mapping, orig_entry))) {
00334                 //We found a different change
00335                 elevation = 0;
00336                 new_mapping = osengine_mapping_new(engine->maptable);
00337                 new_mapping->id = osengine_mappingtable_get_next_id(engine->maptable);
00338                 osync_flag_unset(new_mapping->cmb_synced);
00339                 osync_flag_set(new_mapping->fl_chkconflict);
00340                 osync_flag_unset(new_mapping->fl_multiplied);
00341                 osync_flag_set(new_mapping->fl_solved);
00342                 send_mapping_changed(engine, new_mapping);
00343                 osync_debug("MAP", 3, "Created new mapping for duplication %p with mappingid %lli", new_mapping, new_mapping->id);
00344                 
00345                 /* Now we copy the change that differs, and set it as the master of the new
00346                  * mapping.*/
00347                 OSyncMappingEntry *newentry = osengine_mappingentry_copy(first_diff_entry);     
00348                 new_mapping->master = newentry;
00349                 osengine_mapping_add_entry(new_mapping, newentry);
00350                 osync_change_set_changetype(newentry->change, CHANGE_ADDED);
00351                 osync_flag_set(newentry->fl_has_data);
00352                 osync_flag_set(newentry->fl_mapped);
00353                 osync_flag_set(newentry->fl_has_info);
00354                 osync_flag_set(newentry->fl_dirty);
00355                 osync_flag_unset(newentry->fl_synced);
00356                 
00357                 /* Now we elevate the change (which might be done by adding a -dupe
00358                  * or a (2) to the change uid. We then check if there is already
00359                  * another change on this level and if there is, we elevate again */
00360                 do {
00361                         if (!osync_change_elevate(engine, newentry->change, 1))
00362                                 break;
00363                         elevation += 1;
00364                 } while (!osync_change_check_level(engine, newentry));
00365                 OSyncError *error = NULL;
00366                 osync_change_save(newentry->change, TRUE, &error);
00367                 
00368                 /* Now we search for all changes to belong to the new mapping, so
00369                  * we are searching for changes to do not differ from the change we found
00370                  * to be different from the master of the mapping to duplicate */
00371                 while ((next_entry = _osync_find_next_same(dupe_mapping, first_diff_entry))) {
00372                         newentry = _osync_change_clone(engine, new_mapping, first_diff_entry);
00373                         osync_change_elevate(engine, newentry->change, elevation);
00374                         osengine_mappingentry_update(orig_entry, next_entry->change);
00375                         osync_change_save(next_entry->change, TRUE, NULL);
00376                 }
00377                 
00378                 /* Now we can reset the different change and prepare it for
00379                  * being overwriten during mulitply_master */
00380                 osync_change_set_changetype(first_diff_entry->change, CHANGE_UNKNOWN);
00381 
00382                 //We can now add the new mapping into the queue so it get processed
00383                 send_mapping_changed(engine, new_mapping);
00384         }
00385 
00386         //Multiply our original mapping
00387         osync_flag_set(dupe_mapping->fl_solved);
00388         send_mapping_changed(engine, dupe_mapping);
00389         osync_trace(TRACE_EXIT, "osengine_mapping_duplicate");
00390 }
00391 
00401 void osengine_mapping_solve(OSyncEngine *engine, OSyncMapping *mapping, OSyncChange *change)
00402 {
00403         osync_trace(TRACE_ENTRY, "osengine_mapping_solve(%p, %p, %p)", engine, mapping, change);
00404         OSyncMappingEntry *entry = osengine_mapping_find_entry(mapping, change, NULL);
00405         mapping->master = entry;
00406         osync_flag_set(mapping->fl_solved);
00407         send_mapping_changed(engine, mapping);
00408         osync_trace(TRACE_EXIT, "osengine_mapping_solve");
00409 }
00410 
00420 osync_bool osengine_mapping_ignore_conflict(OSyncEngine *engine, OSyncMapping *mapping, OSyncError **error)
00421 {
00422         osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, engine, mapping, error);
00423         
00424         if (!osengine_mapping_ignore_supported(engine, mapping)) {
00425                 osync_error_set(error, OSYNC_ERROR_GENERIC, "Ignore is not supported for this mapping");
00426                 osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
00427                 return FALSE;
00428         }
00429         
00430         GList *e = NULL;
00431         for (e = mapping->entries; e; e = e->next) {
00432                 OSyncMappingEntry *entry = e->data;
00433                 osync_trace(TRACE_INTERNAL, "Adding %p to logchanges", entry);
00434                 OSyncError *error = NULL;
00435                 if (osync_change_get_changetype(entry->change) != CHANGE_UNKNOWN)
00436                         osync_group_save_changelog(engine->group, entry->change, &error);
00437         }
00438         
00439         //And make sure we dont synchronize it this time
00440         //osengine_mapping_reset(mapping);
00441         osync_flag_set(mapping->fl_multiplied);
00442         osync_flag_set(mapping->cmb_synced);
00443         osync_flag_set(mapping->cmb_has_info);
00444         osync_trace(TRACE_EXIT, "%s", __func__);
00445         return TRUE;
00446 }
00447 
00461 osync_bool osengine_mapping_ignore_supported(OSyncEngine *engine, OSyncMapping *mapping)
00462 {
00463         osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, engine, mapping);
00464         
00465         int i, count = 0;
00466         OSyncChange *change = NULL;
00467         OSyncMember *member = NULL;
00468         OSyncObjType *objtype = NULL;
00469 
00470         count = osengine_mapping_num_changes(mapping);
00471         for (i = 0; i < count; ++i) {
00472                 change = osengine_mapping_nth_change(mapping, i);
00473                 objtype = osync_change_get_objtype(change);
00474 
00475                 member = osync_change_get_member(change);
00476 
00477                 if (!osync_member_has_read_function(member, objtype)) {
00478                         osync_trace(TRACE_EXIT, "%s: Ignore NOT supported", __func__);
00479                         return FALSE;
00480                 }
00481         }
00482         
00483         osync_trace(TRACE_EXIT, "%s: Ignore supported", __func__);
00484         return TRUE;
00485 }
00486 
00499 osync_bool osengine_mapping_solve_latest(OSyncEngine *engine, OSyncMapping *mapping, OSyncError **error)
00500 {
00501         osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, engine, mapping, error);
00502         
00503         time_t time = 0;
00504         time_t latesttime = 0;
00505         osync_bool preveq = FALSE;
00506         
00507         GList *e = NULL;
00508         for (e = mapping->entries; e; e = e->next) {
00509                 OSyncMappingEntry *entry = e->data;
00510                 
00511                 if (osync_change_get_changetype(entry->change) != CHANGE_UNKNOWN) {
00512                         time = osync_change_get_revision(entry->change, error);
00513                         if (time == -1) {
00514                                 osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
00515                                 mapping->master = NULL;
00516                                 return FALSE;
00517                         }
00518                         
00519                         if (time > latesttime) {
00520                                 latesttime = time;
00521                                 mapping->master = entry;
00522                                 preveq = FALSE;
00523                         } else if (time == latesttime)
00524                                 preveq = TRUE;
00525                 }
00526         }
00527         
00528         if (preveq == TRUE) {
00529                 osync_error_set(error, OSYNC_ERROR_GENERIC, "Could not decide for one entry. Timestamps where equal");
00530                 mapping->master = NULL;
00531                 osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
00532                 return FALSE;
00533         }
00534         
00535         osync_flag_set(mapping->fl_solved);
00536         send_mapping_changed(engine, mapping);
00537         
00538         osync_trace(TRACE_EXIT, "%s: %p", __func__, mapping->master);
00539         return TRUE;
00540 }
00541 
00554 osync_bool osengine_mapping_check_timestamps(OSyncEngine *engine, OSyncMapping *mapping, OSyncError **error)
00555 {
00556         osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, engine, mapping, error);
00557         
00558         time_t time = 0;
00559         time_t latesttime = 0;
00560         osync_bool preveq = FALSE;
00561         
00562         GList *e = NULL;
00563         for (e = mapping->entries; e; e = e->next) {
00564                 OSyncMappingEntry *entry = e->data;
00565                 
00566                 if (osync_change_get_changetype(entry->change) != CHANGE_UNKNOWN) {
00567                         time = osync_change_get_revision(entry->change, error);
00568                         if (time == -1) {
00569                                 osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
00570                                 return FALSE;
00571                         }
00572                         
00573                         if (time > latesttime) {
00574                                 latesttime = time;
00575                                 preveq = FALSE;
00576                         } else if (time == latesttime)
00577                                 preveq = TRUE;
00578                 }
00579         }
00580         
00581         if (preveq == TRUE) {
00582                 osync_error_set(error, OSYNC_ERROR_GENERIC, "Could not decide for one entry. Timestamps where equal");
00583                 osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
00584                 return FALSE;
00585         }
00586         
00587         osync_trace(TRACE_EXIT, "%s", __func__);
00588         return TRUE;
00589 }
00590 
00601 void osengine_mapping_solve_updated(OSyncEngine *engine, OSyncMapping *mapping, OSyncChange *change)
00602 {
00603         osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, engine, mapping, change);
00604         OSyncMappingEntry *entry = osengine_mapping_find_entry(mapping, change, NULL);
00605         mapping->master = entry;
00606         
00607         osync_flag_set(entry->fl_dirty);
00608         osync_flag_unset(entry->fl_synced);
00609         send_mappingentry_changed(engine, entry);
00610         
00611         osync_flag_set(mapping->fl_solved);
00612         send_mapping_changed(engine, mapping);
00613         osync_trace(TRACE_EXIT, "%s", __func__);
00614 }
00615