├── .gitignore ├── Makefile ├── src ├── common.h ├── conf.h ├── wget.h ├── Makefile ├── cache.h ├── conf.c ├── wget.c ├── flickrms.c └── cache.c ├── README └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.o 3 | src/flickrms 4 | tags 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | make -C src 3 | clean: 4 | make -C src clean 5 | install: 6 | make -C src install 7 | uninstall: 8 | make -C src uninstall 9 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_H 2 | #define COMMON_H 3 | 4 | #define SUCCESS 0 5 | #define FAIL -1 6 | 7 | #define CLEAN 0 8 | #define DIRTY 1 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/conf.h: -------------------------------------------------------------------------------- 1 | #ifndef CONF_H 2 | #define CONF_H 3 | 4 | #include "common.h" 5 | 6 | char *get_conf_path(); 7 | int create_conf(char *conf_path, flickcurl *fc); 8 | int check_conf_file(char *conf_path, flickcurl *fc); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/wget.h: -------------------------------------------------------------------------------- 1 | #ifndef WGET_H 2 | #define WGET_H 3 | 4 | #include "common.h" 5 | 6 | int wget_init(); 7 | void wget_destroy(); 8 | int wget(const char *in, const char *out); 9 | int get_url_content_length(const char *url); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | CC:=gcc 2 | 3 | export PKG_CONFIG_PATH:=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig 4 | 5 | LXML:=libxml-2.0 6 | FUSE:=fuse 7 | GLIB:=glib-2.0 8 | FLKC:=flickcurl 9 | CURL:=libcurl 10 | IMGM:=MagickWand 11 | INCLUDES:=`pkg-config --libs $(FUSE) $(GLIB) $(FLKC) $(CURL) $(LXML) $(IMGM)` 12 | 13 | OPTS:=-mtune=native -march=native -O2 -pipe 14 | CFLAGS:=$(OPTS) -Wall -W -Werror -Wextra -Wconversion -Wsign-conversion -fstack-protector-strong 15 | LDFLAGS:=-lm -Wl,-O1,--as-needed,-z,relro -fopenmp 16 | 17 | OBJS:=flickrms.o cache.o wget.o conf.o 18 | 19 | PROJ:=flickrms 20 | 21 | all: $(PROJ) 22 | 23 | $(PROJ): $(OBJS) 24 | $(CC) -o $@ $^ $(INCLUDES) $(LDFLAGS) 25 | 26 | flickrms.o: flickrms.c cache.c wget.c 27 | $(CC) $(CFLAGS) `pkg-config --cflags $(FUSE) $(IMGM)` -c $< 28 | 29 | cache.o: cache.c conf.c 30 | $(CC) $(CFLAGS) `pkg-config --cflags $(GLIB) $(FLKC) $(LXML)` -c $< 31 | 32 | wget.o: wget.c 33 | $(CC) $(CFLAGS) `pkg-config --cflags $(CURL)` -c $< 34 | 35 | conf.o: conf.c 36 | $(CC) $(CFLAGS) `pkg-config --cflags $(FLKC) $(LXML)` -c $< 37 | 38 | install: 39 | cp flickrms /usr/local/bin/ 40 | 41 | uninstall: 42 | rm /usr/local/bin/flickrms 43 | 44 | clean: 45 | rm -rf $(OBJS) $(PROJ) 46 | -------------------------------------------------------------------------------- /src/cache.h: -------------------------------------------------------------------------------- 1 | #ifndef CACHE_H 2 | #define CACHE_H 3 | 4 | #include "common.h" 5 | 6 | #define PHOTO_SIZE_UNSET 0 7 | 8 | typedef struct { 9 | char *name; 10 | char *id; 11 | char *uri; 12 | time_t time; 13 | unsigned int size; 14 | unsigned short dirty; 15 | } cached_information; 16 | 17 | 18 | int flickr_cache_init(); 19 | void flickr_cache_kill(); 20 | 21 | int photoDelete(char *photo_id); 22 | unsigned int get_photoset_names(char ***names); 23 | unsigned int get_photo_names(const char *photoset, char ***names); 24 | cached_information *photoset_lookup(const char *photoset); 25 | cached_information *photo_lookup(const char *photoset, const char *photo); 26 | void free_cached_info(cached_information *ci); 27 | char *get_photo_uri(const char *photoset, const char *photo); 28 | int set_photo_name(const char *photoset, const char *photo, const char *newname); 29 | int set_photoset_name(const char *photoset, const char *newname); 30 | int set_photo_size(const char *photoset, const char *photo, unsigned int newsize); 31 | int set_photo_dirty(const char *photoset, const char *photo, unsigned short dirty); 32 | int get_photo_dirty(const char *photoset, const char *photo); 33 | int create_empty_photoset(const char *photoset); 34 | int create_empty_photo(const char *pphotoset, const char *photo); 35 | int upload_photo(const char *photoset, const char *photo, const char *path); 36 | int set_photo_photoset(const char *photoset, const char *photo, const char *new_photoset); 37 | int remove_photo_from_cache(const char *photoset, const char *photo); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /src/conf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "conf.h" 8 | 9 | 10 | #define KEY "2e66493ec959256a79e4e5a3da7df729" 11 | #define SECRET "c1b99d47790391c3" 12 | 13 | 14 | static char conf_file_name[] = ".flickcurl.conf"; 15 | 16 | 17 | char *get_conf_path() { 18 | char *conf_path; 19 | char *home; 20 | 21 | home = getenv("HOME"); 22 | if(!home) 23 | return 0; 24 | conf_path = (char *)malloc(strlen(home) + strlen(conf_file_name) + 2); 25 | if(!conf_path) 26 | return 0; 27 | strcpy(conf_path, home); 28 | strcat(conf_path, "/"); 29 | strcat(conf_path, conf_file_name); 30 | 31 | return conf_path; 32 | } 33 | 34 | int create_conf(char *conf_path, flickcurl *fc) { 35 | #define VERIFIER_SIZE 12 36 | char verifier[VERIFIER_SIZE]; 37 | 38 | flickcurl_set_oauth_client_key(fc, KEY); 39 | flickcurl_set_oauth_client_secret(fc, SECRET); 40 | 41 | if(flickcurl_oauth_create_request_token(fc, NULL)) 42 | return FAIL; 43 | 44 | const char *uri = flickcurl_oauth_get_authorize_uri(fc); 45 | 46 | printf( "Go to: %s\n", uri ); 47 | printf( "Enter the 9-digit Verifier: " ); 48 | 49 | if(!fgets(verifier, VERIFIER_SIZE, stdin)) 50 | return FAIL; 51 | 52 | if(flickcurl_oauth_create_access_token(fc, verifier)) 53 | return FAIL; 54 | 55 | if(flickcurl_config_write_ini(fc, conf_path, "flickr")) 56 | return FAIL; 57 | 58 | return SUCCESS; 59 | } 60 | 61 | int check_conf_file(char *conf_path, flickcurl *fc) { 62 | if(access(conf_path, F_OK) < 0) /* If conf file doesn't exist, create */ 63 | if(create_conf(conf_path, fc)) 64 | return FAIL; 65 | 66 | return SUCCESS; 67 | } 68 | 69 | -------------------------------------------------------------------------------- /src/wget.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "wget.h" 8 | 9 | 10 | int wget_init() { 11 | if(curl_global_init(CURL_GLOBAL_ALL)) 12 | return FAIL; 13 | 14 | return SUCCESS; 15 | } 16 | 17 | void wget_destroy() { 18 | curl_global_cleanup(); 19 | } 20 | 21 | int wget(const char *in, const char *out) { 22 | CURL *curl; 23 | CURLcode res; 24 | FILE *fp; 25 | 26 | if(!(curl = curl_easy_init())) 27 | return FAIL; 28 | 29 | if(!(fp = fopen(out, "wb"))) // Open in binary 30 | return FAIL; 31 | 32 | // Set the curl easy options 33 | curl_easy_setopt(curl, CURLOPT_URL, in); 34 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); 35 | //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); 36 | 37 | res = curl_easy_perform(curl); // Perform the download and write 38 | 39 | curl_easy_cleanup(curl); 40 | fsync(fileno(fp)); 41 | fclose(fp); 42 | return res; 43 | } 44 | 45 | static size_t throw_away(void *ptr, size_t size, size_t nmemb, void *data) 46 | { 47 | (void)ptr; 48 | (void)data; 49 | return (size_t)(size * nmemb); 50 | } 51 | 52 | /* Perform a HEAD request to the url to get the Content Length from 53 | * the headers. Does not get the body. 54 | */ 55 | int get_url_content_length(const char *url) { 56 | CURL *curl; 57 | CURLcode res; 58 | double content_length; 59 | 60 | if(!(curl = curl_easy_init())) 61 | return FAIL; 62 | 63 | // Set the curl easy options 64 | curl_easy_setopt(curl, CURLOPT_URL, url); 65 | curl_easy_setopt(curl, CURLOPT_NOBODY, 1); // Use HEADER request 66 | curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, throw_away); 67 | curl_easy_setopt(curl, CURLOPT_HEADER, 0L); 68 | //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); 69 | 70 | res = curl_easy_perform(curl); 71 | 72 | if(res) { 73 | curl_easy_cleanup(curl); 74 | return FAIL; 75 | } 76 | 77 | res = curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &content_length); 78 | curl_easy_cleanup(curl); 79 | 80 | return (res) ? FAIL : (int)round(content_length); 81 | } 82 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ==Authors== 2 | Created by: Patrick Jennings 3 | Website: https://github.com/patrickjennings/FlickrMS 4 | 5 | 6 | ==About== 7 | Are you tired of spending a significant amount of time messing around with the 8 | Flickr website? Are you sick of seeing advertisements on every page you load? 9 | Or are you just looking for an easier way to manage your Flickr photos from any 10 | computer? Imagine being able to use the applications you want to view and modify 11 | your photos while still taking advantage of the unlimited storage space of the 12 | Flickr cloud! Paying for Flickr Pro is no longer the only option for satisfying 13 | your desire for photo perfection... not when there is FlickrMS! 14 | 15 | This application can be used to mount a Flickr account directly onto your 16 | file system. The photos will seem to be on your local machine but all changes 17 | done will also affect the users Flickr account. Once mounted, the file system 18 | will act like any other directory and you can use any of your favorite image 19 | viewers or editors to access your Flickr photos! 20 | 21 | Photos will be placed in directories that represent which photoset the photo 22 | belongs to. If a photo does not belong to a photoset, the photo will appear in 23 | the root directory. A photo that has an empty name in Flickr, will display its 24 | Flickr photo id instead. All of this is to ensure that all of your photos will 25 | be organized the way you intended and are perfectly viewable from the file 26 | system. 27 | 28 | The application is written in C and was designed for efficiency and 29 | compatability. It will run on any POSIX compliant machine. This means all 30 | UNIX-based and BSD-based machines should be supported. The application will use 31 | the lowest amount of resources possible. The internal caching mechanism 32 | dynamically grows to allow for infinite storage capacity while still recognizing 33 | remote changes to the Flickr account. 34 | 35 | FlickrMS was written by an experienced and well-educated hacker. As such, the 36 | file system will be tested for bugs and UI malfunctions. If you find any pressing 37 | concerns, please let them be known at the following URL: 38 | https://github.com/patrickjennings/FlickrMS/issues 39 | 40 | 41 | ==Installation== 42 | Dependencies: 43 | FUSE 44 | http://fuse.sourceforge.net 45 | Flickcurl-git 46 | https://github.com/dajobe/flickcurl 47 | pkg-config 48 | git://anongit.freedesktop.org/pkg-config 49 | libxml-2.0 50 | http://www.xmlsoft.org 51 | glib-2.0 52 | https://developer.gnome.org/glib/ 53 | libcurl 54 | http://curl.haxx.se/libcurl/ 55 | ImageMagick 56 | http://www.imagemagick.org 57 | 58 | See https://github.com/patrickjennings/FlickrMS/wiki/Installation for more 59 | information about installing dependencies. 60 | 61 | FlickrMS comes with a custom Makefile that can be used to create the binary 62 | application. To compile and create the binary, simply type: 63 | 64 | $ make 65 | 66 | To install the binary, type: 67 | 68 | $ sudo make install 69 | 70 | To uninstall, simply type: 71 | 72 | $ sudo make uninstall 73 | 74 | 75 | ==Configuration== 76 | The configuration is the same used by the flickcurl library. On first 77 | start, a configuration file is created for the user. You must authorize 78 | the use of your flickr account in order to gain the ability to see and 79 | edit photos. 80 | 81 | See http://librdf.org/flickcurl/ for more information on Flickrcurl API 82 | configuration and http://www.flickr.com/services/apps/72157623762128193/ 83 | for more information on authorization. 84 | 85 | 86 | ==Usage== 87 | To mount, execute: 88 | 89 | $ flickrms mountDir/ 90 | 91 | This mounts the file system onto the empty directory 'mountDir'. Then 92 | the you will be able to navigate into 'mountDir' and directly access 93 | your Flickr photos! You will then be able to interact with the files 94 | and folders just like if they were on your local computer. 95 | 96 | To unmount, execute: 97 | 98 | $ fusermount -u mountDir/ 99 | 100 | This will remove the file system from the 'mountDir' directory. If you 101 | navigate into 'mountDir' you will notice that your Flickr photos will 102 | no longer be visible. 103 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2011 Patrick Jennings 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/flickrms.c: -------------------------------------------------------------------------------- 1 | #define FUSE_USE_VERSION 30 2 | #define _XOPEN_SOURCE 500 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #pragma GCC diagnostic push 13 | #pragma GCC diagnostic ignored "-Wunused-parameter" 14 | #pragma GCC diagnostic ignored "-Wconversion" 15 | #pragma GCC diagnostic ignored "-Wfloat-conversion" 16 | #pragma GCC diagnostic ignored "-Wsign-conversion" 17 | #include 18 | #pragma GCC diagnostic pop 19 | 20 | #include "cache.h" 21 | #include "wget.h" 22 | 23 | 24 | #define PERMISSIONS 0755 /* Cached file permissions. */ 25 | #define TMP_DIR_NAME ".flickrms" /* Where to place cached photos. */ 26 | #define PHOTO_TIMEOUT 14400 /* In seconds. */ 27 | 28 | 29 | /* Determines whether to report the true file size in getattr before the file 30 | * has been downloaded locally. The true file size will always be set and cached 31 | * after a file has been downloaded (which happens when opening a file). If this 32 | * option is set, Flickrms will query the remote server to get only the file 33 | * size for each photo from the response headers but not download the photo. 34 | * Using the true photo size is recommended for GUI applications as they tend to 35 | * stat the file before opening. 36 | * Using a fake file size will be much faster for command line usage. 37 | */ 38 | #define USE_TRUE_PHOTO_SIZE 1 /* 1 will use the true size. 0 will use a fake size. */ 39 | #define FAKE_PHOTO_SIZE 1024 /* Only used when USE_TRUE_PHOTO_SIZE is set. */ 40 | 41 | /* Whether to clean the temporary directory on unmount. 42 | * The filesystem does not keep track of files that cannot be uploaded to Flickr, 43 | * such as lock and hidden files created by file browsers, after the file system 44 | * has been destroyed. This option clears the temp dir used for cached files. 45 | */ 46 | #define CLEAN_TMP_DIR_UMOUNT 1 /* 1 or 0. */ 47 | 48 | 49 | static uid_t uid; /* The user id of the user that mounted the filesystem */ 50 | static gid_t gid; /* The group id of the user */ 51 | 52 | static char *tmp_path; 53 | 54 | 55 | /** 56 | * Helper functions 57 | **/ 58 | 59 | /* 60 | * Returns the first index of a '/' or negative if non exists in the 61 | * path supplied. 62 | */ 63 | static size_t get_slash_index(const char *path, unsigned short *found) { 64 | size_t i; 65 | *found = 0; 66 | if(!path) 67 | return 0; 68 | for(i = 0; i < strlen(path); i++) { 69 | if(path[i] == '/') { 70 | *found = 1; 71 | break; 72 | } 73 | } 74 | return i; 75 | } 76 | 77 | /* 78 | * Internal method for splitting a path of the format: 79 | * "/photosetname/photoname" 80 | * into: photoset = "photosetname" and photo = "photoname" 81 | */ 82 | static int get_photoset_photo_from_path(const char *path, char **photoset, char **photo) { 83 | size_t i; 84 | unsigned short found; 85 | char *path_dup; 86 | 87 | path_dup = strdup(path + 1); 88 | i = get_slash_index(path_dup, &found); 89 | 90 | if(!path || !photoset || !photo || !path_dup) 91 | return FAIL; 92 | 93 | if(found) { 94 | path_dup[i] = '\0'; 95 | *photoset = strdup(path_dup); 96 | *photo = strdup(path_dup + i + 1); 97 | } 98 | else { 99 | *photoset = strdup(""); 100 | *photo = strdup(path_dup); 101 | } 102 | 103 | free(path_dup); 104 | return SUCCESS; 105 | } 106 | 107 | /* 108 | * Sets the uid/gid variables to the user's (who mounted the filesystem) 109 | * uid/gid. Want to only give the user access to their flickr account. 110 | */ 111 | static inline int set_user_variables() { 112 | uid = getuid(); 113 | gid = getgid(); 114 | return SUCCESS; 115 | } 116 | 117 | /* 118 | * Set the path to the directory that will be used to get the image 119 | * data from Flickr. 120 | */ 121 | static inline int set_tmp_path() { 122 | char *home; 123 | 124 | if(!(home = getenv("HOME"))) 125 | return FAIL; 126 | 127 | tmp_path = (char *)malloc(strlen(home) + strlen(TMP_DIR_NAME) + 2); 128 | if(!tmp_path) 129 | return FAIL; 130 | 131 | strcpy(tmp_path, home); 132 | strcat(tmp_path, "/"); 133 | strcat(tmp_path, TMP_DIR_NAME); 134 | 135 | return 0 - mkdir(tmp_path, PERMISSIONS); 136 | } 137 | 138 | static int remove_tmp_file(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { 139 | (void)sb; 140 | (void)typeflag; 141 | if(ftwbuf->level == 0) 142 | return SUCCESS; 143 | return remove(fpath); 144 | } 145 | 146 | static inline void remove_tmp_path() { 147 | nftw(tmp_path, remove_tmp_file, 64, FTW_DEPTH | FTW_PHYS); 148 | free(tmp_path); 149 | } 150 | 151 | static inline void imagemagick_init() { 152 | MagickWandGenesis(); 153 | } 154 | 155 | static inline void imagemagick_destroy() { 156 | MagickWandTerminus(); 157 | } 158 | 159 | 160 | /** 161 | * File system functions 162 | **/ 163 | 164 | static inline void set_stbuf(struct stat *stbuf, mode_t mode, uid_t uid, 165 | gid_t gid, off_t size, time_t time, nlink_t nlink) { 166 | stbuf->st_mode = mode; 167 | stbuf->st_uid = uid; 168 | stbuf->st_gid = gid; 169 | stbuf->st_size = size; 170 | stbuf->st_mtime = time; 171 | stbuf->st_nlink = nlink; 172 | } 173 | 174 | /* 175 | * Get photo size if needed. 176 | */ 177 | static int process_photo(const char *photoset, const char *photo, cached_information *ci) { 178 | if(ci->size == PHOTO_SIZE_UNSET && ci->uri) { 179 | int photo_size = FAKE_PHOTO_SIZE; 180 | 181 | if(USE_TRUE_PHOTO_SIZE) { 182 | photo_size = get_url_content_length(ci->uri); 183 | 184 | if(photo_size < 0) 185 | return FAIL; 186 | } 187 | 188 | ci->size = (unsigned int)photo_size; 189 | } 190 | else { /* Possibly dirty. Try stating cached directory. */ 191 | struct stat st_buf; 192 | char *cached_path = (char *)malloc(strlen(photoset) + strlen(photo) + strlen(tmp_path) + 3); 193 | strcpy(cached_path, tmp_path); 194 | if(strcmp(photoset,"")) { 195 | strcat(cached_path, "/"); 196 | strcat(cached_path, photoset); 197 | } 198 | strcat(cached_path, "/"); 199 | strcat(cached_path, photo); 200 | 201 | if(!stat(cached_path, &st_buf)) { 202 | ci->size = (unsigned int)st_buf.st_size; 203 | } 204 | 205 | free(cached_path); 206 | } 207 | 208 | set_photo_size(photoset, photo, ci->size); 209 | return SUCCESS; 210 | } 211 | 212 | static int prime_photo_size_cache(const char *photoset, char **names, unsigned int num_names) { 213 | unsigned int i; 214 | 215 | #pragma omp parallel for 216 | for(i = 0; i < num_names; i++) { 217 | cached_information *ci = photo_lookup(photoset, names[i]); 218 | 219 | if(ci) { 220 | process_photo(photoset, names[i], ci); 221 | free_cached_info(ci); 222 | } 223 | } 224 | 225 | return SUCCESS; 226 | } 227 | 228 | /* 229 | * Gets the attributes (stat) of the node at path. 230 | */ 231 | static int fms_getattr(const char *path, struct stat *stbuf) { 232 | int retval = -ENOENT; 233 | memset((void *)stbuf, 0, sizeof(struct stat)); 234 | 235 | if(!strcmp(path, "/")) { /* Path is mount directory */ 236 | /* FIXME: Total size of all files... or leave at 0? */ 237 | set_stbuf(stbuf, S_IFDIR | PERMISSIONS, uid, gid, 0, 0, 1); 238 | retval = SUCCESS; 239 | } 240 | else { 241 | cached_information *ci = NULL; 242 | /* Point to charcter after root directory */ 243 | const char *lookup_path = path + 1; 244 | unsigned short found; 245 | size_t index = get_slash_index(lookup_path, &found); /* Look up first forward slash */ 246 | /* If forward slash doesn't exist, we are looking at a photo without a photoset or a photoset. */ 247 | if(!found) { 248 | if((ci = photoset_lookup(lookup_path))) { /* See if path is to a photoset (i.e. a directory ) */ 249 | set_stbuf(stbuf, S_IFDIR | PERMISSIONS, uid, gid, ci->size, ci->time, 1); 250 | retval = SUCCESS; 251 | } 252 | else if((ci = photo_lookup("", lookup_path))) { /* See if path is to a photo (i.e. a file ) */ 253 | process_photo("", lookup_path, ci); 254 | set_stbuf(stbuf, S_IFREG | PERMISSIONS, uid, gid, ci->size, ci->time, 1); 255 | retval = SUCCESS; 256 | } 257 | } 258 | else { 259 | /* If forward slash does exist, it means that we have a photo with a photoset (the chars before 260 | * the slash are the photoset name and the chars after the slash are the photo name. 261 | */ 262 | char *photoset = (char *)malloc(index + 1); 263 | if(!photoset) 264 | retval = -ENOMEM; 265 | else { 266 | strncpy(photoset, lookup_path, index); /* Extract the photoset from the path */ 267 | photoset[index] = '\0'; 268 | 269 | ci = photo_lookup(photoset, lookup_path + index + 1); /* Look for the photo */ 270 | if(ci) { 271 | process_photo(photoset, lookup_path + index + 1, ci); 272 | set_stbuf(stbuf, S_IFREG | PERMISSIONS, uid, gid, ci->size, ci->time, 1); 273 | retval = SUCCESS; 274 | } 275 | free(photoset); 276 | } 277 | } 278 | if(ci) 279 | free_cached_info(ci); 280 | } 281 | return retval; 282 | } 283 | 284 | /* Read directory */ 285 | static int fms_readdir(const char *path, void *buf, 286 | fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { 287 | unsigned int num_names, i; 288 | char **names; 289 | (void)offset; 290 | (void)fi; 291 | 292 | filler(buf, ".", NULL, 0); 293 | filler(buf, "..", NULL, 0); 294 | 295 | if(!strcmp(path, "/")) { /* Path is to mounted directory */ 296 | num_names = get_photoset_names(&names); /* Report photoset names */ 297 | for(i = 0; i < num_names; i++) { 298 | filler(buf, names[i], NULL, 0); 299 | free(names[i]); 300 | } 301 | if(num_names > 0) 302 | free(names); 303 | 304 | num_names = get_photo_names("", &names); /* Get all photo names with no photoset attached */ 305 | } 306 | else 307 | num_names = get_photo_names(path + 1, &names); /* Get the names of photos in the photoset */ 308 | 309 | if(num_names > 0) 310 | prime_photo_size_cache(path + 1, names, num_names); 311 | 312 | for(i = 0; i < num_names; i++) { 313 | filler(buf, names[i], NULL, 0); 314 | free(names[i]); 315 | } 316 | free(names); 317 | 318 | return SUCCESS; 319 | } 320 | 321 | static int fms_rename(const char *old_path, const char *new_path) { 322 | char *old_photo; 323 | char *old_photoset; 324 | char *new_photo; 325 | char *new_photoset; 326 | 327 | if(get_photoset_photo_from_path(old_path, &old_photoset, &old_photo)) 328 | return FAIL; 329 | 330 | if(get_photoset_photo_from_path(new_path, &new_photoset, &new_photo)) 331 | return FAIL; 332 | 333 | if(strcmp(old_photo, new_photo)) { 334 | if(set_photo_name(old_photoset, old_photo, new_photo)) { 335 | if(set_photoset_name(old_path + 1, new_path + 1)) 336 | return FAIL; 337 | } 338 | else { 339 | free(old_photo); 340 | old_photo = strdup(new_photo); 341 | } 342 | } 343 | 344 | if(strcmp(old_photoset, new_photoset)) 345 | if(set_photo_photoset(old_photoset, old_photo, new_photoset)) 346 | return FAIL; 347 | 348 | free(old_photo); 349 | free(old_photoset); 350 | free(new_photo); 351 | free(new_photoset); 352 | return SUCCESS; 353 | } 354 | 355 | static inline void set_photoset_tmp_dir(char *dir_path, const char *tmp_path, 356 | const char *photoset) { 357 | strcpy(dir_path, tmp_path); 358 | strcat(dir_path, "/"); 359 | strcat(dir_path, photoset); 360 | } 361 | 362 | static int fms_open(const char *path, struct fuse_file_info *fi) { 363 | char *photo; 364 | char *photoset; 365 | char *uri; 366 | char *wget_path; 367 | int fd; 368 | struct stat st_buf; 369 | 370 | #define RET(ret) free(wget_path); free(uri); free(photo); free(photoset); return ret; 371 | 372 | if(get_photoset_photo_from_path(path, &photoset, &photo)) 373 | return FAIL; 374 | 375 | wget_path = (char *)malloc(strlen(tmp_path) + strlen(path) + 1); 376 | set_photoset_tmp_dir(wget_path, tmp_path, photoset); 377 | 378 | uri = get_photo_uri(photoset, photo); 379 | if(uri) { 380 | mkdir(wget_path, PERMISSIONS); /* Create photoset temp directory if it doesn't exist */ 381 | 382 | strcpy(wget_path, tmp_path); 383 | strcat(wget_path, path); 384 | 385 | if(access(wget_path, F_OK)) { 386 | /* Get the image from flickr and put it into the temp dir if it doesn't already exist. */ 387 | if(wget(uri, wget_path) < 0) { 388 | RET(FAIL) 389 | } 390 | } 391 | } 392 | else 393 | { 394 | /* Photo dirty? Try to open anyway. */ 395 | strcpy(wget_path, tmp_path); 396 | strcat(wget_path, path); 397 | } 398 | 399 | if(stat(wget_path, &st_buf)) { 400 | RET(FAIL) 401 | } 402 | 403 | if((time(NULL) - st_buf.st_mtime) > PHOTO_TIMEOUT) { 404 | if(uri && wget(uri, wget_path) < 0) { 405 | RET(FAIL) 406 | } 407 | } 408 | 409 | fd = open(wget_path, fi->flags); 410 | if(fd < 0) { 411 | RET(-errno) 412 | } 413 | 414 | fi->fh = (uint64_t)fd; 415 | set_photo_size(photoset, photo, (unsigned int)st_buf.st_size); 416 | 417 | RET(SUCCESS) 418 | } 419 | 420 | static int fms_read(const char *path, char *buf, size_t size, 421 | off_t offset, struct fuse_file_info *fi) { 422 | (void)path; 423 | ssize_t ret = pread((int)fi->fh, buf, size, offset); 424 | return (ret < 0) ? -errno : (int)ret; 425 | } 426 | 427 | static int fms_write(const char *path, const char *buf, size_t size, 428 | off_t offset, struct fuse_file_info *fi) { 429 | (void)path; 430 | char *photoset, *photo; 431 | ssize_t ret; 432 | 433 | if(get_photoset_photo_from_path(path, &photoset, &photo)) 434 | return FAIL; 435 | 436 | set_photo_dirty(photoset, photo, DIRTY); 437 | 438 | free(photoset); 439 | free(photo); 440 | ret = pwrite((int)fi->fh, buf, size, offset); 441 | 442 | return (ret < 0) ? -errno : (int)ret; 443 | } 444 | 445 | static int fms_flush(const char *path, struct fuse_file_info *fi) { 446 | (void)path; 447 | (void)fi; 448 | return SUCCESS; 449 | } 450 | 451 | static int fms_release(const char *path, struct fuse_file_info *fi) { 452 | (void)fi; 453 | char *photoset, *photo; 454 | char *temp_scratch_path; 455 | 456 | if(get_photoset_photo_from_path(path, &photoset, &photo)) 457 | return FAIL; 458 | 459 | temp_scratch_path = (char *)malloc(strlen(tmp_path) + strlen(path) + 1); 460 | strcpy(temp_scratch_path, tmp_path); 461 | strcat(temp_scratch_path, path); 462 | 463 | if(get_photo_dirty(photoset, photo) == DIRTY) { 464 | MagickWand *mw = NewMagickWand(); 465 | 466 | if(!mw) 467 | return FAIL; 468 | 469 | if(MagickPingImage(mw, temp_scratch_path)) 470 | upload_photo(photoset, photo, temp_scratch_path); 471 | 472 | DestroyMagickWand(mw); 473 | } 474 | 475 | free(temp_scratch_path); 476 | free(photoset); 477 | free(photo); 478 | 479 | int ret = close((int)fi->fh); 480 | return (ret < 0) ? -errno : SUCCESS; 481 | } 482 | 483 | static int fms_create(const char *path, mode_t mode, struct fuse_file_info *fi) { 484 | int fd; 485 | char *photoset, *photo; 486 | char *temp_scratch_path; 487 | 488 | if(get_photoset_photo_from_path(path, &photoset, &photo)) 489 | return FAIL; 490 | 491 | int retval = create_empty_photo(photoset, photo); 492 | 493 | free(photoset); 494 | free(photo); 495 | 496 | if(retval) 497 | return FAIL; 498 | 499 | temp_scratch_path = (char *)malloc(strlen(tmp_path) + strlen(path) + 1); 500 | strcpy(temp_scratch_path, tmp_path); 501 | strcat(temp_scratch_path, path); 502 | 503 | fd = creat(temp_scratch_path, mode); 504 | fi->fh = (uint64_t)fd; 505 | 506 | free(temp_scratch_path); 507 | return (fd < 0) ? -errno : SUCCESS; 508 | } 509 | 510 | /* Only called after create. For new files. */ 511 | static int fms_fgetattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi) { 512 | (void)path; 513 | int ret = fstat((int)fi->fh, stbuf); 514 | return (ret < 0) ? -errno : SUCCESS; 515 | } 516 | 517 | static int fms_mkdir(const char *path, mode_t mode) { 518 | (void)mode; 519 | char *temp_scratch_path; 520 | const char *photoset = path + 1; 521 | unsigned short found; 522 | 523 | get_slash_index(photoset, &found); 524 | if(found) // Can only mkdir on first level 525 | return FAIL; 526 | 527 | if(create_empty_photoset(photoset)) 528 | return FAIL; 529 | 530 | temp_scratch_path = (char *)malloc(strlen(tmp_path) + strlen(path) + 1); 531 | strcpy(temp_scratch_path, tmp_path); 532 | strcat(temp_scratch_path, path); 533 | 534 | mkdir(temp_scratch_path, PERMISSIONS); // Create photoset tmp directory 535 | 536 | free(temp_scratch_path); 537 | 538 | return SUCCESS; 539 | } 540 | 541 | int fms_statfs(const char *path, struct statvfs* stbuf) { 542 | (void)path; 543 | return statvfs(tmp_path, stbuf); 544 | } 545 | 546 | int fms_chmod(const char *path, mode_t mode) { 547 | char *temp_scratch_path; 548 | int retval = FAIL; 549 | 550 | temp_scratch_path = (char *)malloc(strlen(tmp_path) + strlen(path) + 1); 551 | strcpy(temp_scratch_path, tmp_path); 552 | strcat(temp_scratch_path, path); 553 | 554 | retval = chmod(temp_scratch_path, mode); 555 | 556 | free(temp_scratch_path); 557 | 558 | return retval; 559 | } 560 | 561 | int fms_chown(const char *path, uid_t uid, gid_t gid) { 562 | char *temp_scratch_path; 563 | int retval = FAIL; 564 | 565 | temp_scratch_path = (char *)malloc(strlen(tmp_path) + strlen(path) + 1); 566 | strcpy(temp_scratch_path, tmp_path); 567 | strcat(temp_scratch_path, path); 568 | 569 | retval = chown(temp_scratch_path, uid, gid); 570 | 571 | free(temp_scratch_path); 572 | 573 | return retval; 574 | } 575 | 576 | int fms_unlink(const char *path) { 577 | char *photoset, *photo; 578 | char *temp_scratch_path; 579 | int retval = FAIL; 580 | 581 | if(get_photoset_photo_from_path(path, &photoset, &photo)) 582 | return FAIL; 583 | 584 | if(remove_photo_from_cache(photoset, photo)) { 585 | free(photoset); 586 | free(photo); 587 | return FAIL; 588 | } 589 | 590 | temp_scratch_path = (char *)malloc(strlen(tmp_path) + strlen(path) + 1); 591 | strcpy(temp_scratch_path, tmp_path); 592 | strcat(temp_scratch_path, path); 593 | 594 | retval = unlink(temp_scratch_path); 595 | 596 | free(temp_scratch_path); 597 | free(photoset); 598 | free(photo); 599 | 600 | return retval; 601 | } 602 | 603 | 604 | /** 605 | * Main function 606 | **/ 607 | 608 | 609 | static struct fuse_operations flickrms_oper = { 610 | .getattr = fms_getattr, 611 | .readdir = fms_readdir, 612 | .open = fms_open, 613 | .read = fms_read, 614 | .write = fms_write, 615 | .flush = fms_flush, 616 | .release = fms_release, 617 | .rename = fms_rename, 618 | .create = fms_create, 619 | .fgetattr = fms_fgetattr, 620 | .mkdir = fms_mkdir, 621 | .statfs = fms_statfs, 622 | .chmod = fms_chmod, 623 | .chown = fms_chown, 624 | .unlink = fms_unlink 625 | }; 626 | 627 | int main(int argc, char *argv[]) { 628 | int ret; 629 | 630 | if((ret = set_user_variables())) 631 | return ret; 632 | if((ret = set_tmp_path()) == FAIL) 633 | return ret; 634 | if((ret = flickr_cache_init())) 635 | return ret; 636 | if((ret = wget_init())) 637 | return ret; 638 | 639 | imagemagick_init(); 640 | 641 | ret = fuse_main(argc, argv, &flickrms_oper, NULL); 642 | 643 | flickr_cache_kill(); 644 | wget_destroy(); 645 | imagemagick_destroy(); 646 | 647 | if(CLEAN_TMP_DIR_UMOUNT) 648 | remove_tmp_path(); 649 | 650 | return ret; 651 | } 652 | -------------------------------------------------------------------------------- /src/cache.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "cache.h" 9 | #include "conf.h" 10 | 11 | 12 | #define DEFAULT_CACHE_TIMEOUT 14400 /* In seconds. */ 13 | 14 | /* Valid sizes: http://librdf.org/flickcurl/api/flickcurl-section-photo.html#flickcurl-photo-as-source-uri */ 15 | #define GET_PHOTO_SIZE 'o' 16 | #define PHOTOS_PER_API_CALL 100 17 | #define PHOTO_EXTRAS "date_taken,url_o,original_format" 18 | 19 | 20 | /* Photo parameters */ 21 | #define SAFETY_LEVEL 1 22 | #define CONTENT_TYPE 1 23 | 24 | #define CACHE_UNSET 0 25 | #define CACHE_SET 1 26 | 27 | 28 | typedef struct { 29 | cached_information ci; 30 | unsigned short set; 31 | GHashTable *photo_ht; 32 | } cached_photoset; 33 | 34 | typedef struct { 35 | cached_information ci; 36 | } cached_photo; 37 | 38 | 39 | static GHashTable *photoset_ht; /* The photoset cache */ 40 | static pthread_rwlock_t cache_lock; /* To make thread safe */ 41 | static time_t last_cleaned; /* To age/invalidate the cache */ 42 | 43 | static flickcurl *fc; 44 | 45 | 46 | static inline cached_photo *create_cached_photo() { 47 | return (cached_photo *)calloc(1, sizeof(cached_photo)); 48 | } 49 | 50 | static inline cached_photoset *create_cached_photoset() { 51 | return (cached_photoset *)calloc(1, sizeof(cached_photoset)); 52 | } 53 | 54 | static inline GHashTable *create_cache() { 55 | return g_hash_table_new(g_str_hash, g_str_equal); 56 | } 57 | 58 | 59 | /* 60 | * Initialize the flickcurl connection 61 | */ 62 | static int flickr_init() { 63 | char *conf_path; 64 | char *login; 65 | 66 | flickcurl_init(); 67 | fc = flickcurl_new(); 68 | 69 | conf_path = get_conf_path(); 70 | if(!conf_path) 71 | return FAIL; 72 | 73 | if(check_conf_file(conf_path, fc)) 74 | return FAIL; 75 | 76 | /* Read from the config file, ~/.flickcurl.conf */ 77 | if(flickcurl_config_read_ini(fc, conf_path, "flickr", fc, flickcurl_config_var_handler)) 78 | return FAIL; 79 | 80 | login = flickcurl_test_login(fc); 81 | if(!login) 82 | return FAIL; 83 | 84 | free(login); 85 | free(conf_path); 86 | return SUCCESS; 87 | } 88 | 89 | static void flickr_kill() { 90 | flickcurl_free(fc); 91 | flickcurl_finish(); 92 | } 93 | 94 | 95 | /** 96 | * ===Cache Methods=== 97 | **/ 98 | 99 | /* Creates a new cached_photoset using the photoset or a blank one if NULL is passed in */ 100 | static int new_cached_photoset(cached_photoset **cps, flickcurl_photoset *fps) { 101 | unsigned int i; 102 | cached_information *ci; 103 | 104 | *cps = create_cached_photoset(); 105 | if(!cps) 106 | return FAIL; 107 | 108 | ci = &((*cps)->ci); 109 | ci->name = strdup(fps ? fps->title : ""); 110 | ci->id = strdup(fps ? fps->id : ""); 111 | ci->time = 0; 112 | ci->size = fps ? (unsigned int)fps->photos_count : 0; 113 | ci->dirty = CLEAN; 114 | (*cps)->set = CACHE_UNSET; 115 | (*cps)->photo_ht = g_hash_table_new(g_str_hash, g_str_equal); 116 | 117 | /* Replace backslashes with spaces */ 118 | for(i = 0; i < strlen(ci->name); i++) { 119 | if(ci->name[i] == '/') 120 | ci->name[i] = ' '; 121 | } 122 | return SUCCESS; 123 | } 124 | 125 | static cached_information *copy_cached_info(const cached_information *ci) { 126 | cached_information *newci = ci?(cached_information *)malloc(sizeof(cached_information)):NULL; 127 | if(!newci) 128 | return NULL; 129 | *newci = *ci; 130 | if(ci->name) 131 | newci->name = strdup(ci->name); 132 | if(ci->id) 133 | newci->id = strdup(ci->id); 134 | return newci; 135 | } 136 | 137 | void free_cached_info(cached_information *ci) { 138 | if(ci) { 139 | free(ci->name); 140 | free(ci->id); 141 | free(ci); 142 | } 143 | } 144 | 145 | /* All of our keys and values will be dynamic so we will want to free them. */ 146 | static gboolean free_photo_ht(gpointer key, gpointer value, gpointer user_data) { 147 | (void)user_data; 148 | cached_photo *cp = value; 149 | 150 | if(cp->ci.dirty == CLEAN) { 151 | free(cp->ci.uri); 152 | free(cp->ci.name); 153 | free(cp->ci.id); 154 | free(key); 155 | free(value); 156 | return TRUE; 157 | } 158 | else { 159 | return FALSE; 160 | } 161 | } 162 | 163 | /* All of our keys and values will be dynamic so we will want to free them. */ 164 | static gboolean free_photoset_ht(gpointer key, gpointer value, gpointer user_data) { 165 | GHashTable *photo_ht; 166 | (void)user_data; 167 | 168 | cached_photoset *cps = value; 169 | photo_ht = cps->photo_ht; 170 | 171 | g_hash_table_foreach_remove(photo_ht, free_photo_ht, NULL); 172 | 173 | if(cps->ci.dirty == CLEAN && g_hash_table_size(photo_ht) == 0) { 174 | g_hash_table_destroy(photo_ht); 175 | 176 | free(key); 177 | free(cps->ci.name); 178 | free(cps->ci.id); 179 | free(value); 180 | return TRUE; 181 | } 182 | else { 183 | cps->set = (cps->ci.dirty == CLEAN) ? CACHE_UNSET : CACHE_SET; 184 | return FALSE; 185 | } 186 | } 187 | 188 | /* 189 | * Checks whether the cache should be cleaned. Time can be changed in 190 | * DEFAULT_CACHE_TIMEOUT define. 191 | * Assumes there is a lock initiated 192 | */ 193 | static int check_cache() { 194 | flickcurl_photoset **fps; 195 | cached_photoset *cps; 196 | int i; 197 | 198 | if((time(NULL) - last_cleaned) < DEFAULT_CACHE_TIMEOUT) 199 | return SUCCESS; 200 | 201 | pthread_rwlock_unlock(&cache_lock); /* Release the read lock and lock for writting */ 202 | pthread_rwlock_wrlock(&cache_lock); 203 | 204 | if((time(NULL) - last_cleaned) < DEFAULT_CACHE_TIMEOUT) 205 | return SUCCESS; 206 | 207 | /* Wipe clean entries from the cache. */ 208 | g_hash_table_foreach_remove(photoset_ht, free_photoset_ht, NULL); 209 | 210 | if(g_hash_table_size(photoset_ht) == 0) { 211 | g_hash_table_destroy(photoset_ht); 212 | photoset_ht = create_cache(); 213 | } 214 | 215 | if(!g_hash_table_lookup(photoset_ht, "")) { 216 | /* Create an empty photoset container for the photos not in a photoset */ 217 | if(new_cached_photoset(&cps, NULL)) 218 | return FAIL; 219 | 220 | g_hash_table_insert(photoset_ht, strdup(""), cps); 221 | } 222 | 223 | if(!(fps = flickcurl_photosets_getList(fc, NULL))) 224 | return FAIL; 225 | 226 | /* Add the photosets to the cache */ 227 | for(i = 0; fps[i]; i++) { 228 | if(!g_hash_table_lookup(photoset_ht, fps[i]->title)) { 229 | if(new_cached_photoset(&cps, fps[i])) 230 | return FAIL; 231 | g_hash_table_insert(photoset_ht, strdup(cps->ci.name), cps); 232 | } 233 | } 234 | flickcurl_free_photosets(fps); 235 | 236 | last_cleaned = time(NULL); 237 | 238 | return SUCCESS; 239 | } 240 | 241 | static int populate_photoset_cache(cached_photoset *cps, flickcurl_photo **fp) { 242 | int j = 0; 243 | 244 | if(!fp) 245 | return FAIL; 246 | 247 | /* Add photos to photoset cache */ 248 | for(; fp[j]; j++) { 249 | cached_photo *cp; 250 | struct tm tm = {0}; 251 | char *title; 252 | char *id; 253 | 254 | title = fp[j]->fields[PHOTO_FIELD_title].string; 255 | id = fp[j]->id; 256 | 257 | /* Check if dirty version already exists in the database. */ 258 | if((cp = g_hash_table_lookup(cps->photo_ht, title))) { 259 | if(!strcmp(cp->ci.id, id)) 260 | continue; 261 | } 262 | else if((cp = g_hash_table_lookup(cps->photo_ht, id))) { 263 | if(!strcmp(cp->ci.id, id)) 264 | continue; 265 | else 266 | continue; /* TODO: Need to figure out what to do here. */ 267 | } 268 | 269 | if(!(cp = create_cached_photo())) { 270 | return FAIL; 271 | } 272 | 273 | cp->ci.uri = flickcurl_photo_as_source_uri(fp[j], GET_PHOTO_SIZE); 274 | cp->ci.name = strdup(title); 275 | cp->ci.id = strdup(id); 276 | cp->ci.size = PHOTO_SIZE_UNSET; 277 | cp->ci.dirty = CLEAN; 278 | 279 | sscanf(fp[j]->fields[PHOTO_FIELD_dates_taken].string, "%4d-%2d-%2d %2d:%2d:%2d", 280 | &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday), &(tm.tm_hour), &(tm.tm_min), &(tm.tm_sec)); 281 | tm.tm_year = tm.tm_year - 1900; /* Years since 1900 */ 282 | tm.tm_mon--; /* Programmers start with 0... */ 283 | tm.tm_sec--; 284 | tm.tm_min--; 285 | tm.tm_hour--; 286 | cp->ci.time = mktime(&tm); 287 | 288 | /* Can't place empty or duplicate names into the hash table. If this is the case, use the photo id instead. */ 289 | if(cp->ci.name[0] == '\0' || g_hash_table_lookup(cps->photo_ht, cp->ci.name)) 290 | g_hash_table_insert(cps->photo_ht, strdup(cp->ci.id), cp); 291 | else 292 | g_hash_table_insert(cps->photo_ht, strdup(cp->ci.name), cp); 293 | } 294 | 295 | return j; 296 | } 297 | 298 | static inline flickcurl_photo **get_photoset_photos(cached_photoset *cps, int page) { 299 | flickcurl_photo **fp; 300 | 301 | /* Are we searching for photos in a photoset or not? */ 302 | if(!strcmp(cps->ci.id, "")) { /* Get photos NOT in a photoset */ 303 | if(!(fp = flickcurl_photos_getNotInSet(fc, 0, 0, NULL, NULL, 0, PHOTO_EXTRAS, PHOTOS_PER_API_CALL, page))) 304 | return NULL; 305 | } 306 | else { /* Add the photos of the photoset into the cache */ 307 | if(!(fp = flickcurl_photosets_getPhotos(fc, cps->ci.id, PHOTO_EXTRAS, 0, PHOTOS_PER_API_CALL, page))) 308 | return NULL; 309 | } 310 | 311 | return fp; 312 | } 313 | 314 | /* 315 | * The photosets are filled dynamically based on which photosets are loaded 316 | * (it would be a waste to load all flickr info if not needed). 317 | * This method needs to be called in order to fill the photoset cache 318 | * with photo information. 319 | * Assumes there is a lock initiated 320 | */ 321 | static int check_photoset_cache(cached_photoset *cps) { 322 | flickcurl_photo **fp; 323 | unsigned int total_size = 0; 324 | int processed = 0; 325 | int page = 0; 326 | 327 | if(!cps) 328 | return FAIL; 329 | if(!(cps->photo_ht)) 330 | return FAIL; 331 | if(cps->set) 332 | return SUCCESS; 333 | 334 | pthread_rwlock_unlock(&cache_lock); /* Release the read lock and lock for writting */ 335 | pthread_rwlock_wrlock(&cache_lock); 336 | 337 | if(cps->set) 338 | return SUCCESS; 339 | 340 | while((fp = get_photoset_photos(cps, page++))) { 341 | processed = populate_photoset_cache(cps, fp); 342 | flickcurl_free_photos(fp); 343 | if(processed < 0) 344 | return FAIL; 345 | 346 | total_size += (unsigned int)processed; 347 | 348 | if(processed < PHOTOS_PER_API_CALL) 349 | break; 350 | } 351 | 352 | cps->ci.time = time(NULL); 353 | cps->ci.size = total_size; 354 | cps->set = CACHE_SET; 355 | 356 | return SUCCESS; 357 | } 358 | 359 | /* 360 | * Initiates a new flickcurl connection and creates the caching 361 | * mechanism. 362 | */ 363 | int flickr_cache_init() { 364 | if(flickr_init()) 365 | return FAIL; 366 | photoset_ht = create_cache(); 367 | last_cleaned = 0; 368 | pthread_rwlock_init(&cache_lock, NULL); 369 | return SUCCESS; 370 | } 371 | 372 | /* 373 | * Destroys the caches and the flickcurl connection 374 | */ 375 | void flickr_cache_kill() { 376 | /* Wipe existing cache */ 377 | pthread_rwlock_wrlock(&cache_lock); 378 | pthread_rwlock_destroy(&cache_lock); 379 | g_hash_table_foreach_remove(photoset_ht, free_photoset_ht, NULL); 380 | g_hash_table_destroy(photoset_ht); 381 | flickr_kill(); 382 | } 383 | 384 | /** 385 | * ===Accessing Data Methods=== 386 | **/ 387 | 388 | /* Creates an array of strings cooresponding to the users photosets. 389 | * Returns the number of photosets or negative for an error. 390 | * 391 | * IMPORTANT: Make sure you free(names) after you are done! 392 | */ 393 | unsigned int get_photoset_names(char ***names) { 394 | GHashTableIter iter; 395 | char *key; 396 | unsigned int size, i; 397 | 398 | if(!names) 399 | return 0; 400 | 401 | pthread_rwlock_rdlock(&cache_lock); 402 | if(check_cache()) { 403 | pthread_rwlock_unlock(&cache_lock); 404 | return 0; 405 | } 406 | 407 | /* We dont want to add the "" photoset (used for photos without a photoset) into this list */ 408 | size = g_hash_table_size(photoset_ht) - 1; 409 | 410 | if(!(*names = (char **)malloc(sizeof(*names) * size))) { 411 | pthread_rwlock_unlock(&cache_lock); 412 | return 0; 413 | } 414 | 415 | /* Add each photoset to the list. We add the keys since the names may be duplicates/NULL */ 416 | g_hash_table_iter_init(&iter, photoset_ht); 417 | i = 0; 418 | while(g_hash_table_iter_next(&iter, (gpointer)&key, NULL)) { 419 | if(key && strcmp(key, "")) { 420 | (*names)[i] = strdup(key); 421 | i++; 422 | } 423 | } 424 | pthread_rwlock_unlock(&cache_lock); 425 | return i; 426 | } 427 | 428 | /* Creates an array of strings cooresponding to the users photos 429 | * of a certain photoset. Returns the number of photos in the 430 | * photoset or negative signaling an error. 431 | * 432 | * IMPORTANT: Make sure you free(names) (but not the strings within) 433 | * after you are done! 434 | */ 435 | unsigned int get_photo_names(const char *photoset, char ***names) { 436 | GHashTableIter iter; 437 | char *key; 438 | cached_photoset *cps; 439 | cached_photo *cp; 440 | unsigned int i, size; 441 | 442 | if(!names || !photoset) 443 | return 0; 444 | 445 | pthread_rwlock_rdlock(&cache_lock); 446 | if(check_cache()) 447 | goto fail; 448 | 449 | /* If the photoset is not found in the cache, return */ 450 | if(!(cps = g_hash_table_lookup(photoset_ht, photoset))) 451 | goto fail; 452 | 453 | if(check_photoset_cache(cps)) 454 | goto fail; 455 | 456 | size = g_hash_table_size(cps->photo_ht); 457 | 458 | if(!(*names = (char **)malloc(sizeof(*names) * size))) 459 | goto fail; 460 | 461 | /* Add each photo to the list. We add the keys since the names may be duplicates/NULL */ 462 | g_hash_table_iter_init(&iter, cps->photo_ht); 463 | for(i = 0; g_hash_table_iter_next(&iter, (gpointer)&key, (gpointer)&cp); i++) 464 | { 465 | (*names)[i] = strdup(key); 466 | } 467 | 468 | pthread_rwlock_unlock(&cache_lock); 469 | return size; 470 | 471 | fail: pthread_rwlock_unlock(&cache_lock); 472 | return 0; 473 | } 474 | 475 | /* Looks for the photoset specified in the argument. 476 | * Returns pointer to the stored cached_information 477 | * or 0 if not found. 478 | */ 479 | cached_information *photoset_lookup(const char *photoset) { 480 | cached_photoset *cps; 481 | cached_information *ci_copy = NULL; 482 | 483 | pthread_rwlock_rdlock(&cache_lock); 484 | if(check_cache()) 485 | goto fail; 486 | 487 | cps = g_hash_table_lookup(photoset_ht, photoset); 488 | if(cps) 489 | ci_copy = copy_cached_info(&(cps->ci)); 490 | 491 | fail: pthread_rwlock_unlock(&cache_lock); 492 | return ci_copy; 493 | } 494 | 495 | /* 496 | * Internal method to get the cached_photo of 497 | * a particular photo. 498 | * Assumes there is a lock initiated 499 | */ 500 | static cached_photo *get_photo(const char *photoset, const char *photo) { 501 | cached_photoset *cps; 502 | 503 | if(check_cache()) 504 | return NULL; 505 | 506 | if(!(cps = g_hash_table_lookup(photoset_ht, photoset))) 507 | return NULL; 508 | 509 | if(check_photoset_cache(cps)) 510 | return NULL; 511 | 512 | return g_hash_table_lookup(cps->photo_ht, photo); 513 | } 514 | 515 | /* Looks for the photo specified in the arguments. 516 | * Returns pointer to the stored cached_information 517 | * or 0 if not found. 518 | */ 519 | cached_information *photo_lookup(const char *photoset, const char *photo) { 520 | cached_photo *cp; 521 | cached_information *ci_copy = NULL; 522 | 523 | pthread_rwlock_rdlock(&cache_lock); 524 | if((cp = get_photo(photoset, photo))) 525 | ci_copy = copy_cached_info(&(cp->ci)); 526 | pthread_rwlock_unlock(&cache_lock); 527 | 528 | return ci_copy; 529 | } 530 | 531 | /* Returns the URI used to get the actual image of 532 | * picture. 533 | */ 534 | char *get_photo_uri(const char *photoset, const char *photo) { 535 | cached_photo *cp; 536 | char *uri_copy = NULL; 537 | 538 | pthread_rwlock_rdlock(&cache_lock); 539 | if((cp = get_photo(photoset, photo))) { 540 | if(cp->ci.uri) { 541 | uri_copy = strdup(cp->ci.uri); 542 | } 543 | } 544 | pthread_rwlock_unlock(&cache_lock); 545 | 546 | return uri_copy; 547 | } 548 | 549 | /* Renames the photo specified in the args to the 550 | * new name. 551 | */ 552 | int set_photo_name(const char *photoset, const char *photo, const char *newname) { 553 | cached_photo *cp; 554 | 555 | pthread_rwlock_wrlock(&cache_lock); 556 | if(!(cp = get_photo(photoset, photo))) { 557 | pthread_rwlock_unlock(&cache_lock); 558 | return FAIL; 559 | } 560 | 561 | flickcurl_photos_setMeta(fc, cp->ci.id, newname, ""); 562 | last_cleaned = 0; 563 | pthread_rwlock_unlock(&cache_lock); 564 | return SUCCESS; 565 | } 566 | 567 | /* Renames the photoset */ 568 | int set_photoset_name(const char *photoset, const char *newname) { 569 | void *key, *value; 570 | cached_photoset *cps; 571 | int retval = FAIL; 572 | 573 | pthread_rwlock_wrlock(&cache_lock); 574 | 575 | if(g_hash_table_lookup_extended(photoset_ht, photoset, &key, &value)) { 576 | cps = value; 577 | 578 | if(cps->ci.dirty == CLEAN) 579 | if(flickcurl_photosets_editMeta(fc, cps->ci.id, newname, NULL)) 580 | goto fail; 581 | 582 | free(cps->ci.name); 583 | cps->ci.name = strdup(newname); 584 | 585 | g_hash_table_remove(photoset_ht, photoset); 586 | g_hash_table_insert(photoset_ht, strdup(newname), cps); 587 | 588 | free(key); 589 | retval = SUCCESS; 590 | } 591 | 592 | fail: pthread_rwlock_unlock(&cache_lock); 593 | 594 | return retval; 595 | } 596 | 597 | /* Sets the photos size */ 598 | int set_photo_size(const char *photoset, const char *photo, unsigned int newsize) { 599 | cached_photo *cp; 600 | 601 | pthread_rwlock_wrlock(&cache_lock); 602 | if(!(cp = get_photo(photoset, photo))) { 603 | pthread_rwlock_unlock(&cache_lock); 604 | return FAIL; 605 | } 606 | 607 | cp->ci.size = newsize; 608 | pthread_rwlock_unlock(&cache_lock); 609 | 610 | return SUCCESS; 611 | } 612 | 613 | int set_photo_dirty(const char *photoset, const char *photo, unsigned short dirty) { 614 | cached_photo *cp; 615 | 616 | pthread_rwlock_wrlock(&cache_lock); 617 | if(!(cp = get_photo(photoset, photo))) { 618 | pthread_rwlock_unlock(&cache_lock); 619 | return FAIL; 620 | } 621 | 622 | cp->ci.dirty = dirty; 623 | pthread_rwlock_unlock(&cache_lock); 624 | 625 | return SUCCESS; 626 | } 627 | 628 | int get_photo_dirty(const char *photoset, const char *photo) { 629 | cached_photo *cp; 630 | unsigned short dirty; 631 | 632 | pthread_rwlock_rdlock(&cache_lock); 633 | if(!(cp = get_photo(photoset, photo))) { 634 | pthread_rwlock_unlock(&cache_lock); 635 | return FAIL; 636 | } 637 | dirty = cp->ci.dirty; 638 | pthread_rwlock_unlock(&cache_lock); 639 | 640 | return dirty; 641 | } 642 | 643 | int create_empty_photoset(const char *photoset) { 644 | cached_photoset *cps; 645 | int retval = FAIL; 646 | 647 | pthread_rwlock_wrlock(&cache_lock); 648 | 649 | if(g_hash_table_lookup(photoset_ht, photoset)) 650 | goto fail; 651 | 652 | /* The new empty photoset */ 653 | if(!(cps = create_cached_photoset())) 654 | goto fail; 655 | 656 | cps->ci.name = strdup(photoset); 657 | cps->ci.id = strdup(""); 658 | cps->ci.dirty = DIRTY; 659 | cps->ci.time = time(NULL); 660 | cps->set = CACHE_SET; 661 | cps->photo_ht = create_cache(); 662 | 663 | g_hash_table_insert(photoset_ht, strdup(cps->ci.name), cps); 664 | 665 | retval = SUCCESS; 666 | 667 | fail: pthread_rwlock_unlock(&cache_lock); 668 | return retval; 669 | } 670 | 671 | int create_empty_photo(const char *photoset, const char *photo) { 672 | cached_photoset *cps; 673 | cached_photo *cp; 674 | int retval = FAIL; 675 | 676 | pthread_rwlock_wrlock(&cache_lock); 677 | 678 | cps = g_hash_table_lookup(photoset_ht, photoset); 679 | 680 | /* Check to see photoset exists. */ 681 | if(!cps) 682 | goto fail; 683 | 684 | /* Check if photo already exists */ 685 | if(g_hash_table_lookup(cps->photo_ht, photo)) 686 | goto fail; 687 | 688 | /* The new empty photo */ 689 | if(!(cp = create_cached_photo())) 690 | goto fail; 691 | 692 | cp->ci.name = strdup(photo); 693 | cp->ci.id = strdup(""); 694 | cp->ci.dirty = DIRTY; 695 | cp->ci.time = time(NULL); 696 | cp->ci.size = PHOTO_SIZE_UNSET; 697 | 698 | g_hash_table_insert(cps->photo_ht, strdup(cp->ci.name), cp); 699 | 700 | retval = SUCCESS; 701 | 702 | fail: pthread_rwlock_unlock(&cache_lock); 703 | return retval; 704 | } 705 | 706 | int upload_photo(const char *photoset, const char *photo, const char *path) { 707 | flickcurl_upload_status* status; 708 | flickcurl_upload_params params; 709 | cached_photoset *cps; 710 | cached_photo *cp; 711 | int retval = FAIL; 712 | 713 | pthread_rwlock_wrlock(&cache_lock); 714 | 715 | cps = g_hash_table_lookup(photoset_ht, photoset); 716 | 717 | if(!cps) 718 | goto fail; 719 | 720 | if(!(cp = g_hash_table_lookup(cps->photo_ht, photo))) 721 | goto fail; 722 | 723 | memset(¶ms, '\0', sizeof(flickcurl_upload_params)); 724 | params.safety_level = SAFETY_LEVEL; /* default safety */ 725 | params.content_type = CONTENT_TYPE; /* default photo */ 726 | params.photo_file = path; 727 | params.title = cp->ci.name; 728 | 729 | status = flickcurl_photos_upload_params(fc, ¶ms); 730 | 731 | if(status) { 732 | if(cps->ci.dirty == DIRTY) { // if photoset is dirty, create it 733 | char * photosetid = flickcurl_photosets_create(fc, cps->ci.name, NULL, status->photoid, NULL); 734 | 735 | if(photosetid) { 736 | cps->ci.id = photosetid; 737 | cps->ci.dirty = CLEAN; 738 | } 739 | } 740 | else if(strcmp(cps->ci.id, "")) { // if photoset has an id, add new photo to it 741 | flickcurl_photosets_addPhoto(fc, cps->ci.id, status->photoid); 742 | } 743 | 744 | flickcurl_free_upload_status(status); 745 | } 746 | 747 | cp->ci.dirty = CLEAN; 748 | 749 | cps->set = CACHE_UNSET; 750 | 751 | retval = SUCCESS; 752 | 753 | fail: pthread_rwlock_unlock(&cache_lock); 754 | return retval; 755 | } 756 | 757 | int set_photo_photoset(const char *photoset, const char *photo, const char *new_photoset) { 758 | cached_photoset *cps; 759 | cached_photoset *new_cps; 760 | cached_photo *cp; 761 | int retval = FAIL; 762 | 763 | pthread_rwlock_wrlock(&cache_lock); 764 | 765 | cps = g_hash_table_lookup(photoset_ht, photoset); 766 | new_cps = g_hash_table_lookup(photoset_ht, new_photoset); 767 | 768 | if(!cps || !new_cps) 769 | goto fail; 770 | 771 | if(!(cp = g_hash_table_lookup(cps->photo_ht, photo))) 772 | goto fail; 773 | 774 | if(strcmp(cps->ci.id, "")) { 775 | flickcurl_photosets_removePhoto(fc, cps->ci.id, cp->ci.id); 776 | } 777 | 778 | if(strcmp(new_cps->ci.id, "")) { 779 | flickcurl_photosets_addPhoto(fc, new_cps->ci.id, cp->ci.id); 780 | } 781 | 782 | g_hash_table_foreach_remove(cps->photo_ht, free_photo_ht, NULL); 783 | g_hash_table_foreach_remove(new_cps->photo_ht, free_photo_ht, NULL); 784 | 785 | cps->set = CACHE_UNSET; 786 | new_cps->set = CACHE_UNSET; 787 | 788 | retval = SUCCESS; 789 | 790 | fail: pthread_rwlock_unlock(&cache_lock); 791 | return retval; 792 | } 793 | 794 | int remove_photo_from_cache(const char *photoset, const char *photo) { 795 | void *key, *value; 796 | cached_photoset *cps; 797 | cached_photo *cp; 798 | int retval = FAIL; 799 | 800 | pthread_rwlock_wrlock(&cache_lock); 801 | 802 | if(!(cps = g_hash_table_lookup(photoset_ht, photoset))) 803 | goto fail; 804 | 805 | if(g_hash_table_lookup_extended(cps->photo_ht, photo, &key, &value)) { 806 | cp = value; 807 | 808 | if(cp->ci.dirty) { 809 | g_hash_table_remove(cps->photo_ht, photo); 810 | 811 | free(cp->ci.uri); 812 | free(cp->ci.name); 813 | free(cp->ci.id); 814 | free(key); 815 | free(value); 816 | 817 | retval = SUCCESS; 818 | } 819 | } 820 | 821 | fail: pthread_rwlock_unlock(&cache_lock); 822 | return retval; 823 | } 824 | 825 | int photoDelete(char *photo_id) { 826 | return flickcurl_photos_delete(fc, photo_id); 827 | } 828 | 829 | --------------------------------------------------------------------------------