index.c

Go to the documentation of this file.
00001 /*
00002  * File Name: index.c
00003  */
00004 
00005 /*
00006  * This file is part of ctb.
00007  *
00008  * ctb is free software: you can redistribute it and/or modify
00009  * it under the terms of the GNU General Public License as published by
00010  * the Free Software Foundation, either version 2 of the License, or
00011  * (at your option) any later version.
00012  *
00013  * ctb is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00016  * GNU General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU General Public License
00019  * along with this program. If not, see <http://www.gnu.org/licenses/>.
00020  */
00021 
00022 /**
00023  * Copyright (C) 2008 iRex Technologies B.V.
00024  * All rights reserved.
00025  */
00026 
00027 #include "config.h"
00028 
00029 #include <sys/time.h>
00030 #include <sys/types.h>
00031 #include <dirent.h>
00032 #include <sys/stat.h>
00033 #include <time.h>
00034 #include <gtk/gtk.h>
00035 #include <unistd.h>
00036 #include <stdlib.h>
00037 
00038 #include "log.h"
00039 #include "filetypes.h"
00040 #include "index_db.h"
00041 #include "index.h"
00042 #include "tags.h"
00043 #include "index_ipc.h"
00044 #include "shortcut.h"
00045 
00046 const GSList* g_iter;
00047 gchar startdir[256];
00048 int startdirlen = 0;
00049 gchar drz_dir[256];
00050 gchar shortcut_dir[256];
00051 gchar ade_thumbs_dir[256];
00052 
00053 typedef struct
00054 {
00055     const gchar *filename;
00056     const gchar *filepath;
00057     gboolean    is_dir;
00058     gint64      filesize;
00059     gint64      date_modified;
00060 } fs_entry_t;
00061 
00062 
00063 typedef unsigned long long u_int64_t;
00064 u_int64_t getCurrentTime()
00065 {
00066     struct timespec now;
00067 
00068     clock_gettime(CLOCK_MONOTONIC, &now);
00069     u_int64_t now64 = now.tv_sec;
00070     now64 *= 1000000;
00071     now64 += (now.tv_nsec/1000);
00072     return now64;
00073 }
00074 
00075 
00076 static const char *HIDDEN_FILENAMES[] =
00077 {
00078     ERMETADB_GLOBAL_DATABASE_FILE,
00079     ERMETADB_LOCAL_DATABASE_FILE,
00080     ERMETADB_LOCAL_DATABASE_FILE "-journal",
00081     ERMETADB_LOCAL_DATABASE_FILE ".old",
00082     "autorun.inf",
00083     "Thumbs.db",
00084     NULL
00085 };
00086 
00087 
00088 static gboolean fs_ignore_filename (const gchar *filename)
00089 {
00090     int len = strlen(filename);
00091     if (len == 0) return TRUE;
00092 
00093     // check on hidden prefix
00094     if (filename[0] == '.' || filename[0] == '_') return TRUE;
00095 
00096     // check on hidden filename
00097     const char** cpp = NULL;
00098     for (cpp = HIDDEN_FILENAMES ; *cpp; cpp++)
00099     {
00100         if ( strcmp(filename, *cpp) == 0 ) return TRUE;
00101     }
00102 
00103     return FALSE;
00104 }
00105 
00106 
00107 static db_entry_t* create_db_entry(const fs_entry_t* fs_entry)
00108 {
00109     db_entry_t* entry = g_malloc(sizeof(db_entry_t));
00110     memset(entry, 0, sizeof(db_entry_t));
00111     entry->filename = g_strdup(fs_entry->filename);
00112     entry->filepath = g_strdup(fs_entry->filepath);
00113     entry->is_dir = fs_entry->is_dir;
00114     entry->filesize = fs_entry->filesize;
00115     entry->last_modified = fs_entry->date_modified;
00116     entry->state = NEW;
00117     return entry;
00118 }
00119 
00120 
00121 static db_entry_t* get_entry(const GSList* db_model, const char* filepath, const char* filename)
00122 {
00123 
00124     const GSList* iter = db_model;
00125     while (iter) {
00126         db_entry_t* db_entry = iter->data;
00127         if ((strcmp(db_entry->filename, filename) == 0) &&
00128             strcmp(db_entry->filepath, filepath) == 0)
00129         {
00130             return db_entry;
00131         }
00132         iter = g_slist_next(iter);
00133     }
00134     return NULL;
00135 }
00136 
00137 
00138 static void compare_entry(GSList** db_model, fs_entry_t* fs_entry)
00139 {
00140     db_entry_t* db_entry = get_entry(*db_model, fs_entry->filepath, fs_entry->filename);
00141     if (db_entry) {     // entry exists
00142         if (db_entry->is_dir != fs_entry->is_dir) {
00143             // add new entry, old entry will be deleted automatically
00144             db_entry_t* new_entry = create_db_entry(fs_entry);
00145             // NOTE: entry should be appended! (first delete old one, then add new one)
00146             *db_model = g_slist_append(*db_model, new_entry);
00147             return;
00148         }
00149         if (!db_entry->is_dir && db_entry->filesize != fs_entry->filesize) {
00150             db_entry->filesize = fs_entry->filesize;
00151             db_entry->state = CHANGED;
00152         }
00153         if (db_entry->last_modified != fs_entry->date_modified) {
00154             db_entry->last_modified = fs_entry->date_modified;
00155             db_entry->state = CHANGED;
00156         }
00157         if (db_entry->state == UNKNOWN) db_entry->state = SAME;
00158     } else {    // unknown entry, add to list
00159         db_entry_t* new_entry = create_db_entry(fs_entry);
00160         *db_model = g_slist_prepend(*db_model, new_entry);
00161         // NOTE: on all entries marked NEW there is memory management on strings!!
00162     }
00163 }
00164 
00165 
00166 static gboolean ignore_dir(const char* name)
00167 {
00168     if (strcmp(ade_thumbs_dir, name) == 0) return TRUE;
00169     return FALSE;
00170 }
00171 
00172 
00173 static gboolean ignore_file(const char* filename, const char* ext, gboolean is_dir) 
00174 {
00175     if (fs_ignore_filename(filename)) return TRUE;
00176 
00177     // if is directory, don't skip
00178     if (is_dir) return FALSE;
00179 
00180     // ignore files without extension
00181     if (ext[0] == 0) return TRUE;
00182 
00183     // don't skip shortcuts
00184     if (is_shortcut_file_extension(ext)) return FALSE;
00185 
00186     return (get_viewer_from_file_extension(ext) == NULL);
00187 }
00188 
00189 
00190 static gboolean ignore_thumbnail_generation(db_entry_t* db_entry)
00191 {
00192     // Only handle changed or new files      
00193     if (db_entry->state != CHANGED && db_entry->state != NEW)
00194       return TRUE;
00195 
00196     // Ignore directories
00197     if (db_entry->is_dir)
00198       return TRUE;
00199 
00200     // Otherwise check extension
00201     const char *ext = g_extension_pointer(db_entry->filename);
00202                      
00203     // Ignore files not configured
00204     if (get_viewer_from_file_extension(ext) == NULL)
00205       return TRUE;
00206       
00207     // Ignore files where thumbnail generation was not enabled for
00208     return (!get_generate_thumbnail(ext));
00209 }
00210 
00211 
00212 static void read_directory(const gchar* name, GSList** db_model)
00213 {
00214     DIR* dir = opendir(name);
00215     if (dir == NULL) {
00216         perror("opendir");
00217         return;
00218     }
00219     struct dirent* current = readdir(dir);
00220     while (current != 0)
00221     {
00222         if (current->d_name[0] == '.') goto next;
00223         char fullname[1024];
00224         snprintf(fullname, sizeof(fullname), "%s/%s", name, current->d_name);
00225         struct stat statbuf;
00226         stat(fullname, &statbuf);
00227 
00228         gboolean is_dir = S_ISDIR(statbuf.st_mode);
00229         gboolean is_reg = S_ISREG(statbuf.st_mode);
00230         if (!is_dir && !is_reg) goto next;
00231 
00232         // try to open and close all local db's for possible version updates
00233         if (strcmp(current->d_name, ERMETADB_LOCAL_DATABASE_FILE) == 0) {
00234             LOGPRINTF("trying to open and close %s", fullname);
00235             erMetadb local_db = ermetadb_local_open(name, TRUE);
00236             if (local_db == NULL) {
00237                 ERRORPRINTF("cannot open db %s", fullname);
00238             }
00239             ermetadb_close(local_db);
00240             goto next;
00241         }
00242 
00243         const char *ext = g_extension_pointer(current->d_name);
00244 
00245         if (is_drz_file_extension(ext))
00246         {
00247             if (strcmp(name, drz_dir) != 0) {
00248                 // create drz dir if it doesn't exist
00249                 g_mkdir_with_parents(drz_dir, 644);
00250     
00251                 // move file to drz directory
00252                 char command[256];
00253                 LOGPRINTF("force moving %s to %s/", fullname, drz_dir);
00254                 sprintf(command, "mv -f %s %s/", fullname, drz_dir);
00255                 system(command);
00256             }
00257             else
00258             {
00259                 LOGPRINTF("ignoring %s", current->d_name);
00260             }
00261             goto next;
00262         }
00263 
00264         if (ignore_file(current->d_name, ext, is_dir))
00265         {
00266             LOGPRINTF("ignoring %s", current->d_name);
00267             goto next;
00268         }
00269 
00270         if (is_dir && ignore_dir(fullname))
00271         {
00272             LOGPRINTF("ignoring %s", current->d_name);
00273             goto next;
00274         }
00275 
00276         fs_entry_t fs_entry;
00277         fs_entry.filename = current->d_name;
00278         fs_entry.filepath = name;
00279         fs_entry.is_dir = is_dir;
00280         fs_entry.filesize = is_dir ? 0 : statbuf.st_size;
00281         fs_entry.date_modified = statbuf.st_mtime;
00282         compare_entry(db_model, &fs_entry);
00283         
00284         if (is_dir)
00285         { 
00286             read_directory(fullname, db_model);
00287         }
00288 next:
00289         current = readdir(dir);
00290     }
00291     closedir(dir);
00292 }
00293 
00294 #if (LOGGING_ON)
00295 static const char* state2str(DBState state)
00296 {
00297     static const char* states[] =
00298     {
00299            [UNKNOWN] = "UNKNOWN",
00300            [SAME]    = "SAME",
00301            [CHANGED] = "CHANGED",
00302            [NEW]     = "NEW",
00303     };
00304     return states[state];
00305 }
00306 #endif
00307 
00308 
00309 static gboolean send_store_metadata(db_entry_t* db_entry)
00310 {
00311      LOGPRINTF("Store metadata for [%s/%s]", db_entry->filepath, db_entry->filename);
00312      ipc_send_store_metadata(db_entry->filepath, db_entry->filename);
00313      return TRUE;
00314 }
00315 
00316 
00317 // display_name and author are ut params, and need to be freed with g_free()
00318 static gboolean check_shortcut(const GSList* db_model,
00319                                const char* filepath,
00320                                const char* filename,
00321                                char** display_name,
00322                                char** author)
00323 {
00324     shortcut_t *shortcut = NULL;
00325     int ret = parse_shortcut_file(filepath, filename, &shortcut);
00326     if (ret != ER_OK) {
00327         LOGPRINTF("DENYING: %s/%s - invalid shortcut file", filepath, filename);
00328         return FALSE;
00329     }
00330     g_assert(shortcut);
00331 
00332     if (shortcut->type == SHORTCUT_TO_APPLICATION) {
00333         // dont check application shortcuts
00334         // name and author deliberatly empty, will be read from .desktop file on runtime
00335         *author = g_strdup("");
00336         *display_name = g_strdup("");
00337         goto allowed;
00338     }
00339 
00340     // only allow shortcuts to file or directory (not apps?)
00341     if (shortcut->type != SHORTCUT_TO_FILE &&
00342         shortcut->type != SHORTCUT_TO_FOLDER)
00343     {
00344         LOGPRINTF("DENYING: %s/%s - not shortcut to file/folder", filepath, filename);
00345         goto not_allowed;
00346     }
00347 
00348     // dont allow nested shortcuts
00349     if (is_shortcut_file(shortcut->details.file.filename)) {
00350         LOGPRINTF("DENYING: %s/%s - shortcut to shortcut", filepath, filename);
00351         goto not_allowed;
00352     }
00353 
00354     // check if destination exists in db
00355     if (get_entry(db_model, shortcut->details.file.directory,
00356             shortcut->details.file.filename) == NULL)
00357     {
00358         LOGPRINTF("DENYING: %s/%s - target unknown", filepath, filename);
00359         goto not_allowed;
00360     }
00361 
00362     // pass ownership to display_name
00363     *display_name = shortcut->name;
00364     shortcut->name = NULL;
00365 
00366     // pass target dir relative to startdir (= mountpoint)
00367     const char* localdir = shortcut->details.file.directory + startdirlen;
00368     if (localdir[0] == '/') {   // have subdir
00369         *author = g_strdup_printf("%s/%s", localdir+1, shortcut->details.file.filename);
00370     } else {        // no subdir
00371         *author = g_strdup(shortcut->details.file.filename);
00372     }
00373 
00374 allowed:
00375     shortcut_free(shortcut);
00376     return TRUE;
00377 not_allowed:
00378     shortcut_free(shortcut);
00379     return FALSE;
00380 }
00381 
00382 
00383 static gboolean handle_changes(const GSList* db_model, const char* dir, gboolean skip_thumbnails)
00384 {
00385     int removed = 0;
00386     int same = 0;
00387     int changed = 0;
00388     int new = 0;
00389 
00390     db_state_t db_state;
00391     if (db_open_global(&db_state, dir) != ER_OK) {
00392         ERRORPRINTF("error opening db");
00393         return FALSE;
00394     }
00395 
00396     int ret = db_start_transaction(&db_state);
00397     if (ret != ER_OK) {
00398         ERRORPRINTF("error starting transaction");
00399         goto error;
00400     }
00401 
00402     const GSList* iter = db_model;
00403     while (iter) {
00404         db_entry_t* db_entry = iter->data;
00405         if (db_entry->state != SAME) {
00406             LOGPRINTF(" %s  %4lld  %s", state2str(db_entry->state), db_entry->file_id, db_entry->filename);
00407         }
00408 
00409         gchar* display_name = NULL;
00410         gchar* author = NULL;
00411 
00412         gboolean is_shortcut = is_shortcut_file(db_entry->filename) && !db_entry->is_dir;
00413         // special checking for shortcuts
00414         if (is_shortcut && db_entry->state != UNKNOWN)
00415         {
00416             if (!check_shortcut(db_model, db_entry->filepath, db_entry->filename, &display_name, &author)) {
00417                 // delete from fs if it's in DIR_SHORTCUTS
00418                 if (strcmp(db_entry->filepath, shortcut_dir) == 0) {
00419                     char fullname[256];
00420                     sprintf(fullname, "%s/%s", db_entry->filepath, db_entry->filename);
00421                     LOGPRINTF("deleting %s", fullname);
00422                     int err = unlink(fullname);
00423                     if (err) {
00424                         WARNPRINTF("cannot delete %s", fullname);
00425                     }
00426                 }
00427                 switch (db_entry->state) {
00428                 case UNKNOWN:
00429                     // was in db, but now deleted, so ok
00430                     break;
00431                 case SAME:
00432                 case CHANGED:
00433                     // was in db, delete by setting to unknown
00434                     db_entry->state = UNKNOWN;
00435                     break;
00436                 case NEW:
00437                     // not yet in db, skip it
00438                     goto next;
00439                 }
00440             }
00441         }
00442 
00443         switch (db_entry->state) {
00444         case UNKNOWN:
00445             db_delete_entry(&db_state, db_entry);
00446             removed++;
00447             break;
00448         case SAME:
00449             // do nothing
00450             same++;
00451             break;
00452         case CHANGED:
00453             db_update_entry(&db_state, db_entry);
00454             if (is_shortcut) {
00455                 db_update_shortcut_entry(&db_state, db_entry, display_name, author);
00456             }
00457             changed++;
00458             break;
00459         case NEW:
00460             {
00461                 const gchar* tag = NULL;
00462                 if (!db_entry->is_dir) tag = get_tag_for_file(db_entry->filename, db_entry->filepath, startdir);
00463                 db_add_entry(&db_state, db_entry, tag, display_name, author);
00464                 new++;
00465                 break;
00466             }
00467         }
00468 next:
00469         g_free(display_name);
00470         g_free(author);
00471         iter = g_slist_next(iter);
00472     }
00473 
00474     db_end_transaction (&db_state);
00475 
00476     if (!skip_thumbnails) {
00477         // This while loop searches for the first document to store metadata and thumbnails for
00478         // The rest of the documents is handled in store_metadata_ready
00479         g_iter = db_model;
00480         while (g_iter) {
00481             db_entry_t* db_entry = g_iter->data;
00482 
00483             if (!ignore_thumbnail_generation(db_entry))
00484             {
00485                 if (send_store_metadata(db_entry))
00486                 {
00487                     gtk_main();
00488                     break;
00489                 }
00490             }
00491             g_iter = g_slist_next(g_iter);
00492         }
00493     }
00494 
00495 error:
00496     db_close(&db_state);
00497     printf("mdbindex: new %d  removed %d  same %d  changed %d\n", new, removed, same, changed);
00498 
00499     return (new + changed + removed) > 0;
00500 }
00501 
00502 
00503 /// @brief Handle next documents
00504 ///
00505 void store_metadata_ready()
00506 {
00507     g_iter = g_slist_next(g_iter);
00508 
00509     while (g_iter) {
00510         db_entry_t* db_entry = g_iter->data;
00511 
00512         if (!ignore_thumbnail_generation(db_entry))
00513         {
00514             if (send_store_metadata(db_entry))
00515             {
00516                 return;
00517             }
00518         }
00519         g_iter = g_slist_next(g_iter);
00520     }
00521     gtk_main_quit();
00522 }
00523 
00524 
00525 gboolean index_full(gchar *dir, gboolean skip_thumbnails)
00526 {
00527     int len = strlen(dir);
00528     // strip off trailing slash
00529     if (len>0 && dir[len-1] == '/') dir[len-1] = '\0';
00530     strncpy(startdir, dir, sizeof(startdir));
00531     startdir[sizeof(startdir)-1] = '\0';
00532     startdirlen = strlen(startdir);
00533 
00534     // check if dir exists and is dir
00535     struct stat statbuf;
00536     int res = stat(dir, &statbuf);
00537     if (res == -1 || !S_ISDIR(statbuf.st_mode)) {
00538         fprintf(stderr, "mdbindex: directory '%s' doesn't exist or is not a dir\n", dir);
00539         return FALSE;
00540     }
00541 
00542     // init static dirs with startdir pre-fix
00543     sprintf(drz_dir, "%s/%s", startdir, DIR_DRZ);
00544     sprintf(shortcut_dir, "%s/%s", startdir, DIR_SHORTCUTS);
00545     sprintf(ade_thumbs_dir, "%s/%s", startdir, DIR_ADE_THUMBS);
00546 
00547     db_state_t db_state;
00548     if (db_open_global(&db_state, dir) != ER_OK)
00549     {
00550         ERRORPRINTF("Failed to open global db");
00551         return FALSE;
00552     }
00553     db_close(&db_state);
00554     
00555     filetypes_init(FALSE);
00556 
00557     // read database model
00558     u_int64_t t1 = getCurrentTime();
00559     GSList* db_model = db_get_model(dir);
00560     if (db_model == NULL) WARNPRINTF("DB is empty");
00561     u_int64_t t2 = getCurrentTime();
00562     u_int64_t diff = t2 - t1;
00563     LOGPRINTF("DB DURATION = %lld ms", diff / 1000);
00564 
00565     // read fs and mark changes
00566     t1 = getCurrentTime();
00567     read_directory(dir, &db_model);
00568     t2 = getCurrentTime();
00569     diff = t2 - t1;
00570     LOGPRINTF("FS/COMPARE DURATION = %lld ms", diff / 1000);
00571 
00572     // update database with changed entries
00573     t1 = getCurrentTime();
00574     gboolean changed = handle_changes(db_model, dir, skip_thumbnails);
00575     t2 = getCurrentTime();
00576     diff = t2 - t1;
00577     LOGPRINTF("DB UPDATE DURATION = %lld ms", diff / 1000);
00578 
00579     // NOTE: not freeing db_model for speed
00580     return changed;
00581 }
00582 
Generated by  doxygen 1.6.2-20100208