Go to:
Gentoo Home
Documentation
Forums
Lists
Bugs
Planet
Store
Wiki
Get Gentoo!
Gentoo's Bugzilla – Attachment 168818 Details for
Bug 189000
sys-fs/flickrfs-1.3.9 cannot be built -> error: file 'flickrfs' does not exist
Home
|
New
–
[Ex]
|
Browse
|
Search
|
Privacy Policy
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Patch to update 1.3.9 to cvs version.
flickrfs-1.3.9_to_cvs-200810.patch (text/plain), 173.52 KB, created by
Róbert Čerňanský
on 2008-10-17 19:01:11 UTC
(
hide
)
Description:
Patch to update 1.3.9 to cvs version.
Filename:
MIME Type:
Creator:
Róbert Čerňanský
Created:
2008-10-17 19:01:11 UTC
Size:
173.52 KB
patch
obsolete
>diff -Naur flickrfs-1.3.9.orig/COPYING flickrfs-cvs.orig/COPYING >--- flickrfs-1.3.9.orig/COPYING 1970-01-01 01:00:00.000000000 +0100 >+++ flickrfs-cvs.orig/COPYING 2007-03-29 16:44:24.000000000 +0200 >@@ -0,0 +1,340 @@ >+ GNU GENERAL PUBLIC LICENSE >+ Version 2, June 1991 >+ >+ Copyright (C) 1989, 1991 Free Software Foundation, Inc. >+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA >+ Everyone is permitted to copy and distribute verbatim copies >+ of this license document, but changing it is not allowed. >+ >+ Preamble >+ >+ The licenses for most software are designed to take away your >+freedom to share and change it. By contrast, the GNU General Public >+License is intended to guarantee your freedom to share and change free >+software--to make sure the software is free for all its users. This >+General Public License applies to most of the Free Software >+Foundation's software and to any other program whose authors commit to >+using it. (Some other Free Software Foundation software is covered by >+the GNU Library General Public License instead.) You can apply it to >+your programs, too. >+ >+ When we speak of free software, we are referring to freedom, not >+price. Our General Public Licenses are designed to make sure that you >+have the freedom to distribute copies of free software (and charge for >+this service if you wish), that you receive source code or can get it >+if you want it, that you can change the software or use pieces of it >+in new free programs; and that you know you can do these things. >+ >+ To protect your rights, we need to make restrictions that forbid >+anyone to deny you these rights or to ask you to surrender the rights. >+These restrictions translate to certain responsibilities for you if you >+distribute copies of the software, or if you modify it. >+ >+ For example, if you distribute copies of such a program, whether >+gratis or for a fee, you must give the recipients all the rights that >+you have. You must make sure that they, too, receive or can get the >+source code. And you must show them these terms so they know their >+rights. >+ >+ We protect your rights with two steps: (1) copyright the software, and >+(2) offer you this license which gives you legal permission to copy, >+distribute and/or modify the software. >+ >+ Also, for each author's protection and ours, we want to make certain >+that everyone understands that there is no warranty for this free >+software. If the software is modified by someone else and passed on, we >+want its recipients to know that what they have is not the original, so >+that any problems introduced by others will not reflect on the original >+authors' reputations. >+ >+ Finally, any free program is threatened constantly by software >+patents. We wish to avoid the danger that redistributors of a free >+program will individually obtain patent licenses, in effect making the >+program proprietary. To prevent this, we have made it clear that any >+patent must be licensed for everyone's free use or not licensed at all. >+ >+ The precise terms and conditions for copying, distribution and >+modification follow. >+ >+ GNU GENERAL PUBLIC LICENSE >+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION >+ >+ 0. This License applies to any program or other work which contains >+a notice placed by the copyright holder saying it may be distributed >+under the terms of this General Public License. The "Program", below, >+refers to any such program or work, and a "work based on the Program" >+means either the Program or any derivative work under copyright law: >+that is to say, a work containing the Program or a portion of it, >+either verbatim or with modifications and/or translated into another >+language. (Hereinafter, translation is included without limitation in >+the term "modification".) Each licensee is addressed as "you". >+ >+Activities other than copying, distribution and modification are not >+covered by this License; they are outside its scope. The act of >+running the Program is not restricted, and the output from the Program >+is covered only if its contents constitute a work based on the >+Program (independent of having been made by running the Program). >+Whether that is true depends on what the Program does. >+ >+ 1. You may copy and distribute verbatim copies of the Program's >+source code as you receive it, in any medium, provided that you >+conspicuously and appropriately publish on each copy an appropriate >+copyright notice and disclaimer of warranty; keep intact all the >+notices that refer to this License and to the absence of any warranty; >+and give any other recipients of the Program a copy of this License >+along with the Program. >+ >+You may charge a fee for the physical act of transferring a copy, and >+you may at your option offer warranty protection in exchange for a fee. >+ >+ 2. You may modify your copy or copies of the Program or any portion >+of it, thus forming a work based on the Program, and copy and >+distribute such modifications or work under the terms of Section 1 >+above, provided that you also meet all of these conditions: >+ >+ a) You must cause the modified files to carry prominent notices >+ stating that you changed the files and the date of any change. >+ >+ b) You must cause any work that you distribute or publish, that in >+ whole or in part contains or is derived from the Program or any >+ part thereof, to be licensed as a whole at no charge to all third >+ parties under the terms of this License. >+ >+ c) If the modified program normally reads commands interactively >+ when run, you must cause it, when started running for such >+ interactive use in the most ordinary way, to print or display an >+ announcement including an appropriate copyright notice and a >+ notice that there is no warranty (or else, saying that you provide >+ a warranty) and that users may redistribute the program under >+ these conditions, and telling the user how to view a copy of this >+ License. (Exception: if the Program itself is interactive but >+ does not normally print such an announcement, your work based on >+ the Program is not required to print an announcement.) >+ >+These requirements apply to the modified work as a whole. If >+identifiable sections of that work are not derived from the Program, >+and can be reasonably considered independent and separate works in >+themselves, then this License, and its terms, do not apply to those >+sections when you distribute them as separate works. But when you >+distribute the same sections as part of a whole which is a work based >+on the Program, the distribution of the whole must be on the terms of >+this License, whose permissions for other licensees extend to the >+entire whole, and thus to each and every part regardless of who wrote it. >+ >+Thus, it is not the intent of this section to claim rights or contest >+your rights to work written entirely by you; rather, the intent is to >+exercise the right to control the distribution of derivative or >+collective works based on the Program. >+ >+In addition, mere aggregation of another work not based on the Program >+with the Program (or with a work based on the Program) on a volume of >+a storage or distribution medium does not bring the other work under >+the scope of this License. >+ >+ 3. You may copy and distribute the Program (or a work based on it, >+under Section 2) in object code or executable form under the terms of >+Sections 1 and 2 above provided that you also do one of the following: >+ >+ a) Accompany it with the complete corresponding machine-readable >+ source code, which must be distributed under the terms of Sections >+ 1 and 2 above on a medium customarily used for software interchange; or, >+ >+ b) Accompany it with a written offer, valid for at least three >+ years, to give any third party, for a charge no more than your >+ cost of physically performing source distribution, a complete >+ machine-readable copy of the corresponding source code, to be >+ distributed under the terms of Sections 1 and 2 above on a medium >+ customarily used for software interchange; or, >+ >+ c) Accompany it with the information you received as to the offer >+ to distribute corresponding source code. (This alternative is >+ allowed only for noncommercial distribution and only if you >+ received the program in object code or executable form with such >+ an offer, in accord with Subsection b above.) >+ >+The source code for a work means the preferred form of the work for >+making modifications to it. For an executable work, complete source >+code means all the source code for all modules it contains, plus any >+associated interface definition files, plus the scripts used to >+control compilation and installation of the executable. However, as a >+special exception, the source code distributed need not include >+anything that is normally distributed (in either source or binary >+form) with the major components (compiler, kernel, and so on) of the >+operating system on which the executable runs, unless that component >+itself accompanies the executable. >+ >+If distribution of executable or object code is made by offering >+access to copy from a designated place, then offering equivalent >+access to copy the source code from the same place counts as >+distribution of the source code, even though third parties are not >+compelled to copy the source along with the object code. >+ >+ 4. You may not copy, modify, sublicense, or distribute the Program >+except as expressly provided under this License. Any attempt >+otherwise to copy, modify, sublicense or distribute the Program is >+void, and will automatically terminate your rights under this License. >+However, parties who have received copies, or rights, from you under >+this License will not have their licenses terminated so long as such >+parties remain in full compliance. >+ >+ 5. You are not required to accept this License, since you have not >+signed it. However, nothing else grants you permission to modify or >+distribute the Program or its derivative works. These actions are >+prohibited by law if you do not accept this License. Therefore, by >+modifying or distributing the Program (or any work based on the >+Program), you indicate your acceptance of this License to do so, and >+all its terms and conditions for copying, distributing or modifying >+the Program or works based on it. >+ >+ 6. Each time you redistribute the Program (or any work based on the >+Program), the recipient automatically receives a license from the >+original licensor to copy, distribute or modify the Program subject to >+these terms and conditions. You may not impose any further >+restrictions on the recipients' exercise of the rights granted herein. >+You are not responsible for enforcing compliance by third parties to >+this License. >+ >+ 7. If, as a consequence of a court judgment or allegation of patent >+infringement or for any other reason (not limited to patent issues), >+conditions are imposed on you (whether by court order, agreement or >+otherwise) that contradict the conditions of this License, they do not >+excuse you from the conditions of this License. If you cannot >+distribute so as to satisfy simultaneously your obligations under this >+License and any other pertinent obligations, then as a consequence you >+may not distribute the Program at all. For example, if a patent >+license would not permit royalty-free redistribution of the Program by >+all those who receive copies directly or indirectly through you, then >+the only way you could satisfy both it and this License would be to >+refrain entirely from distribution of the Program. >+ >+If any portion of this section is held invalid or unenforceable under >+any particular circumstance, the balance of the section is intended to >+apply and the section as a whole is intended to apply in other >+circumstances. >+ >+It is not the purpose of this section to induce you to infringe any >+patents or other property right claims or to contest validity of any >+such claims; this section has the sole purpose of protecting the >+integrity of the free software distribution system, which is >+implemented by public license practices. Many people have made >+generous contributions to the wide range of software distributed >+through that system in reliance on consistent application of that >+system; it is up to the author/donor to decide if he or she is willing >+to distribute software through any other system and a licensee cannot >+impose that choice. >+ >+This section is intended to make thoroughly clear what is believed to >+be a consequence of the rest of this License. >+ >+ 8. If the distribution and/or use of the Program is restricted in >+certain countries either by patents or by copyrighted interfaces, the >+original copyright holder who places the Program under this License >+may add an explicit geographical distribution limitation excluding >+those countries, so that distribution is permitted only in or among >+countries not thus excluded. In such case, this License incorporates >+the limitation as if written in the body of this License. >+ >+ 9. The Free Software Foundation may publish revised and/or new versions >+of the General Public License from time to time. Such new versions will >+be similar in spirit to the present version, but may differ in detail to >+address new problems or concerns. >+ >+Each version is given a distinguishing version number. If the Program >+specifies a version number of this License which applies to it and "any >+later version", you have the option of following the terms and conditions >+either of that version or of any later version published by the Free >+Software Foundation. If the Program does not specify a version number of >+this License, you may choose any version ever published by the Free Software >+Foundation. >+ >+ 10. If you wish to incorporate parts of the Program into other free >+programs whose distribution conditions are different, write to the author >+to ask for permission. For software which is copyrighted by the Free >+Software Foundation, write to the Free Software Foundation; we sometimes >+make exceptions for this. Our decision will be guided by the two goals >+of preserving the free status of all derivatives of our free software and >+of promoting the sharing and reuse of software generally. >+ >+ NO WARRANTY >+ >+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY >+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN >+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES >+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED >+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF >+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS >+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE >+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, >+REPAIR OR CORRECTION. >+ >+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING >+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR >+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, >+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING >+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED >+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY >+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER >+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE >+POSSIBILITY OF SUCH DAMAGES. >+ >+ END OF TERMS AND CONDITIONS >+ >+ How to Apply These Terms to Your New Programs >+ >+ If you develop a new program, and you want it to be of the greatest >+possible use to the public, the best way to achieve this is to make it >+free software which everyone can redistribute and change under these terms. >+ >+ To do so, attach the following notices to the program. It is safest >+to attach them to the start of each source file to most effectively >+convey the exclusion of warranty; and each file should have at least >+the "copyright" line and a pointer to where the full notice is found. >+ >+ <one line to give the program's name and a brief idea of what it does.> >+ Copyright (C) <year> <name of author> >+ >+ This program is free software; you can redistribute it and/or modify >+ it under the terms of the GNU General Public License as published by >+ the Free Software Foundation; either version 2 of the License, or >+ (at your option) any later version. >+ >+ This program is distributed in the hope that it will be useful, >+ but WITHOUT ANY WARRANTY; without even the implied warranty of >+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+ GNU General Public License for more details. >+ >+ You should have received a copy of the GNU General Public License >+ along with this program; if not, write to the Free Software >+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA >+ >+ >+Also add information on how to contact you by electronic and paper mail. >+ >+If the program is interactive, make it output a short notice like this >+when it starts in an interactive mode: >+ >+ Gnomovision version 69, Copyright (C) year name of author >+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. >+ This is free software, and you are welcome to redistribute it >+ under certain conditions; type `show c' for details. >+ >+The hypothetical commands `show w' and `show c' should show the appropriate >+parts of the General Public License. Of course, the commands you use may >+be called something other than `show w' and `show c'; they could even be >+mouse-clicks or menu items--whatever suits your program. >+ >+You should also get your employer (if you work as a programmer) or your >+school, if any, to sign a "copyright disclaimer" for the program, if >+necessary. Here is a sample; alter the names: >+ >+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program >+ `Gnomovision' (which makes passes at compilers) written by James Hacker. >+ >+ <signature of Ty Coon>, 1 April 1989 >+ Ty Coon, President of Vice >+ >+This General Public License does not permit incorporating your program into >+proprietary programs. If your program is a subroutine library, you may >+consider it more useful to permit linking proprietary applications with the >+library. If this is what you want to do, use the GNU Library General >+Public License instead of this License. >diff -Naur flickrfs-1.3.9.orig/flickrapi.py flickrfs-cvs.orig/flickrapi.py >--- flickrfs-1.3.9.orig/flickrapi.py 2007-02-04 06:08:09.000000000 +0100 >+++ flickrfs-cvs.orig/flickrapi.py 1970-01-01 01:00:00.000000000 +0100 >@@ -1,531 +0,0 @@ >-#!/usr/bin/python >-# >-# Flickr API implementation >-# >-# Inspired largely by Michele Campeotto's flickrclient and Aaron Swartz' >-# xmltramp... but I wanted to get a better idea of how python worked in >-# those regards, so I mostly worked those components out for myself. >-# >-# http://micampe.it/things/flickrclient >-# http://www.aaronsw.com/2002/xmltramp/ >-# >-# Release 1: initial release >-# Release 2: added upload functionality >-# Release 3: code cleanup, convert to doc strings >-# Release 4: better permission support >-# Release 5: converted into fuller-featured "flickrapi" >-# Release 6: fix upload sig bug (thanks Deepak Jois), encode test output >-# >-# Copyright 2005 Brian "Beej Jorgensen" Hall beej@beej.us >-# >-# This work is licensed under the Creative Commons >-# Attribution License. To view a copy of this license, >-# visit http://creativecommons.org/licenses/by/2.5/ or send >-# a letter to Creative Commons, 543 Howard Street, 5th >-# Floor, San Francisco, California, 94105, USA. >-# >-# This license says that I must be credited for any derivative works. >-# You do not need to credit me to simply use the FlickrAPI classes in >-# your Python scripts-- you only need to credit me if you're taking this >-# FlickrAPI class and transforming it into your own. >-# >-# Previous versions of this API were granted to the public domain. >-# You're free to use those as you please. >-# >-# Beej Jorgensen, August 2005 >-# beej@beej.us >-#------------------------------------------ >-# Modified(debugged portions + added functionality) by Manish Rai Jain <manishrjain@gmail.com> >-# If you are interested in finding out exactly >-# what portions I have modified, just search for 'manish'. Each change is >-# tagged with my name for easy locate. >-# Additional modifications similarly tagged by R. David Murray <rdmurray@bitdance.com> >- >-import sys >-import md5 >-import string >-import urllib >-import httplib >-import os.path >-import xml.dom.minidom >-import urllib2 >-import socket >-DEBUG = 0 >-######################################################################## >-# XML functionality >-######################################################################## >- >-#----------------------------------------------------------------------- >-class XMLNode: >- """XMLNode -- generic class for holding an XML node >- xmlStr = \"\"\"<xml foo="32"> >- <name bar="10">Name0</name> >- <name bar="11" baz="12">Name1</name> >- </xml>\"\"\" >- >- f = XMLNode.parseXML(xmlStr) >- >- print f.elementName >- print f['foo'] >- print f.name >- print f.name[0].elementName >- print f.name[0]["bar"] >- print f.name[0].elementText >- print f.name[1].elementName >- print f.name[1]["bar"] >- print f.name[1]["baz"] >- >- """ >- >- def __init__(self): >- """Construct an empty XML node.""" >- self.elementName="" >- self.elementText="" >- self.attrib={} >- self.xml="" >- >- def __setitem__(self, key, item): >- """Store a node's attribute in the attrib hash.""" >- self.attrib[key] = item >- >- def __getitem__(self, key): >- """Retrieve a node's attribute from the attrib hash.""" >- return self.attrib[key] >- >- # Modified here: add a couple of methods to make it even easier to handle errors. >- # Mod by: R. David Murray <rdmurray@bitdance.com> >- def __nonzero__(self): >- if self['stat'] == "fail": return False >- return True >- >- def get_errortext(self): >- if self: return '' >- return "%s: error %s: %s\n" % (self.elementName, \ >- self.err[0]['code'], self.err[0]['msg']) >- errormsg = property(get_errortext) >- >- #----------------------------------------------------------------------- >- #@classmethod >- def parseXML(cls, xmlStr="", storeXML=False): >- """Convert an XML string into a nice instance tree of XMLNodes. >- >- xmlStr -- the XML to parse >- storeXML -- if True, stores the XML string in the root XMLNode.xml >- >- """ >- >- def __parseXMLElement(element, thisNode): >- """Recursive call to process this XMLNode.""" >- thisNode.elementName = element.nodeName >- >- #print element.nodeName >- >- # add element attributes as attributes to this node >- for i in range(element.attributes.length): >- an = element.attributes.item(i) >- thisNode[an.name] = an.nodeValue >- >- for a in element.childNodes: >- if a.nodeType == xml.dom.Node.ELEMENT_NODE: >- >- child = XMLNode() >- try: >- list = getattr(thisNode, a.nodeName) >- except AttributeError: >- setattr(thisNode, a.nodeName, []) >- >- # add the child node as an attrib to this node >- list = getattr(thisNode, a.nodeName); >- #print "appending child: %s to %s" % (a.nodeName, thisNode.elementName) >- list.append(child); >- >- __parseXMLElement(a, child) >- >- elif a.nodeType == xml.dom.Node.TEXT_NODE: >- thisNode.elementText += a.nodeValue >- >- return thisNode >- dom = xml.dom.minidom.parseString(xmlStr) >- >- # get the root >- rootNode = XMLNode() >- if storeXML: rootNode.xml = xmlStr >- >- return __parseXMLElement(dom.firstChild, rootNode) >- >-######################################################################## >-# Flickr functionality >-######################################################################## >- >-#----------------------------------------------------------------------- >-class FlickrAPI: >- """Encapsulated flickr functionality. >- >- Example usage: >- >- flickr = FlickrAPI(flickrAPIKey, flickrSecret) >- rsp = flickr.auth_checkToken(api_key=flickrAPIKey, auth_token=token) >- >- """ >- flickrHost = "flickr.com" >- flickrRESTForm = "/services/rest/" >- flickrAuthForm = "/services/auth/" >- flickrUploadForm = "/services/upload/" >- #------------------------------------------------------------------- >- def __init__(self, apiKey, secret): >- """Construct a new FlickrAPI instance for a given API key and secret.""" >- self.apiKey = apiKey >- self.secret = secret >- >- self.__handlerCache={} >- socket.setdefaulttimeout(10) >- #------------------------------------------------------------------- >- def __sign(self, data): >- """Calculate the flickr signature for a set of params. >- >- data -- a hash of all the params and values to be hashed, e.g. >- {"api_key":"AAAA", "auth_token":"TTTT"} >- >- """ >- dataName = "" >- if self.secret is not None: >- dataName = self.secret >- #print data >- keys = data.keys() >- keys.sort() >- >- for a in keys: >- if data[a] is not None: >- dataName += "%s%s" % (a, data[a]) >- #print 'dataName:', dataName >- hash = md5.new() >- hash.update(dataName) >- return hash.hexdigest() >- >- #------------------------------------------------------------------- >- def __getattr__(self, method, **arg): >- """Handle all the flickr API calls. >- >- This is Michele Campeotto's cleverness, wherein he writes a >- general handler for methods not defined, and assumes they are >- flickr methods. He then converts them to a form to be passed as >- the method= parameter, and goes from there. >- >- http://micampe.it/things/flickrclient >- >- My variant is the same basic thing, except it tracks if it has >- already created a handler for a specific call or not. >- >- example usage: >- >- flickr.auth_getFrob(api_key="AAAAAA") >- rsp = flickr.favorites_getList(api_key=flickrAPIKey, \\ >- auth_token=token) >- >- """ >- >- if not self.__handlerCache.has_key(method): >- def handler(_self = self, _method = method, **arg): >- _method = "flickr." + _method.replace("_", ".") >- url = "http://" + FlickrAPI.flickrHost + \ >- FlickrAPI.flickrRESTForm >- arg["method"] = _method >- # Modified here: use default api_key and auth_token if not supplied >- # Mod by: R. David Murray <rdmurray@bitdance.com> >- >- #API Key is used in all the methods. So, instead of specifying it as >- #parameter in calling function, we can append it over here by default. >- #Token eq. to Authentication is not required for ALL methods. So, >- #better specify when needed. -Manish >- >- if not 'api_key' in arg: arg["api_key"] = _self.apiKey >-# if not 'auth_token' in arg and hasattr(_self, 'token'): arg['auth_token'] = _self.token >- >- postData = str(urllib.urlencode(arg)) + "&api_sig=" + \ >- str(_self.__sign(arg)) >- if DEBUG: >- print "--url---------------------------------------------" >- print url >- print "--postData----------------------------------------" >- print postData >- data = '<rsp stat="ok"></rsp>' >- req = urllib2.Request(url, postData) >-# socket.defaulttimetout(60) >- f = urllib2.urlopen(req) >- data = f.read() >- if DEBUG: >- print "--response----------------------------------------" >- print data >- f.close() >- tempNode = XMLNode() >- # Modified here: added XMLNode() as the 1st argument >- # Mod by: Manish Rai Jain <manishrjain@gmail.com> >- return XMLNode.parseXML(XMLNode(),xmlStr=data, storeXML=True) >- >- self.__handlerCache[method] = handler; >- >- return self.__handlerCache[method] >- >- #------------------------------------------------------------------- >- def __getAuthURL(self, perms, frob): >- """Return the authorization URL to get a token. >- >- This is the URL the app will launch a browser toward if it >- needs a new token. >- >- perms -- "read", "write", or "delete" >- frob -- picked up from an earlier call to FlickrAPI.auth_getFrob() >- >- """ >- >- data = {"api_key": self.apiKey, "frob": frob, "perms": perms} >- data["api_sig"] = self.__sign(data) >- return "http://%s%s?%s" % (FlickrAPI.flickrHost, \ >- FlickrAPI.flickrAuthForm, urllib.urlencode(data)) >- >- #------------------------------------------------------------------- >- def upload(self, filename, jpegData="", **arg): >- """Upload a file to flickr. >- >- jpegData -- send buffered data read from file instead of filename >- >- Be extra careful you spell the parameters correctly, or you will >- get a rather cryptic "Invalid Signature" error on the upload! >- >- Supported parameters: >- >- api_key >- auth_token -- documentation mistakenly calls this "auth_hash" >- title >- description >- tags -- space-delimited list of tags, "tag1 tag2 tag3" >- is_public -- "1" or "0" >- is_friend -- "1" or "0" >- is_family -- "1" or "0" >- >- """ >- >- # verify key names >- for a in arg.keys(): >- if a != "api_key" and a != "auth_token" and a != "title" and \ >- a != "description" and a != "tags" and a != "is_public" and \ >- a != "is_friend" and a != "is_family": >- >- sys.stderr.write("FlickrAPI: warning: unknown parameter " \ >- "\"%s\" sent to FlickrAPI.upload\n" % (a)) >- >- # Modified here: use default api_key and auth_token if not supplied >- # Mod by: R. David Murray <rdmurray@bitdance.com> >- if not 'api_key' in arg: arg["api_key"] = self.apiKey >- if not 'auth_token' in arg: arg['auth_token'] = self.token >- arg["api_sig"] = self.__sign(arg) >- url = "http://" + FlickrAPI.flickrHost + FlickrAPI.flickrUploadForm >- >- # construct POST data >-# boundary = "===beej=jorgensen==========7d45e178b0434" >- import mimetools >- boundary = mimetools.choose_boundary() >- Hdr = "multipart/form-data; boundary=%s" % boundary >- body = "" >- >- # required params >- for a in ('api_key', 'auth_token', 'api_sig'): >- body += "--%s\r\n" % (boundary) >- body += "Content-Disposition: form-data; name=\""+a+"\"\r\n\r\n" >- body += "%s\r\n" % (arg[a]) >- >- # optional params >- for a in ('title', 'description', 'tags', 'is_public', \ >- 'is_friend', 'is_family'): >- >- if arg.has_key(a): >- body += "--%s\r\n" % (boundary) >- body += "Content-Disposition: form-data; name=\""+a+"\"\r\n\r\n" >- body += "%s\r\n" % (arg[a]) >- >- body += "--%s\r\n" % (boundary) >- body += "Content-Disposition: form-data; name=\"photo\";" >- body += " filename=\"%s\"\r\n" % filename >- body += "Content-Type: image/jpeg\r\n\r\n" >- >- #print body >- >- try: >- # Added by Manish <manishrjain@gmail.com> for allowing upload >- # by sending buffer instead of specifying filename >- if jpegData=="": >- fp = file(filename, "rb") >- jpegData = fp.read() >- fp.close() >- >- postData = body.encode("utf_8") + jpegData + "\r\n" + \ >- ("--%s--" % (boundary)).encode("utf_8") >- >- >- # Modified by Manish <manishrjain@gmail.com> for allowing >- # upload through proxy >- >- request = urllib2.Request(url) >- request.add_data(postData) >- request.add_header("Content-Type", Hdr) >- response = urllib2.urlopen(request) >- rspXML = response.read() >- # Modified by Manish Rai Jain <manishrjain@gmail.com> >- return XMLNode.parseXML(XMLNode(), xmlStr=rspXML) >- except IOError: >- return None >- >- >- >- #----------------------------------------------------------------------- >- #@classmethod >- def testFailure(cls, rsp, exit=True): >- """Exit app if the rsp XMLNode indicates failure.""" >- if rsp['stat'] == "fail": >- sys.stderr.write("%s: error %s: %s\n" % (rsp.elementName, \ >- rsp.err[0]['code'], rsp.err[0]['msg'])) >- if exit: sys.exit(1) >- >- #----------------------------------------------------------------------- >- def __getCachedTokenPath(self): >- """Return the directory holding the app data.""" >- return os.path.expanduser("~/.flickr/%s" % (self.apiKey)) >- >- #----------------------------------------------------------------------- >- def __getCachedTokenFilename(self): >- """Return the full pathname of the cached token file.""" >- return "%s/auth.xml" % (self.__getCachedTokenPath()) >- >- #----------------------------------------------------------------------- >- def __getCachedToken(self): >- """Read and return a cached token, or None if not found. >- >- The token is read from the cached token file, which is basically the >- entire RSP response containing the auth element. >- """ >- >- try: >- f = file(self.__getCachedTokenFilename(), "r") >- data = f.read() >- f.close() >- # Modified here: added XMLNode() as the 1st argument >- # Mod by: Manish Rai Jain <manishrjain@gmail.com> >- rsp = XMLNode.parseXML(XMLNode(), data) >- >- return rsp.auth[0].token[0].elementText >- >- except IOError: >- return None >- >- #----------------------------------------------------------------------- >- def __setCachedToken(self, xml): >- """Cache a token for later use. >- >- The cached tag is stored by simply saving the entire RSP response >- containing the auth element. >- >- """ >- >- path = self.__getCachedTokenPath() >- if not os.path.exists(path): >- os.makedirs(path) >- >- f = file(self.__getCachedTokenFilename(), "w") >- f.write(xml) >- f.close() >- >- >- #----------------------------------------------------------------------- >- def getToken(self, perms="write", browser="lynx"): >- """Get a token either from the cache, or make a new one from the >- frob. >- >- This first attempts to find a token in the user's token cache on >- disk. >- >- If that fails (or if the token is no longer valid based on >- flickr.auth.checkToken) a new frob is acquired. The frob is >- validated by having the user log into flickr (with lynx), and >- subsequently a valid token is retrieved. >- >- The newly minted token is then cached locally for the next run. >- >- perms--"read", "write", or "delete" >- browser--whatever browser should be used in the system() call >- >- """ >- >- # see if we have a saved token >- token = self.__getCachedToken() >- >- # see if it's valid >- if token != None: >- rsp = self.auth_checkToken(api_key=self.apiKey, auth_token=token) >- if rsp['stat'] != "ok": >- token = None >- else: >- # see if we have enough permissions >- tokenPerms = rsp.auth[0].perms[0].elementText >- if tokenPerms == "read" and perms != "read": token = None >- elif tokenPerms == "write" and perms == "delete": token = None >- >- # get a new token if we need one >- if token == None: >- # get the frob >- rsp = self.auth_getFrob(api_key=self.apiKey) >- self.testFailure(rsp) >- frob = rsp.frob[0].elementText >- >- # validate online >- os.system("%s '%s'" % (browser, self.__getAuthURL(perms, frob))) >- # get a token >- rsp = self.auth_getToken(api_key=self.apiKey, frob=frob) >- self.testFailure(rsp) >- token = rsp.auth[0].token[0].elementText >- >- # store the auth info for next time >- self.__setCachedToken(rsp.xml) >- >- # Modified here: save the auth token to use as a default >- # Mod by: R. David Murray <rdmurray@bitdance.com> >- self.token = token >- return token >- >- >-######################################################################## >-# App functionality >-######################################################################## >- >-def main(argv): >- # flickr auth information: >- flickrAPIKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # API key >- flickrSecret = "yyyyyyyyyyyyyyyy" # shared "secret" >- >- # make a new FlickrAPI instance >- fapi = FlickrAPI(flickrAPIKey, flickrSecret) >- >- # do the whole whatever-it-takes to get a valid token: >- token = fapi.getToken(browser="/opt/firefox/firefox") >- >- # get my favorites >- rsp = fapi.favorites_getList(api_key=flickrAPIKey,auth_token=token) >- fapi.testFailure(rsp) >- >- # and print them >- for a in rsp.photos[0].photo: >- print "%10s: %s" % (a['id'], a['title'].encode("ascii", "replace")) >- >- # upload the file foo.jpg >- #rsp = fapi.upload("foo.jpg", api_key=flickrAPIKey, auth_token=token, \ >- # title="This is the title", description="This is the description", \ >- # tags="tag1 tag2 tag3", is_public="1") >- #if rsp == None: >- # sys.stderr.write("can't find file\n") >- #else: >- # fapi.testFailure(rsp) >- >- return 0 >- >-# run the main if we're not being imported: >-if __name__ == "__main__": sys.exit(main(sys.argv)) >- >diff -Naur flickrfs-1.3.9.orig/flickrfs/flickrapi.py flickrfs-cvs.orig/flickrfs/flickrapi.py >--- flickrfs-1.3.9.orig/flickrfs/flickrapi.py 1970-01-01 01:00:00.000000000 +0100 >+++ flickrfs-cvs.orig/flickrfs/flickrapi.py 2007-06-15 19:04:00.000000000 +0200 >@@ -0,0 +1,549 @@ >+# Flickr API implementation >+# >+# Inspired largely by Michele Campeotto's flickrclient and Aaron Swartz' >+# xmltramp... but I wanted to get a better idea of how python worked in >+# those regards, so I mostly worked those components out for myself. >+# >+# http://micampe.it/things/flickrclient >+# http://www.aaronsw.com/2002/xmltramp/ >+# >+# Release 1: initial release >+# Release 2: added upload functionality >+# Release 3: code cleanup, convert to doc strings >+# Release 4: better permission support >+# Release 5: converted into fuller-featured "flickrapi" >+# Release 6: fix upload sig bug (thanks Deepak Jois), encode test output >+# Release 7: fix path construction, Manish Rai Jain's improvements, exceptions >+# Release 8: change API endpoint to "api.flickr.com" >+# Release 9: change to MIT license >+# Release 10: fix horrid \r\n bug on final boundary >+# Release 11: break out validateFrob() for subclassing >+# >+# Work by (or inspired by) Manish Rai Jain <manishrjain@gmail.com>: >+# >+# improved error reporting, proper multipart MIME boundary creation, >+# use of urllib2 to allow uploads through a proxy, upload accepts >+# raw data as well as a filename >+# >+# Copyright (c) 2007 Brian "Beej Jorgensen" Hall >+# >+# Permission is hereby granted, free of charge, to any person obtaining >+# a copy of this software and associated documentation files (the >+# "Software"), to deal in the Software without restriction, including >+# without limitation the rights to use, copy, modify, merge, publish, >+# distribute, sublicense, and/or sell copies of the Software, and to >+# permit persons to whom the Software is furnished to do so, subject to >+# the following conditions: >+# >+# The above copyright notice and this permission notice shall be >+# included in all copies or substantial portions of the Software. >+# >+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, >+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF >+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. >+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY >+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, >+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE >+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. >+# >+# Certain previous versions of this API were granted to the public >+# domain. You're free to use those as you please. >+# >+# Beej Jorgensen, Maintainer, 19-Jan-2007 >+# beej@beej.us >+# >+#------------------------------------------ >+# Modified(debugged portions + added functionality) by Manish Rai Jain <manishrjain@gmail.com> >+# If you are interested in finding out exactly >+# what portions I have modified, just search for 'manish'. Each change is >+# tagged with my name for easy locate. >+# Additional modifications similarly tagged by R. David Murray <rdmurray@bitdance.com> >+ >+import sys >+import md5 >+import string >+import urllib >+import httplib >+import os.path >+import xml.dom.minidom >+import urllib2 >+import socket >+DEBUG = 0 >+######################################################################## >+# XML functionality >+######################################################################## >+ >+#----------------------------------------------------------------------- >+class XMLNode: >+ """XMLNode -- generic class for holding an XML node >+ xmlStr = \"\"\"<xml foo="32"> >+ <name bar="10">Name0</name> >+ <name bar="11" baz="12">Name1</name> >+ </xml>\"\"\" >+ >+ f = XMLNode.parseXML(xmlStr) >+ >+ print f.elementName >+ print f['foo'] >+ print f.name >+ print f.name[0].elementName >+ print f.name[0]["bar"] >+ print f.name[0].elementText >+ print f.name[1].elementName >+ print f.name[1]["bar"] >+ print f.name[1]["baz"] >+ >+ """ >+ >+ def __init__(self): >+ """Construct an empty XML node.""" >+ self.elementName="" >+ self.elementText="" >+ self.attrib={} >+ self.xml="" >+ >+ def __setitem__(self, key, item): >+ """Store a node's attribute in the attrib hash.""" >+ self.attrib[key] = item >+ >+ def __getitem__(self, key): >+ """Retrieve a node's attribute from the attrib hash.""" >+ return self.attrib[key] >+ >+ # Modified here: add a couple of methods to make it even easier to handle errors. >+ # Mod by: R. David Murray <rdmurray@bitdance.com> >+ def __nonzero__(self): >+ if self['stat'] == "fail": return False >+ return True >+ >+ def get_errortext(self): >+ if self: return '' >+ return "%s: error %s: %s\n" % (self.elementName, \ >+ self.err[0]['code'], self.err[0]['msg']) >+ errormsg = property(get_errortext) >+ >+ #----------------------------------------------------------------------- >+ #@classmethod >+ def parseXML(cls, xmlStr="", storeXML=False): >+ """Convert an XML string into a nice instance tree of XMLNodes. >+ >+ xmlStr -- the XML to parse >+ storeXML -- if True, stores the XML string in the root XMLNode.xml >+ >+ """ >+ >+ def __parseXMLElement(element, thisNode): >+ """Recursive call to process this XMLNode.""" >+ thisNode.elementName = element.nodeName >+ >+ #print element.nodeName >+ >+ # add element attributes as attributes to this node >+ for i in range(element.attributes.length): >+ an = element.attributes.item(i) >+ thisNode[an.name] = an.nodeValue >+ >+ for a in element.childNodes: >+ if a.nodeType == xml.dom.Node.ELEMENT_NODE: >+ >+ child = XMLNode() >+ try: >+ list = getattr(thisNode, a.nodeName) >+ except AttributeError: >+ setattr(thisNode, a.nodeName, []) >+ >+ # add the child node as an attrib to this node >+ list = getattr(thisNode, a.nodeName); >+ #print "appending child: %s to %s" % (a.nodeName, thisNode.elementName) >+ list.append(child); >+ >+ __parseXMLElement(a, child) >+ >+ elif a.nodeType == xml.dom.Node.TEXT_NODE: >+ thisNode.elementText += a.nodeValue >+ >+ return thisNode >+ dom = xml.dom.minidom.parseString(xmlStr) >+ >+ # get the root >+ rootNode = XMLNode() >+ if storeXML: rootNode.xml = xmlStr >+ >+ return __parseXMLElement(dom.firstChild, rootNode) >+ >+######################################################################## >+# Flickr functionality >+######################################################################## >+ >+#----------------------------------------------------------------------- >+class FlickrAPI: >+ """Encapsulated flickr functionality. >+ >+ Example usage: >+ >+ flickr = FlickrAPI(flickrAPIKey, flickrSecret) >+ rsp = flickr.auth_checkToken(api_key=flickrAPIKey, auth_token=token) >+ >+ """ >+ flickrHost = "flickr.com" >+ flickrRESTForm = "/services/rest/" >+ flickrAuthForm = "/services/auth/" >+ flickrUploadForm = "/services/upload/" >+ #------------------------------------------------------------------- >+ def __init__(self, apiKey, secret): >+ """Construct a new FlickrAPI instance for a given API key and secret.""" >+ self.apiKey = apiKey >+ self.secret = secret >+ >+ self.__handlerCache={} >+ socket.setdefaulttimeout(10) >+ #------------------------------------------------------------------- >+ def __sign(self, data): >+ """Calculate the flickr signature for a set of params. >+ >+ data -- a hash of all the params and values to be hashed, e.g. >+ {"api_key":"AAAA", "auth_token":"TTTT"} >+ >+ """ >+ dataName = "" >+ if self.secret is not None: >+ dataName = self.secret >+ #print data >+ keys = data.keys() >+ keys.sort() >+ >+ for a in keys: >+ if data[a] is not None: >+ dataName += "%s%s" % (a, data[a]) >+ #print 'dataName:', dataName >+ hash = md5.new() >+ hash.update(dataName) >+ return hash.hexdigest() >+ >+ #------------------------------------------------------------------- >+ def __getattr__(self, method, **arg): >+ """Handle all the flickr API calls. >+ >+ This is Michele Campeotto's cleverness, wherein he writes a >+ general handler for methods not defined, and assumes they are >+ flickr methods. He then converts them to a form to be passed as >+ the method= parameter, and goes from there. >+ >+ http://micampe.it/things/flickrclient >+ >+ My variant is the same basic thing, except it tracks if it has >+ already created a handler for a specific call or not. >+ >+ example usage: >+ >+ flickr.auth_getFrob(api_key="AAAAAA") >+ rsp = flickr.favorites_getList(api_key=flickrAPIKey, \\ >+ auth_token=token) >+ >+ """ >+ >+ if not self.__handlerCache.has_key(method): >+ def handler(_self = self, _method = method, **arg): >+ _method = "flickr." + _method.replace("_", ".") >+ url = "http://" + FlickrAPI.flickrHost + \ >+ FlickrAPI.flickrRESTForm >+ arg["method"] = _method >+ # Modified here: use default api_key and auth_token if not supplied >+ # Mod by: R. David Murray <rdmurray@bitdance.com> >+ >+ #API Key is used in all the methods. So, instead of specifying it as >+ #parameter in calling function, we can append it over here by default. >+ #Token eq. to Authentication is not required for ALL methods. So, >+ #better specify when needed. -Manish >+ >+ if not 'api_key' in arg: arg["api_key"] = _self.apiKey >+# if not 'auth_token' in arg and hasattr(_self, 'token'): arg['auth_token'] = _self.token >+ >+ postData = str(urllib.urlencode(arg)) + "&api_sig=" + \ >+ str(_self.__sign(arg)) >+ if DEBUG: >+ print "--url---------------------------------------------" >+ print url >+ print "--postData----------------------------------------" >+ print postData >+ data = '<rsp stat="ok"></rsp>' >+ req = urllib2.Request(url, postData) >+# socket.defaulttimetout(60) >+ f = urllib2.urlopen(req) >+ data = f.read() >+ if DEBUG: >+ print "--response----------------------------------------" >+ print data >+ f.close() >+ tempNode = XMLNode() >+ # Modified here: added XMLNode() as the 1st argument >+ # Mod by: Manish Rai Jain <manishrjain@gmail.com> >+ return XMLNode.parseXML(XMLNode(),xmlStr=data, storeXML=True) >+ >+ self.__handlerCache[method] = handler; >+ >+ return self.__handlerCache[method] >+ >+ #------------------------------------------------------------------- >+ def __getAuthURL(self, perms, frob): >+ """Return the authorization URL to get a token. >+ >+ This is the URL the app will launch a browser toward if it >+ needs a new token. >+ >+ perms -- "read", "write", or "delete" >+ frob -- picked up from an earlier call to FlickrAPI.auth_getFrob() >+ >+ """ >+ >+ data = {"api_key": self.apiKey, "frob": frob, "perms": perms} >+ data["api_sig"] = self.__sign(data) >+ return "http://%s%s?%s" % (FlickrAPI.flickrHost, \ >+ FlickrAPI.flickrAuthForm, urllib.urlencode(data)) >+ >+ #------------------------------------------------------------------- >+ def upload(self, filename, jpegData="", **arg): >+ """Upload a file to flickr. >+ >+ jpegData -- send buffered data read from file instead of filename >+ >+ Be extra careful you spell the parameters correctly, or you will >+ get a rather cryptic "Invalid Signature" error on the upload! >+ >+ Supported parameters: >+ >+ api_key >+ auth_token -- documentation mistakenly calls this "auth_hash" >+ title >+ description >+ tags -- space-delimited list of tags, "tag1 tag2 tag3" >+ is_public -- "1" or "0" >+ is_friend -- "1" or "0" >+ is_family -- "1" or "0" >+ >+ """ >+ >+ # verify key names >+ for a in arg.keys(): >+ if a != "api_key" and a != "auth_token" and a != "title" and \ >+ a != "description" and a != "tags" and a != "is_public" and \ >+ a != "is_friend" and a != "is_family": >+ >+ sys.stderr.write("FlickrAPI: warning: unknown parameter " \ >+ "\"%s\" sent to FlickrAPI.upload\n" % (a)) >+ >+ # Modified here: use default api_key and auth_token if not supplied >+ # Mod by: R. David Murray <rdmurray@bitdance.com> >+ if not 'api_key' in arg: arg["api_key"] = self.apiKey >+ if not 'auth_token' in arg: arg['auth_token'] = self.token >+ arg["api_sig"] = self.__sign(arg) >+ url = "http://" + FlickrAPI.flickrHost + FlickrAPI.flickrUploadForm >+ >+ # construct POST data >+# boundary = "===beej=jorgensen==========7d45e178b0434" >+ import mimetools >+ boundary = mimetools.choose_boundary() >+ Hdr = "multipart/form-data; boundary=%s" % boundary >+ body = "" >+ >+ # required params >+ for a in ('api_key', 'auth_token', 'api_sig'): >+ body += "--%s\r\n" % (boundary) >+ body += "Content-Disposition: form-data; name=\""+a+"\"\r\n\r\n" >+ body += "%s\r\n" % (arg[a]) >+ >+ # optional params >+ for a in ('title', 'description', 'tags', 'is_public', \ >+ 'is_friend', 'is_family'): >+ >+ if arg.has_key(a): >+ body += "--%s\r\n" % (boundary) >+ body += "Content-Disposition: form-data; name=\""+a+"\"\r\n\r\n" >+ body += "%s\r\n" % (arg[a]) >+ >+ body += "--%s\r\n" % (boundary) >+ body += "Content-Disposition: form-data; name=\"photo\";" >+ body += " filename=\"%s\"\r\n" % filename >+ body += "Content-Type: image/jpeg\r\n\r\n" >+ >+ #print body >+ >+ try: >+ # Added by Manish <manishrjain@gmail.com> for allowing upload >+ # by sending buffer instead of specifying filename >+ if jpegData=="": >+ fp = file(filename, "rb") >+ jpegData = fp.read() >+ fp.close() >+ >+ postData = body.encode("utf_8") + jpegData + "\r\n" + \ >+ ("--%s--" % (boundary)).encode("utf_8") >+ >+ >+ # Modified by Manish <manishrjain@gmail.com> for allowing >+ # upload through proxy >+ >+ request = urllib2.Request(url) >+ request.add_data(postData) >+ request.add_header("Content-Type", Hdr) >+ response = urllib2.urlopen(request) >+ rspXML = response.read() >+ # Modified by Manish Rai Jain <manishrjain@gmail.com> >+ return XMLNode.parseXML(XMLNode(), xmlStr=rspXML) >+ except IOError: >+ return None >+ >+ >+ >+ #----------------------------------------------------------------------- >+ #@classmethod >+ def testFailure(cls, rsp, exit=True): >+ """Exit app if the rsp XMLNode indicates failure.""" >+ if rsp['stat'] == "fail": >+ sys.stderr.write("%s: error %s: %s\n" % (rsp.elementName, \ >+ rsp.err[0]['code'], rsp.err[0]['msg'])) >+ if exit: sys.exit(1) >+ >+ #----------------------------------------------------------------------- >+ def __getCachedTokenPath(self): >+ """Return the directory holding the app data.""" >+ return os.path.expanduser("~/.flickr/%s" % (self.apiKey)) >+ >+ #----------------------------------------------------------------------- >+ def __getCachedTokenFilename(self): >+ """Return the full pathname of the cached token file.""" >+ return "%s/auth.xml" % (self.__getCachedTokenPath()) >+ >+ #----------------------------------------------------------------------- >+ def __getCachedToken(self): >+ """Read and return a cached token, or None if not found. >+ >+ The token is read from the cached token file, which is basically the >+ entire RSP response containing the auth element. >+ """ >+ >+ try: >+ f = file(self.__getCachedTokenFilename(), "r") >+ data = f.read() >+ f.close() >+ # Modified here: added XMLNode() as the 1st argument >+ # Mod by: Manish Rai Jain <manishrjain@gmail.com> >+ rsp = XMLNode.parseXML(XMLNode(), data) >+ >+ return rsp.auth[0].token[0].elementText >+ >+ except IOError: >+ return None >+ >+ #----------------------------------------------------------------------- >+ def __setCachedToken(self, xml): >+ """Cache a token for later use. >+ >+ The cached tag is stored by simply saving the entire RSP response >+ containing the auth element. >+ >+ """ >+ >+ path = self.__getCachedTokenPath() >+ if not os.path.exists(path): >+ os.makedirs(path) >+ >+ f = file(self.__getCachedTokenFilename(), "w") >+ f.write(xml) >+ f.close() >+ >+ >+ #----------------------------------------------------------------------- >+ def getToken(self, perms="write", browser="lynx"): >+ """Get a token either from the cache, or make a new one from the >+ frob. >+ >+ This first attempts to find a token in the user's token cache on >+ disk. >+ >+ If that fails (or if the token is no longer valid based on >+ flickr.auth.checkToken) a new frob is acquired. The frob is >+ validated by having the user log into flickr (with lynx), and >+ subsequently a valid token is retrieved. >+ >+ The newly minted token is then cached locally for the next run. >+ >+ perms--"read", "write", or "delete" >+ browser--whatever browser should be used in the system() call >+ >+ """ >+ >+ # see if we have a saved token >+ token = self.__getCachedToken() >+ >+ # see if it's valid >+ if token != None: >+ rsp = self.auth_checkToken(api_key=self.apiKey, auth_token=token) >+ if rsp['stat'] != "ok": >+ token = None >+ else: >+ # see if we have enough permissions >+ tokenPerms = rsp.auth[0].perms[0].elementText >+ if tokenPerms == "read" and perms != "read": token = None >+ elif tokenPerms == "write" and perms == "delete": token = None >+ >+ # get a new token if we need one >+ if token == None: >+ # get the frob >+ rsp = self.auth_getFrob(api_key=self.apiKey) >+ self.testFailure(rsp) >+ frob = rsp.frob[0].elementText >+ >+ # validate online >+ os.system("%s '%s'" % (browser, self.__getAuthURL(perms, frob))) >+ # get a token >+ rsp = self.auth_getToken(api_key=self.apiKey, frob=frob) >+ self.testFailure(rsp) >+ token = rsp.auth[0].token[0].elementText >+ >+ # store the auth info for next time >+ self.__setCachedToken(rsp.xml) >+ >+ # Modified here: save the auth token to use as a default >+ # Mod by: R. David Murray <rdmurray@bitdance.com> >+ self.token = token >+ return token >+ >+ >+######################################################################## >+# App functionality >+######################################################################## >+ >+def main(argv): >+ # flickr auth information: >+ flickrAPIKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # API key >+ flickrSecret = "yyyyyyyyyyyyyyyy" # shared "secret" >+ >+ # make a new FlickrAPI instance >+ fapi = FlickrAPI(flickrAPIKey, flickrSecret) >+ >+ # do the whole whatever-it-takes to get a valid token: >+ token = fapi.getToken(browser="/usr/bin/x-www-browser") >+ >+ # get my favorites >+ rsp = fapi.favorites_getList(api_key=flickrAPIKey,auth_token=token) >+ fapi.testFailure(rsp) >+ >+ # and print them >+ for a in rsp.photos[0].photo: >+ print "%10s: %s" % (a['id'], a['title'].encode("ascii", "replace")) >+ >+ # upload the file foo.jpg >+ #rsp = fapi.upload("foo.jpg", api_key=flickrAPIKey, auth_token=token, \ >+ # title="This is the title", description="This is the description", \ >+ # tags="tag1 tag2 tag3", is_public="1") >+ #if rsp == None: >+ # sys.stderr.write("can't find file\n") >+ #else: >+ # fapi.testFailure(rsp) >+ >+ return 0 >+ >+# run the main if we're not being imported: >+if __name__ == "__main__": sys.exit(main(sys.argv)) >+ >diff -Naur flickrfs-1.3.9.orig/flickrfs/flickrfs.py flickrfs-cvs.orig/flickrfs/flickrfs.py >--- flickrfs-1.3.9.orig/flickrfs/flickrfs.py 1970-01-01 01:00:00.000000000 +0100 >+++ flickrfs-cvs.orig/flickrfs/flickrfs.py 2008-10-17 17:20:36.000000000 +0200 >@@ -0,0 +1,1108 @@ >+#!/usr/bin/python >+#=============================================================================== >+# flickrfs - Virtual Filesystem for Flickr >+# Copyright (c) 2005,2006 Manish Rai Jain <manishrjain@gmail.com> >+# >+# This program can be distributed under the terms of the GNU GPL version 2, or >+# its later versions. >+# >+# DISCLAIMER: The API Key and Shared Secret are provided by the author in >+# the hope that it will prevent unnecessary trouble to the end-user. The >+# author will not be liable for any misuse of this API Key/Shared Secret >+# through this application/derived apps/any 3rd party apps using this key. >+#=============================================================================== >+ >+__author__ = "Manish Rai Jain (manishrjain@gmail.com)" >+__license__ = "GPLv2 (details at http://www.gnu.org/licenses/licenses.html#GPL)" >+ >+import thread, string, ConfigParser, mimetypes, codecs >+import time, logging, logging.handlers, os, sys >+from glob import glob >+from errno import * >+from traceback import format_exc >+# The python-fuse api has changed in 0.2, but we're still using the 0.1 api. >+# The following two lines line will make us compatible with both versions by >+# making 2.0 use the 1.0 api. >+# (See http://fuse4bsd.creo.hu/README.new_fusepy_api.html) >+import fuse >+fuse.fuse_python_api = (0, 1) >+Fuse = fuse.Fuse >+import threading >+import random, commands >+from urllib2 import URLError >+from transactions import TransFlickr >+import inodes >+ >+#Some global definitions and functions >+NUMRETRIES = 3 >+DEFAULTCONFIG = """\ >+[configuration] >+ >+browser: /usr/bin/x-www-browser >+image.size: >+sets.sync.int: 300 >+stream.sync.int: 300 >+""" >+ >+#Set up the .flickfs directory. >+homedir = os.getenv('HOME') >+flickrfsHome = os.path.join(homedir, '.flickrfs') >+dbPath = os.path.join(flickrfsHome, '.inode.bdb') >+ >+if not os.path.exists(flickrfsHome): >+ os.mkdir(os.path.join(flickrfsHome)) >+else: >+ # Remove previous metadata files from ~/.flickrfs >+ for a in glob(os.path.join(flickrfsHome, '.*')): >+ os.remove(os.path.join(flickrfsHome, a)) >+ try: >+ os.remove(dbPath) >+ except: >+ pass >+ >+# Added by Varun Hiremath >+if not os.path.exists(flickrfsHome + "/config.txt"): >+ fconfig = open(flickrfsHome+"/config.txt",'w') >+ fconfig.write(DEFAULTCONFIG) >+ fconfig.close() >+ >+# Set up logging >+rootlogger = logging.getLogger() >+loghdlr = logging.handlers.RotatingFileHandler( >+ os.path.join(flickrfsHome,'log'), "a", 5242880, 3) >+logfmt = logging.Formatter("%(asctime)s %(name)-14s %(levelname)-7s %(threadName)-10s %(funcName)-22s %(message)s", "%x %X") >+loghdlr.setFormatter(logfmt) >+rootlogger.addHandler(loghdlr) >+rootlogger.setLevel(logging.DEBUG) >+log = logging.getLogger('flickrfs') >+logattr = logging.getLogger('flickrfs.attr') >+logattr.setLevel(logging.INFO) >+ >+cp = ConfigParser.ConfigParser() >+cp.read(flickrfsHome + '/config.txt') >+_resizeStr = "" >+sets_sync_int = 600.0 >+stream_sync_int = 600.0 >+try: >+ _resizeStr = cp.get('configuration', 'image.size') >+except: >+ print 'No default size of image found. Will upload original size of images.' >+try: >+ sets_sync_int = float(cp.get('configuration', 'sets.sync.int')) >+except: >+ pass >+try: >+ stream_sync_int = float(cp.get('configuration', 'stream.sync.int')) >+except: >+ pass >+try: >+ browserName = cp.get('configuration', 'browser') >+except: >+ pass >+ >+# Retrive the resize string. >+def GetResizeStr(): >+ return _resizeStr >+ >+#Utility functions. >+def _log_exception_wrapper(func, *args, **kw): >+ """Call 'func' with args and kws and log any exception it throws. >+ """ >+ for i in range(0, NUMRETRIES): >+ log.debug("retry attempt %s for func %s", i, func.__name__) >+ try: >+ func(*args, **kw) >+ return >+ except: >+ log.error("exception in function %s", func.__name__) >+ log.error(format_exc()) >+ >+def background(func, *args, **kw): >+ """Run 'func' as a thread, logging any exceptions it throws. >+ >+ To run >+ >+ somefunc(arg1, arg2='value') >+ >+ as a thread, do: >+ >+ background(somefunc, arg1, arg2='value') >+ >+ Any exceptions thrown are logged as errors, and the traceback is logged. >+ """ >+ thread.start_new_thread(_log_exception_wrapper, (func,)+args, kw) >+ >+def timerThread(func, func1, interval): >+ '''Execute func now, followed by func1 every interval seconds >+ ''' >+ log.debug("running first pass funtion %s", func.__name__) >+ t = threading.Timer(0.0, func) >+ try: >+ t.run() >+ except: >+ log.debug(format_exc()) >+ while(interval): >+ log.debug("scheduling function %s to run in %s seconds", >+ func1.__name__, interval) >+ t = threading.Timer(interval, func1) >+ try: >+ t.run() >+ except: >+ log.debug(format_exc()) >+ >+def retryFlickrOp(isNone, func, *args): >+ # This function helps in retrying the flickr transactions, in case they fail. >+ result = None >+ for i in range(0, NUMRETRIES): >+ log.debug("retry attempt %d for func %s", i, func.__name__) >+ try: >+ result = func(*args) >+ if result is None: >+ if isNone: >+ return result >+ else: >+ continue >+ else: >+ return result >+ except URLError, detail: >+ log.error("URLError in function %s with error: %s", >+ func.__name__, detail) >+ # We've utilized all our attempts, send out the result whatever it is. >+ return result >+ >+class Flickrfs(Fuse): >+ >+ def __init__(self, *args, **kw): >+ >+ Fuse.__init__(self, *args, **kw) >+ log.info("mountpoint: %s", repr(self.mountpoint)) >+ log.info("mount options: %s", ', '.join(self.optlist)) >+ log.info("named mount options: %s", >+ ', '.join([ "%s: %s" % (k, v) for k, v in self.optdict.items() ])) >+ >+ self.inodeCache = inodes.InodeCache(dbPath) # Inodes need to be stored. >+ self.imgCache = inodes.ImageCache() >+ self.NSID = "" >+ self.transfl = TransFlickr(browserName) >+ >+ # Set some variables to be utilized by statfs function. >+ self.statfsCounter = -1 >+ self.max = 0L >+ self.used = 0L >+ >+ self.NSID = self.transfl.getUserId() >+ if self.NSID is None: >+ log.error("can't retrieve user information") >+ sys.exit(-1) >+ >+ log.info('getting list of licenses available') >+ self.licenses = self.transfl.getLicenses() >+ if self.licenses is None: >+ log.error("can't retrieve license information") >+ sys.exit(-1) >+ >+ # do stuff to set up your filesystem here, if you want >+ self._mkdir("/") >+ self._mkdir("/tags") >+ self._mkdir("/tags/personal") >+ self._mkdir("/tags/public") >+ background(timerThread, self.sets_thread, >+ self.sync_sets_thread, sets_sync_int) #sync every 2 minutes >+ >+ >+ def imageResize(self, bufData): >+ # If no resizing information is present, then return the buffer directly. >+ if GetResizeStr() == "": >+ return bufData >+ >+ # Else go ahead and do the conversion. >+ im = '/tmp/flickrfs-' + str(int(random.random()*1000000000)) >+ f = open(im, 'w') >+ f.write(bufData) >+ f.close() >+ cmd = 'identify -format "%%w" %s'%(im,) >+ status,ret = commands.getstatusoutput(cmd) >+ msg = ("%s command not found; you must install Imagemagick to get " >+ "auto photo resizing") >+ if status!=0: >+ print msg % 'identify' >+ log.error(msg, identify) >+ return bufData >+ try: >+ if int(ret)<int(GetResizeStr().split('x')[0]): >+ log.info('image size is smaller than size specified in config.txt;' >+ ' retaining original size') >+ return bufData >+ except: >+ log.error('invalid format of image.size in config.txt') >+ return bufData >+ log.debug("resizing image %s to size %s" % (im, GetResizeStr())) >+ cmd = 'convert %s -resize %s %s-conv'%(im, GetResizeStr(), im) >+ ret = os.system(cmd) >+ if ret!=0: >+ print msg % 'convert' >+ log.error(msg, 'convert') >+ return bufData >+ else: >+ f = open(im + '-conv') >+ return f.read() >+ >+ >+ def writeMetaInfo(self, id, INFO): >+ #The metadata may be unicode strings, so we need to encode them on write >+ filePath = os.path.join(flickrfsHome, '.'+id) >+ f = codecs.open(filePath, 'w', 'utf8') >+ f.write('# Metadata file : flickrfs - Virtual filesystem for flickr\n') >+ f.write('# Photo owner: %s NSID: %s\n' % (INFO[7], INFO[8])) >+ f.write('# Handy link to photo: %s\n'%(INFO[9])) >+ f.write('# Licences available: \n') >+ for (k, v) in self.licenses: >+ f.write('# %s : %s\n' % (k, v)) >+ f.write('[metainfo]\n') >+ f.write("%s:%s\n"%('title', INFO[4])) >+ f.write("%s:%s\n"%('description', INFO[3])) >+ tags = ','.join(INFO[5]) >+ f.write("%s:%s\n"%('tags', tags)) >+ f.write("%s:%s\n"%('license',INFO[6])) >+ f.close() >+ f = open(filePath) >+ f.read() >+ fileSize = f.tell() >+ f.close() >+ return fileSize >+ >+ def __populate_set(self, set_id, curdir): >+ # Exception handling will be done by background function. >+ photosInSet = self.transfl.getPhotosFromPhotoset(set_id) >+ for b,p in photosInSet.iteritems(): >+ info = self.transfl.parseInfoFromPhoto(b,p) >+ self._mkfileWithMeta(curdir, info) >+ log.info("set %s populated, photo count %s", curdir, len(photosInSet)) >+ >+ def sets_thread(self): >+ """ >+ The beauty of the FUSE python implementation is that with the >+ python interpreter running in foreground, you can have threads >+ """ >+ print "Sets are being populated in the background." >+ log.info("started") >+ self._mkdir("/sets") >+ for a in self.transfl.getPhotosetList(): >+ title = a.title[0].elementText.replace('/', '_') >+ log.info("populating set %s", title) >+ curdir = "/sets/" + title >+ if title.strip()=='': >+ curdir = "/sets/" + a['id'] >+ set_id = a['id'] >+ self._mkdir(curdir, id=set_id) >+ self.__populate_set(set_id, curdir) >+ >+ def _sync_code(self, psetOnline, curdir): >+ psetLocal = set(map(lambda x: x[0], self.getdir(curdir, False))) >+ for b in psetOnline: >+ info = self.transfl.parseInfoFromPhoto(b) >+ imageTitle = info.get('title','') >+ if hasattr(b, 'originalformat'): >+ imageTitle = self.__getImageTitle(imageTitle, >+ b['id'], b['originalformat']) >+ else: >+ imageTitle = self.__getImageTitle(imageTitle, b['id']) >+ path = "%s/%s"%(curdir, imageTitle) >+ inode = self.inodeCache.get(path) >+ # This exception throwing is just for debugging. >+ if inode == None and self.inodeCache.has_key(path): >+ e = OSError("Path %s present in inodeCache" % path) >+ e.errno = ENOENT >+ raise e >+ if inode == None: # Image inode not present in the set. >+ log.debug("new image found: %s", path) >+ self._mkfileWithMeta(curdir, info) >+ else: >+ if inode.mtime != int(info.get('dupdate')): >+ log.debug("image %s changed", path) >+ self.inodeCache.pop(path) >+ if self.inodeCache.has_key(path + ".meta"): >+ self.inodeCache.pop(path + ".meta") >+ self._mkfileWithMeta(curdir, info) >+ psetLocal.discard(imageTitle) >+ if len(psetLocal)>0: >+ log.info('%s photos have been deleted online' % len(psetLocal)) >+ for c in psetLocal: >+ log.info('deleting %s', c) >+ self.unlink("%s/%s" % (curdir, c), False) >+ >+ def __sync_set_in_background(self, set_id, curdir): >+ # Exception handling will be done by background function. >+ log.info("syncing set %s", curdir) >+ psetOnline = self.transfl.getPhotosFromPhotoset(set_id) >+ self._sync_code(psetOnline, curdir) >+ log.info("set %s sync successfully finished", curdir) >+ >+ def sync_sets_thread(self): >+ log.info("started") >+ setListOnline = self.transfl.getPhotosetList() >+ setListLocal = self.getdir('/sets', False) >+ >+ for a in setListOnline: >+ title = a.title[0].elementText.replace('/', '_') >+ if title.strip()=="": >+ title = a['id'] >+ if (title,0) not in setListLocal: #New set added online >+ log.info("new set %s found online", title) >+ self._mkdir('/sets/'+title, a['id']) >+ else: #Present Online >+ setListLocal.remove((title,0)) >+ for a in setListLocal: #List of sets present locally, but not online >+ log.info('set %s no longer online, recursively deleting it', a) >+ self.rmdir('/sets/'+a[0], online=False, recr=True) >+ >+ for a in setListOnline: >+ title = a.title[0].elementText.replace('/', '_') >+ curdir = "/sets/" + title >+ if title.strip()=='': >+ curdir = "/sets/" + a['id'] >+ set_id = a['id'] >+ self.__sync_set_in_background(set_id, curdir) >+ log.info('finished') >+ >+ def sync_stream_thread(self): >+ log.info('started') >+ psetOnline = self.transfl.getPhotoStream(self.NSID) >+ self._sync_code(psetOnline, '/stream') >+ log.info('finished') >+ >+ def stream_thread(self): >+ log.info("started") >+ print "Populating photostream" >+ for b in self.transfl.getPhotoStream(self.NSID): >+ info = self.transfl.parseInfoFromPhoto(b) >+ self._mkfileWithMeta('/stream', info) >+ log.info("finished") >+ print "Photostream population finished." >+ >+ def tags_thread(self, path): >+ ind = string.rindex(path, '/') >+ tagName = path[ind+1:] >+ if tagName.strip()=='': >+ log.error("the tagName '%s' doesn't contain any tags", tagName) >+ return >+ log.info("started for %s", tagName) >+ sendtagList = ','.join(tagName.split(':')) >+ if(path.startswith('/tags/personal')): >+ user_id = self.NSID >+ else: >+ user_id = None >+ for b in self.transfl.getTaggedPhotos(sendtagList, user_id): >+ info = self.transfl.parseInfoFromPhoto(b) >+ self._mkfileWithMeta(path, info) >+ >+ def getUnixPerms(self, info): >+ mode = info.get('mode') >+ if mode is not None: >+ return mode >+ perms = info.get('perms') >+ if perms is None: >+ return 0644 >+ if perms is "1": # public >+ return 0755 >+ elif perms is "2": # friends only. Add 1 to 4 in middle letter. >+ return 0754 >+ elif perms is "3": # family only. Add 2 to 4 in middle letter. >+ return 0764 >+ elif perms is "4": # friends and family. Add 1+2 to 4 in middle letter. >+ return 0774 >+ else: >+ return 0744 # private >+ >+ def __getImageTitle(self, title, id, format = "jpg"): >+ temp = title.replace('/', '') >+# return "%s_%s.%s" % (temp[:32], id, format) >+ # Store the photos original name. Thus, when pictures are uploaded >+ # their names would remain as it is, allowing easy resumption of >+ # uploading of images, in case some of the photos fail uploading. >+ return "%s.%s" % (temp, format) >+ >+ def _mkfileWithMeta(self, path, info): >+ # Don't write the meta information here, because it requires access to >+ # the full INFO. Only do with the smaller version of information that >+ # is provided. >+ if info is None: >+ return >+ title = info.get("title", "") >+ id = info.get("id", "") >+ ext = info.get("format", "jpg") >+ title = self.__getImageTitle(title, id, ext) >+ >+ # Refactor this section of code, so that it can be called >+ # from read. >+ # Figure out a way to retrieve information, which can be >+ # used in _mkfile. >+ mtime = info.get("dupdate") >+ ctime = info.get("dupload") >+ perms = self.getUnixPerms(info) >+ self._mkfile(path+"/"+title, id=id, mode=perms, mtime=mtime, ctime=ctime) >+ self._mkfile(path+'/.'+title+'.meta', id) >+ >+ def _parsepathid(self, path, id=""): >+ #Path and Id may be unicode strings, so encode them to utf8 now before >+ #we use them, otherwise python will throw errors when we combine them >+ #with regular strings. >+ path = path.encode('utf8') >+ if id!=0: id = id.encode('utf8') >+ parentDir, name = os.path.split(path) >+ if parentDir=='': >+ parentDir = '/' >+ log.debug("parentDir %s", parentDir) >+ return path, id, parentDir, name >+ >+ def _mkdir(self, path, id="", mtime=None, ctime=None): >+ path, id, parentDir, name = self._parsepathid(path, id) >+ log.debug("creating directory %s", path) >+ self.inodeCache[path] = inodes.DirInode(path, id, mtime=mtime, ctime=ctime) >+ if path!='/': >+ pinode = self.getInode(parentDir) >+ pinode.nlink += 1 >+ self.updateInode(parentDir, pinode) >+ log.debug("nlink of %s is now %s", parentDir, pinode.nlink) >+ >+ def _mkfile(self, path, id="", mode=None, >+ comm_meta="", mtime=None, ctime=None): >+ path, id, parentDir, name = self._parsepathid(path, id) >+ log.debug("creating file %s with id %s", path, id) >+ image_name, extension = os.path.splitext(name) >+ if not extension: >+ log.error("can't create file without extension") >+ return >+ fInode = inodes.FileInode(path, id, mode=mode, comm_meta=comm_meta, >+ mtime=mtime, ctime=ctime) >+ self.inodeCache[path] = fInode >+ # Now create the meta info inode if the meta info file exists >+ # refactoring: create the meta info inode, regardless of the >+ # existence of datapath. >+# path = os.path.join(parentDir, '.' + image_name + '.meta') >+# datapath = os.path.join(flickrfsHome, '.'+id) >+# if os.path.exists(datapath): >+# size = os.path.getsize(datapath) >+# self.inodeCache[path] = FileInode(path, id) >+ >+ def getattr(self, path): >+ # getattr is being called 4-6 times every second for '/' >+ # Don't log those calls, as they clutter up the log file. >+ if path != "/": >+ logattr.debug("getattr: %s", path) >+ templist = path.split('/') >+ if path.startswith('/sets/'): >+ templist[2] = templist[2].split(':')[0] >+ elif path.startswith('/stream'): >+ templist[1] = templist[1].split(':')[0] >+ path = '/'.join(templist) >+ >+ inode=self.getInode(path) >+ if inode: >+ #log.debug("inode %s", inode) >+ statTuple = (inode.mode,inode.ino,inode.dev,inode.nlink, >+ inode.uid,inode.gid,inode.size,inode.atime,inode.mtime,inode.ctime) >+ #log.debug("statsTuple %s", statTuple) >+ return statTuple >+ else: >+ e = OSError("No such file"+path) >+ e.errno = ENOENT >+ raise e >+ >+ def readlink(self, path): >+ log.debug("readlink") >+ return os.readlink(path) >+ >+ def getdir(self, path, hidden=True): >+ logattr.debug("getdir: %s", path) >+ templist = [] >+ if hidden: >+ templist = ['.', '..'] >+ for a in self.inodeCache.keys(): >+ ind = a.rindex('/') >+ if path=='/': >+ path="" >+ if path==a[:ind]: >+ name = a.split('/')[-1] >+ if name=="": >+ continue >+ if hidden and name.startswith('.'): >+ templist.append(name) >+ elif not name.startswith('.'): >+ templist.append(name) >+ return map(lambda x: (x,0), templist) >+ >+ def unlink(self, path, online=True): >+ log.debug("unlink %s", path) >+ if self.inodeCache.has_key(path): >+ inode = self.inodeCache.pop(path) >+ # Remove the meta data file as well if it exists >+ if self.inodeCache.has_key(path + ".meta"): >+ self.inodeCache.pop(path + ".meta") >+ >+ typesinfo = mimetypes.guess_type(path) >+ if typesinfo[0] is None or typesinfo[0].count('image')<=0: >+ log.debug("unlinked non-image file %s", path) >+ return >+ >+ if path.startswith('/sets/'): >+ ind = path.rindex('/') >+ pPath = path[:ind] >+ pinode = self.getInode(pPath) >+ if online: >+ self.transfl.removePhotofromSet(photoId=inode.photoId, >+ photosetId=pinode.setId) >+ log.info("photo %s removed from set", path) >+ del inode >+ else: >+ log.error("%s is not a known file", path) >+ #Dont' raise an exception. Not useful when >+ #using editors like Vim. They make loads of >+ #crap buffer files >+ >+ def rmdir(self, path, online=True, recr=False): >+ log.debug("removing %s", path) >+ if self.inodeCache.has_key(path): >+ for a in self.inodeCache.keys(): >+ if a.startswith(path+'/'): >+ if recr: >+ self.unlink(a, online) >+ else: >+ e = OSError("Directory not empty") >+ e.errno = ENOTEMPTY >+ raise e >+ else: >+ log.error("%s is not a known directory", path) >+ e = OSError("No such folder"+path) >+ e.errno = ENOENT >+ raise e >+ >+ if path=='/sets' or path=='/tags' or path=='/tags/personal' \ >+ or path=='/tags/public' or path=='/stream': >+ log.debug("attempt to remove framework file %s rejected", path) >+ e = OSError("removal of folder %s not allowed" % (path)) >+ e.errno = EPERM >+ raise e >+ >+ ind = path.rindex('/') >+ pPath = path[:ind] >+ inode = self.inodeCache.pop(path) >+ if online and path.startswith('/sets/'): >+ self.transfl.deleteSet(inode.setId) >+ del inode >+ pInode = self.getInode(pPath) >+ pInode.nlink -= 1 >+ self.updateInode(pPath, pInode) >+ >+ def symlink(self, path, path1): >+ log.debug("symlink") >+ return os.symlink(path, path1) >+ >+ def rename(self, path, path1): >+ log.debug("%s %s", path, path1) >+ #Donot allow Vim to create a file~ >+ #Check for .meta in both paths >+ if path.count('~')>0 or path1.count('~')>0: >+ log.debug("vim enablement path entered") >+ try: >+ #Get inode, but _dont_ remove from cache >+ inode = self.getInode(path) >+ if inode is not None: >+ self.inodeCache[path1] = inode >+ except: >+ log.debug("couldn't find inode for %s", path) >+ return >+ >+ #Read from path >+ inode = self.getInode(path) >+ if inode is None or not hasattr(inode, 'photoId'): >+ return >+ fname = os.path.join(flickrfsHome, '.'+inode.photoId) >+ f = open(fname, 'r') >+ buf = f.read() >+ f.close() >+ >+ #Now write to path1 >+ inode = self.getInode(path1) >+ if inode is None or not hasattr(inode, 'photoId'): >+ return >+ fname = os.path.join(flickrfsHome, '.'+inode.photoId) >+ f = open(fname, 'w') >+ f.write(buf) >+ f.close() >+ inode.size = os.path.getsize(fname) >+ self.updateInode(path1, inode) >+ retinfo = self.parse(fname, inode.photoId) >+ if retinfo.count('Error')>0: >+ log.error(retinfo) >+ >+ def link(self, srcpath, destpath): >+ log.debug("%s %s", srcpath, destpath) >+ #Add image from stream to set, w/o retrieving >+ slist = srcpath.split('/') >+ sname_file = slist.pop(-1) >+ dlist = destpath.split('/') >+ dname_file = dlist.pop(-1) >+ error = 0 >+ if sname_file=="" or sname_file.startswith('.'): >+ error = 1 >+ if dname_file=="" or dname_file.startswith('.'): >+ error = 1 >+ if not destpath.startswith('/sets/'): >+ error = 1 >+ if error is 1: >+ log.error("linking is allowed only between 2 image files") >+ return >+ sinode = self.getInode(srcpath) >+ self._mkfile(destpath, id=sinode.id, mode=sinode.mode, >+ comm_meta=sinode.comm_meta, mtime=sinode.mtime, >+ ctime=sinode.ctime) >+ parentPath = '/'.join(dlist) >+ pinode = self.getInode(parentPath) >+ if pinode.setId==0: >+ try: >+ pinode.setId = self.transfl.createSet(parentPath, sinode.photoId) >+ self.updateInode(parentPath, pinode) >+ except: >+ e = OSError("Can't create a new set") >+ e.errno = EIO >+ raise e >+ else: >+ self.transfl.put2Set(pinode.setId, sinode.photoId) >+ >+ >+ def chmod(self, path, mode): >+ log.debug("%s %s" % path, mode) >+ inode = self.getInode(path) >+ typesinfo = mimetypes.guess_type(path) >+ >+ if inode.comm_meta is None: >+ log.debug("chmod on directory ignored") >+ return >+ >+ elif typesinfo[0] is None or typesinfo[0].count('image')<=0: >+ >+ os.chmod(path, mode) >+ return >+ >+ elif self.transfl.setPerm(inode.photoId, mode, inode.comm_meta)==True: >+ inode.mode = mode >+ self.updateInode(path, inode) >+ return >+ >+ def chown(self, path, user, group): >+ log.debug("%s:%s %s (ignored)", user, group, path) >+ >+ def truncate(self, path, size): >+ log.debug("%s %s", path, size) >+ ind = path.rindex('/') >+ name_file = path[ind+1:] >+ >+ typeinfo = mimetypes.guess_type(path) >+ if typeinfo[0] is None or typeinfo[0].count('image')<=0: >+ inode = self.getInode(path) >+ filePath = os.path.join(flickrfsHome, '.'+inode.photoId) >+ f = open(filePath, 'w+') >+ return f.truncate(size) >+ >+ def mknod(self, path, mode, dev): >+ log.debug("%s %s %s ", path, mode, dev) >+ templist = path.split('/') >+ name_file = templist[-1] >+ >+ if name_file.startswith('.') and name_file.count('.meta') > 0: >+ # We need to handle the special case, where some meta files are being >+ # created through mknod. Creation of meta files is done when adding >+ # images automatically; and they should not go through mknod system call. >+ # Editors like Vim, try to generate random swap files when reading >+ # meta; and this should be *disallowed*. >+ log.debug("mknod for meta file %s ignored", path) >+ return >+ >+ if path.startswith('/sets/'): >+ templist[2] = templist[2].split(':')[0] >+ elif path.startswith('/stream'): >+ templist[1] = templist[1].split(':')[0] >+ path = '/'.join(templist) >+ >+ log.debug("modified file path %s", path) >+ #Lets guess what kind of a file is this. >+ #Is it an image file? or, some other temporary file >+ #created by the tools you're using. >+ typeinfo = mimetypes.guess_type(path) >+ if typeinfo[0] is None or typeinfo[0].count('image') <= 0: >+ f = open(os.path.join(flickrfsHome,'.'+name_file), 'w') >+ f.close() >+ # TODO(manishrjain): This should not be FileInode, it should rather be >+ # Inode. >+ self.inodeCache[path] = inodes.FileInode(path, name_file, mode=mode) >+ else: >+ self._mkfile(path, id="NEW", mode=mode) >+ >+ def mkdir(self, path, mode): >+ log.debug("%s with mode %s", path, mode) >+ if path.startswith("/tags"): >+ if path.count('/')==3: #/tags/personal (or private)/dirname ONLY >+ self._mkdir(path) >+ background(self.tags_thread, path) >+ else: >+ e = OSError("Not allowed to create directory %s" % path) >+ e.errno = EACCES >+ raise e >+ elif path.startswith("/sets"): >+ if path.count('/')==2: #Only allow creation of new set /sets/newset >+ self._mkdir(path, id=0) >+ #id=0 means that not yet created online >+ else: >+ e = OSError("Not allowed to create directory %s" % path) >+ e.errno = EACCES >+ raise e >+ elif path=='/stream': >+ self._mkdir(path) >+ background(timerThread, self.stream_thread, >+ self.sync_stream_thread, stream_sync_int) >+ >+ else: >+ e = OSError("Not allowed to create directory %s" % path) >+ e.errno = EACCES >+ raise e >+ >+ def utime(self, path, times): >+ inode = self.getInode(path) >+ inode.atime = times[0] >+ inode.mtime = times[1] >+ self.updateInode(path, inode) >+ return 0 >+ >+ def open(self, path, flags): >+ log.info("%s with flags %s", path, flags) >+ ind = path.rindex('/') >+ name_file = path[ind+1:] >+ if name_file.startswith('.') and name_file.endswith('.meta'): >+ self.handleAccessToNonImage(path) >+ return 0 >+ typesinfo = mimetypes.guess_type(path) >+ if typesinfo[0] is None or typesinfo[0].count('image')<=0: >+ log.debug('non-image file found %s', path) >+ self.handleAccessToNonImage(path) >+ return 0 >+ >+ templist = path.split('/') >+ if path.startswith('/sets/'): >+ templist[2] = templist[2].split(':')[0] >+ elif path.startswith('/stream'): >+ templist[1] = templist[1].split(':')[0] >+ path = '/'.join(templist) >+ log.debug("path after modification is %s", path) >+ >+ inode = self.getInode(path) >+ if inode.photoId=="NEW": #Just skip if new (i.e. uploading) >+ return 0 >+ if self.imgCache.getBuffer(inode.photoId)=="": >+ log.debug("retrieving image %s from flickr", inode.photoId) >+ self.imgCache.setBuffer(inode.photoId, >+ str(self.transfl.getPhoto(inode.photoId))) >+ inode.size = self.imgCache.getBufLen(inode.photoId) >+ log.debug("size of image is %s", inode.size) >+ self.updateInode(path, inode) >+ return 0 >+ >+ def read(self, path, length, offset): >+ log.debug("%s offset %s length %s", path, offset, length) >+ ind = path.rindex('/') >+ name_file = path[ind+1:] >+ if name_file.startswith('.') and name_file.endswith('.meta'): >+ # Check if file is not present. If not, retrieve and >+ # create the file locally. >+ buf = self.handleReadNonImage(path, length, offset) >+ return buf >+ typesinfo = mimetypes.guess_type(path) >+ if typesinfo[0] is None or typesinfo[0].count('image')<=0: >+ return self.handleReadNonImage(path, length, offset) >+ return self.handleReadImage(path, length, offset) >+ >+ def parse(self, fname, photoId): >+ cp = ConfigParser.ConfigParser() >+ log.debug("parsing file %s for photoid %s", fname, photoId) >+ cp.read(fname) >+ log.debug("file %s has been read by ConfigParser", fname) >+ options = cp.options('metainfo') >+ title='' >+ desc='' >+ tags='' >+ license='' >+ if 'description' in options: >+ desc = cp.get('metainfo', 'description') >+ if 'tags' in options: >+ tags = cp.get('metainfo', 'tags') >+ if 'title' in options: >+ title = cp.get('metainfo', 'title') >+ if 'license' in options: >+ license = cp.get('metainfo', 'license') >+ >+ log.debug("setting metadata for file %s", fname) >+ if self.transfl.setMeta(photoId, title, desc)==False: >+ return "Error:Can't set Meta information" >+ >+ log.debug("setting tags for %s", fname) >+ if self.transfl.setTags(photoId, tags)==False: >+ log.debug("setting tags for %s failed", fname) >+ return "Error:Can't set tags" >+ >+ log.debug("setting license for %s", fname) >+ if self.transfl.setLicense(photoId, license)==False: >+ return "Error:Can't set license" >+ >+ # except: >+ # log.error("Can't parse file:%s:"%(fname,)) >+ # return "Error:Can't parse" >+ return 'Success:Updated photo:%s:%s:'%(fname,photoId) >+ >+ ################################################## >+ # 'handle' Functions for handling read and writes. >+ ################################################## >+ def handleAccessToNonImage(self, path): >+ inode = self.getInode(path) >+ if inode is None: >+ log.error("inode %s doesn't exist", path) >+ e = OSError("No inode found") >+ e.errno = EIO >+ raise e >+ fname = os.path.join(flickrfsHome, '.'+inode.photoId) #ext >+ # Handle the case when file already exists. >+ if not os.path.exists(fname) or os.path.getsize(fname) == 0L: >+ log.info("retrieving meta information for file %s and photo id %s", >+ fname, inode.photoId) >+ INFO = self.transfl.getPhotoInfo(inode.photoId) >+ size = self.writeMetaInfo(inode.photoId, INFO) >+ log.info("information has been written for photo id %s", inode.photoId) >+ inode.size = size >+ self.updateInode(path, inode) >+ time.sleep(1) # Enough time for OS to call for getattr again. >+ return inode >+ >+ def handleReadNonImage(self, path, length, offset): >+ inode = self.handleAccessToNonImage(path) >+ f = open(os.path.join(flickrfsHome, '.'+inode.photoId), 'r') >+ f.seek(offset) >+ return f.read(length) >+ >+ def handleReadImage(self, path, length, offset): >+ inode = self.getInode(path) >+ if inode is None: >+ log.error("inode %s doesn't exist", path) >+ e = OSError("No inode found") >+ e.errno = EIO >+ raise e >+ if self.imgCache.getBufLen(inode.photoId) is 0: >+ log.debug("retrieving image %s from flickr", inode.photoId) >+ buf = retryFlickrOp(False, self.transfl.getPhoto, >+ inode.photoId) >+ if len(buf) == 0: >+ log.error("can't retrieve image %s", inode.photoId) >+ e = OSError("Unable to retrieve image.") >+ e.errno = EIO >+ raise e >+ self.imgCache.setBuffer(inode.photoId, buf) >+ inode.size = self.imgCache.getBufLen(inode.photoId) >+ temp = self.imgCache.getBuffer(inode.photoId, offset, offset+length) >+ if len(temp) < length: >+ self.imgCache.popBuffer(inode.photoId) >+ self.updateInode(path, inode) >+ return temp >+ >+ def handleWriteToNonImage(self, path, buf, off): >+ inode = self.handleAccessToNonImage(path) >+ fname = os.path.join(flickrfsHome, '.'+inode.photoId) #ext >+ log.debug("writing to %s", fname) >+ f = open(fname, 'r+') >+ f.seek(off) >+ f.write(buf) >+ f.close() >+ if len(buf)<4096: >+ inode.size = os.path.getsize(fname) >+ retinfo = self.parse(fname, inode.photoId) >+ if retinfo.count('Error')>0: >+ e = OSError(retinfo.split(':')[1]) >+ e.errno = EIO >+ raise e >+ self.updateInode(path, inode) >+ return len(buf) >+ >+ def handleUploadingImage(self, path, inode, taglist): >+ tags = [ '"%s"'%(a,) for a in taglist] >+ tags.append('flickrfs') >+ taglist = ' '.join(tags) >+ log.info('uploading %s with len %s', >+ path, self.imgCache.getBufLen(inode.photoId)) >+ id = None >+ bufData = self.imgCache.getBuffer(inode.photoId) >+ bufData = self.imageResize(bufData) >+ id = retryFlickrOp(False, self.transfl.uploadfile, >+ path, taglist, bufData, inode.mode) >+ if id is None: >+ log.error("unable to upload file %s", inode.photoId) >+ e = OSError("Unable to upload file.") >+ e.errno = EIO >+ raise e >+ self.imgCache.popBuffer(inode.photoId) >+ inode.photoId = id >+ self.updateInode(path, inode) >+ return inode >+ >+ def handleWriteToBuffer(self, path, buf): >+ inode = self.getInode(path) >+ if inode is None: >+ log.error("inode %s doesn't exist", path) >+ e = OSError("No inode found") >+ e.errno = EIO >+ raise e >+ self.imgCache.addBuffer(inode.photoId, buf) >+ return inode >+ >+ def handleWriteAddToSet(self, parentPath, pinode, inode): >+ #Create set if it doesn't exist online (i.e. if id=0) >+ if pinode.setId is 0: >+ # Retry creation of set if unsuccessful. >+ pinode.setId = retryFlickrOp(False, self.transfl.createSet, >+ parentPath, inode.photoId) >+ # If the set is created, then return. >+ if pinode.setId is not None: >+ self.updateInode(parentPath, pinode) >+ return >+ else: >+ log.error("unable to create set %s", parentPath) >+ e = OSError("Unable to create set.") >+ e.errno = EIO >+ raise e >+ else: >+ # If the operation put2Set doesn't throw exception, that means >+ # that the picture has been successfully added to set. >+ # Return in that case, retry otherwise. >+ retryFlickrOp(True, self.transfl.put2Set, >+ pinode.setId, inode.photoId) >+ return >+ >+ ############################# >+ # End of 'handle' Functions. >+ ############################# >+ >+ def write(self, path, buf, off): >+ log.debug("write to %s at offset %s", path, off) >+ ind = path.rindex('/') >+ name_file = path[ind+1:] >+ if name_file.startswith('.') and name_file.count('.meta')>0: >+ return self.handleWriteToNonImage(path, buf, off) >+ typesinfo = mimetypes.guess_type(path) >+ if typesinfo[0] is None or typesinfo[0].count('image')<=0: >+ return self.handleWriteToNonImage(path, buf, off) >+ templist = path.split('/') >+ inode = None >+ if path.startswith('/tags'): >+ e = OSError("Copying to tags not allowed") >+ e.errno = EIO >+ raise e >+ if path.startswith('/stream'): >+ tags = templist[1].split(':') >+ templist[1] = tags.pop(0) >+ path = '/'.join(templist) >+ inode = self.handleWriteToBuffer(path, buf) >+ if len(buf) < 4096: >+ self.handleUploadingImage(path, inode, tags) >+ elif path.startswith('/sets/'): >+ setnTags = templist[2].split(':') >+ setName = setnTags.pop(0) >+ templist[2] = setName >+ path = '/'.join(templist) >+ inode = self.handleWriteToBuffer(path, buf) >+ if len(buf) < 4096: >+ templist.pop(-1) >+ parentPath = '/'.join(templist) >+ pinode = self.getInode(parentPath) >+ inode = self.handleUploadingImage(path, inode, setnTags) >+ self.handleWriteAddToSet(parentPath, pinode, inode) >+ log.debug("done write to %s at offset %s", path, off) >+ if len(buf)<4096: >+ templist = path.split('/') >+ templist.pop(-1) >+ parentPath = '/'.join(templist) >+ try: >+ self.inodeCache.pop(path) >+ except: >+ pass >+ INFO = self.transfl.getPhotoInfo(inode.photoId) >+ info = self.transfl.parseInfoFromFullInfo(inode.photoId, INFO) >+ self._mkfileWithMeta(parentPath, info) >+ self.writeMetaInfo(inode.photoId, INFO) >+ return len(buf) >+ >+ def getInode(self, path): >+ if self.inodeCache.has_key(path): >+ #log.debug("got cached inode for %s", path) >+ return self.inodeCache[path] >+ else: >+ #log.debug("%s is not in inode cache", path) >+ return None >+ >+ def updateInode(self, path, inode): >+ self.inodeCache[path] = inode >+ >+ def release(self, path, flags): >+ log.debug("%s with flags %s ignored", path, flags) >+ return 0 >+ >+ def statfs(self): >+ """ >+ Should return a tuple with the following elements in respective order: >+ >+ F_BSIZE - Preferred file system block size. (int) >+ F_FRSIZE - Fundamental file system block size. (int) >+ F_BLOCKS - Total number of blocks in the filesystem. (long) >+ F_BFREE - Total number of free blocks. (long) >+ F_BAVAIL - Free blocks available to non-super user. (long) >+ F_FILES - Total number of file nodes. (long) >+ F_FFREE - Total number of free file nodes. (long) >+ F_FAVAIL - Free nodes available to non-super user. (long) >+ F_FLAG - Flags. System dependent: see statvfs() man page. (int) >+ F_NAMEMAX - Maximum file name length. (int) >+ Feel free to set any of the above values to 0, which tells >+ the kernel that the info is not available. >+ """ >+ block_size = 1024 >+ blocks = 0L >+ blocks_free = 0L >+ files = 0L >+ files_free = 0L >+ namelen = 255 >+ # statfs is being called repeatedly at least once a second. >+ # The bandwidth information doesn't change that often, so >+ # save upon communication with flickr servers to retrieve this >+ # information. Only retrieve it once in a while. >+ if self.statfsCounter >= 500 or self.statfsCounter is -1: >+ (self.max, self.used) = self.transfl.getBandwidthInfo() >+ self.statfsCounter = 0 >+ log.info('retrieved bandwidth info: max %s used %s', >+ self.max, self.used) >+ self.statfsCounter = self.statfsCounter + 1 >+ >+ if self.max is not None: >+ blocks = long(self.max)/block_size >+ blocks_used = long(self.used)/block_size >+ blocks_free = blocks - blocks_used >+ blocks_available = blocks_free >+ return (block_size, blocks, blocks_free, blocks_available, >+ files, files_free, namelen) >+ >+ def fsync(self, path, isfsyncfile): >+ log.debug("%s, isfsyncfile=%s", path, isfsyncfile) >+ return 0 >+ >+ >+if __name__ == '__main__': >+ try: >+ server = Flickrfs() >+ server.multithreaded = 1; >+ server.main() >+ except KeyError: >+ log.error('got key error; exiting...') >+ sys.exit(0) >diff -Naur flickrfs-1.3.9.orig/flickrfs/inodes.py flickrfs-cvs.orig/flickrfs/inodes.py >--- flickrfs-1.3.9.orig/flickrfs/inodes.py 1970-01-01 01:00:00.000000000 +0100 >+++ flickrfs-cvs.orig/flickrfs/inodes.py 2007-03-29 16:44:26.000000000 +0200 >@@ -0,0 +1,134 @@ >+#=============================================================================== >+# flickrfs - Virtual Filesystem for Flickr >+# Copyright (c) 2005,2006 Manish Rai Jain <manishrjain@gmail.com> >+# >+# This program can be distributed under the terms of the GNU GPL version 2, or >+# its later versions. >+# >+# DISCLAIMER: The API Key and Shared Secret are provided by the author in >+# the hope that it will prevent unnecessary trouble to the end-user. The >+# author will not be liable for any misuse of this API Key/Shared Secret >+# through this application/derived apps/any 3rd party apps using this key. >+#=============================================================================== >+ >+__author__ = "Manish Rai Jain (manishrjain@gmail.com)" >+__license__ = "GPLv2 (details at http://www.gnu.org/licenses/licenses.html#GPL)" >+ >+import os, sys, time >+from stat import * >+import cPickle >+ >+DEFAULTBLOCKSIZE = 4*1024 # 4 KB >+ >+class Inode(object): >+ """Common base class for all file system objects >+ """ >+ def __init__(self, path=None, id='', mode=None, >+ size=0L, mtime=None, ctime=None): >+ self.nlink = 1 >+ self.size = size >+ self.id = id >+ self.mode = mode >+ self.ino = long(time.time()) >+ self.dev = 409089L >+ self.uid = int(os.getuid()) >+ self.gid = int(os.getgid()) >+ now = int(time.time()) >+ self.atime = now >+ if mtime is None: >+ self.mtime = now >+ else: >+ self.mtime = int(mtime) >+ if ctime is None: >+ self.ctime = now >+ else: >+ self.ctime = int(ctime) >+ self.blocksize = DEFAULTBLOCKSIZE >+ >+class DirInode(Inode): >+ def __init__(self, path=None, id="", mode=None, mtime=None, ctime=None): >+ if mode is None: mode = 0755 >+ super(DirInode, self).__init__(path, id, mode, 0L, mtime, ctime) >+ self.mode = S_IFDIR | self.mode >+ self.nlink += 1 >+ self.dirfile = "" >+ self.setId = self.id >+ >+ >+class FileInode(Inode): >+ def __init__(self, path=None, id="", mode=None, comm_meta="", >+ size=0L, mtime=None, ctime=None): >+ if mode is None: mode = 0644 >+ super(FileInode, self).__init__(path, id, mode, size, mtime, ctime) >+ self.mode = S_IFREG | self.mode >+ self.photoId = self.id >+ self.comm_meta = comm_meta >+ >+ >+class ImageCache: >+ def __init__(self): >+ self.bufDict = {} >+ >+ def setBuffer(self, id, buf): >+ self.bufDict[id] = buf >+ >+ def addBuffer(self, id, inc): >+ buf = self.getBuffer(id) >+ self.setBuffer(id, buf+inc) >+ >+ def getBuffer(self, id, start=0, end=0): >+ if end == 0: >+ return self.bufDict.get(id, "")[start:] >+ else: >+ return self.bufDict.get(id, "")[start:end] >+ >+ def getBufLen(self, id): >+ return long(len(self.bufDict.get(id, ""))) >+ >+ def popBuffer(self, id): >+ if id in self.bufDict: >+ return self.bufDict.pop(id) >+ >+ >+class InodeCache(dict): >+ def __init__(self, dbPath): >+ dict.__init__(self) >+ try: >+ import bsddb >+ # If bsddb is available, utilize that package >+ # and store the inodes in database. >+ self.db = bsddb.btopen(dbPath, flag='c') >+ except: >+ # Otherwise, store the inodes in memory. >+ self.db = {} >+ # Keep the keys in memory. >+ self.keysCache = set() >+ >+ def __getitem__(self, key, d=None): >+ # key k may be unicode, so convert it to >+ # a normal string first. >+ if not self.has_key(key): >+ return d >+ valObjStr = self.db.get(str(key)) >+ return cPickle.loads(valObjStr) >+ >+ def __setitem__(self, key, value): >+ self.keysCache.add(key) >+ self.db[str(key)] = cPickle.dumps(value) >+ >+ def get(self, k, d=None): >+ return self.__getitem__(k, d) >+ >+ def keys(self): >+ return list(self.keysCache) >+ >+ def pop(self, k, *args): >+ # key k may be unicode, so convert it to >+ # a normal string first. >+ valObjStr = self.db.pop(str(k), *args) >+ self.keysCache.discard(k) >+ if valObjStr != None: >+ return cPickle.loads(valObjStr) >+ >+ def has_key(self, k): >+ return k in self.keysCache >diff -Naur flickrfs-1.3.9.orig/flickrfs/transactions.py flickrfs-cvs.orig/flickrfs/transactions.py >--- flickrfs-1.3.9.orig/flickrfs/transactions.py 1970-01-01 01:00:00.000000000 +0100 >+++ flickrfs-cvs.orig/flickrfs/transactions.py 2008-01-31 20:32:34.000000000 +0100 >@@ -0,0 +1,443 @@ >+#=============================================================================== >+# flickrfs - Virtual Filesystem for Flickr >+# Copyright (c) 2005,2006 Manish Rai Jain <manishrjain@gmail.com> >+# >+# This program can be distributed under the terms of the GNU GPL version 2, or >+# its later versions. >+# >+# DISCLAIMER: The API Key and Shared Secret are provided by the author in >+# the hope that it will prevent unnecessary trouble to the end-user. The >+# author will not be liable for any misuse of this API Key/Shared Secret >+# through this application/derived apps/any 3rd party apps using this key. >+#=============================================================================== >+ >+__author__ = "Manish Rai Jain (manishrjain@gmail.com)" >+__license__ = "GPLv2 (details at http://www.gnu.org/licenses/licenses.html#GPL)" >+ >+from flickrapi import FlickrAPI >+from traceback import format_exc >+import urllib2 >+import sys >+import string >+import os >+import time >+import logging >+log = logging.getLogger('flickrfs.trans') >+ >+# flickr auth information >+flickrAPIKey = "f8aa9917a9ae5e44a87cae657924f42d" # API key >+flickrSecret = "3fbf7144be7eca28" # shared "secret" >+ >+# Utility functions >+def kwdict(**kw): return kw >+ >+#Transactions with flickr, wraps FlickrAPI >+# calls in Flickfs-specialized functions. >+class TransFlickr: >+ >+ extras = "original_format,date_upload,last_update" >+ >+ def __init__(self, browserName): >+ self.fapi = FlickrAPI(flickrAPIKey, flickrSecret) >+ self.user_id = "" >+ # proceed with auth >+ # TODO use auth.checkToken function if available, >+ # and wait after opening browser. >+ print "Authorizing with flickr..." >+ log.info("authorizing with flickr...") >+ try: >+ self.authtoken = self.fapi.getToken(browser=browserName) >+ except: >+ print ("Can't retrieve token from browser %s" % browserName) >+ print ("\tIf you're behind a proxy server," >+ " first set http_proxy environment variable.") >+ print "\tPlease close all your browser windows, and try again" >+ log.error(format_exc()) >+ log.error("can't retrieve token from browser %s", browserName) >+ sys.exit(-1) >+ if self.authtoken == None: >+ print "Unable to authorize (reason unknown)" >+ log.error('not able to authorize; exiting') >+ sys.exit(-1) >+ #Add some authorization checks here(?) >+ print "Authorization complete." >+ log.info('authorization complete') >+ >+ def uploadfile(self, filepath, taglist, bufData, mode): >+ #Set public 4(always), 1(public). Public overwrites f&f. >+ public = mode&1 >+ #Set friends and family 4(always), 2(family), 1(friends). >+ friends = mode>>3 & 1 >+ family = mode>>4 & 1 >+ #E.g. 745 - 4:No f&f, but 5:public >+ #E.g. 754 - 5:friends, but not public >+ #E.g. 774 - 7:f&f, but not public >+ >+ log.info("uploading file %s", filepath) >+ log.info(" data length: %s", len(bufData)) >+ log.info(" taglist: %s", taglist) >+ log.info(" permissions: family %s, friends %s, public %s", >+ family, friends, public) >+ filename = os.path.splitext(os.path.basename(filepath))[0] >+ rsp = self.fapi.upload(filename=filepath, jpegData=bufData, >+ title=filename, >+ tags=taglist, >+ is_public=public and "1" or "0", >+ is_friend=friends and "1" or "0", >+ is_family=family and "1" or "0") >+ >+ if rsp is None: >+ log.error("response None from attempt to write file %s", filepath) >+ log.error("will attempt recovery...") >+ recent_rsp = None >+ trytimes = 2 >+ while(trytimes): >+ log.info("sleeping for 3 seconds...") >+ time.sleep(3) >+ trytimes -= 1 >+ # Keep on trying to retrieve the recently uploaded photo, till we >+ # actually get the information, or the function throws an exception. >+ while(recent_rsp is None or not recent_rsp): >+ recent_rsp = self.fapi.photos_recentlyUpdated( >+ auth_token=self.authtoken, min_date='1', per_page='1') >+ >+ pic = recent_rsp.photos[0].photo[0] >+ log.info('we are looking for %s', filename) >+ log.info('most recently updated pic is %s', pic['title']) >+ if filename == pic['title']: >+ id = pic['id'] >+ log.info("file %s uploaded with photoid %s", filepath, id) >+ return id >+ log.error("giving up; upload of %s appears to have failed", filepath) >+ return None >+ else: >+ id = rsp.photoid[0].elementText >+ log.info("file %s uploaded with photoid %s", filepath, id) >+ return id >+ >+ def put2Set(self, set_id, photo_id): >+ log.info("uploading photo %s to set id %s", photo_id, set_id) >+ rsp = self.fapi.photosets_addPhoto(auth_token=self.authtoken, >+ photoset_id=set_id, photo_id=photo_id) >+ if rsp: >+ log.info("photo uploaded to set") >+ else: >+ log.error(rsp.errormsg) >+ >+ def createSet(self, path, photo_id): >+ log.info("creating set %s with primary photo %s", path, photo_id) >+ path, title = os.path.split(path) >+ rsp = self.fapi.photosets_create(auth_token=self.authtoken, >+ title=title, primary_photo_id=photo_id) >+ if rsp: >+ log.info("created set %s", title) >+ return rsp.photoset[0]['id'] >+ else: >+ log.error(rsp.errormsg) >+ >+ def deleteSet(self, set_id): >+ log.info("deleting set %s", set_id) >+ if str(set_id)=="0": >+ log.info("ignoring attempt to delete set wtih set_id 0 (a locally " >+ "created set that has not yet acquired an id via uploading") >+ return >+ rsp = self.fapi.photosets_delete(auth_token=self.authtoken, >+ photoset_id=set_id) >+ if rsp: >+ log.info("deleted set %s", set_id) >+ else: >+ log.error(rsp.errormsg) >+ >+ def getPhotoInfo(self, photoId): >+ log.debug("id: %s", photoId) >+ rsp = self.fapi.photos_getInfo(auth_token=self.authtoken, photo_id=photoId) >+ if not rsp: >+ log.error("can't retrieve information about photo %s; got error %s", >+ photoId, rsp.errormsg) >+ return None >+ #XXX: should see if there's some other 'format' option we can fall back to. >+ try: format = rsp.photo[0]['originalformat'] >+ except KeyError: format = 'jpg' >+ perm_public = rsp.photo[0].visibility[0]['ispublic'] >+ perm_family = rsp.photo[0].visibility[0]['isfamily'] >+ perm_friend = rsp.photo[0].visibility[0]['isfriend'] >+ if perm_public == '1': >+ mode = 0755 >+ else: >+ b_cnt = 4 >+ if perm_family == '1': >+ b_cnt += 2 >+ if perm_friend == '1': >+ b_cnt += 1 >+ mode = "07" + str(b_cnt) + "4" >+ mode = int(mode) >+ >+ if hasattr(rsp.photo[0],'permissions'): >+ permcomment = rsp.photo[0].permissions[0]['permcomment'] >+ permaddmeta = rsp.photo[0].permissions[0]['permaddmeta'] >+ else: >+ permcomment = permaddmeta = [None] >+ >+ commMeta = '%s%s' % (permcomment,permaddmeta) # Required for chmod. >+ desc = rsp.photo[0].description[0].elementText >+ title = rsp.photo[0].title[0].elementText >+ if hasattr(rsp.photo[0].tags[0], "tag"): >+ taglist = [ a.elementText for a in rsp.photo[0].tags[0].tag ] >+ else: >+ taglist = [] >+ license = rsp.photo[0]['license'] >+ owner = rsp.photo[0].owner[0]['username'] >+ ownerNSID = rsp.photo[0].owner[0]['nsid'] >+ url = rsp.photo[0].urls[0].url[0].elementText >+ posted = rsp.photo[0].dates[0]['posted'] >+ lastupdate = rsp.photo[0].dates[0]['lastupdate'] >+ return (format, mode, commMeta, desc, title, taglist, >+ license, owner, ownerNSID, url, int(posted), int(lastupdate)) >+ >+ def setPerm(self, photoId, mode, comm_meta="33"): >+ log.debug("id: %s, mode: %s, comm_meta=%s", photoId, mode, comm_meta) >+ public = mode&1 #Set public 4(always), 1(public). Public overwrites f&f >+ #Set friends and family 4(always), 2(family), 1(friends) >+ friends = mode>>3 & 1 >+ family = mode>>4 & 1 >+ if len(comm_meta)<2: >+ # This wd patch string index out of range bug, caused >+ # because some photos may not have comm_meta value set. >+ comm_meta="33" >+ rsp = self.fapi.photos_setPerms(auth_token=self.authtoken, >+ is_public=str(public), >+ is_friend=str(friends), >+ is_family=str(family), >+ perm_comment=comm_meta[0], >+ perm_addmeta=comm_meta[1], >+ photo_id=photoId) >+ if not rsp: >+ log.error("couldn't set permission for photo %s; got error %s", >+ photoId, rsp.errormsg) >+ return False >+ log.info("permissions have been set for photo %s", photoId) >+ return True >+ >+ def setTags(self, photoId, tags): >+ log.debug("id: %s, tags: %s", photoId, tags) >+ templist = [ '"%s"'%(a,) for a in string.split(tags, ',')] + ['flickrfs'] >+ tagstring = ' '.join(templist) >+ rsp = self.fapi.photos_setTags(auth_token=self.authtoken, >+ photo_id=photoId, tags=tagstring) >+ if not rsp: >+ log.error("couldn't set tags for %s; got error %s", >+ photoId, rsp.errormsg) >+ return False >+ return True >+ >+ def setMeta(self, photoId, title, desc): >+ log.debug("id: %s, title: %s, desc: %s", photoId, title, desc) >+ rsp = self.fapi.photos_setMeta(auth_token=self.authtoken, >+ photo_id=photoId, title=title, >+ description=desc) >+ if not rsp: >+ log.error("couldn't set meta info for photo %s; got error", >+ photoId, rsp.errormsg) >+ return False >+ return True >+ >+ def getLicenses(self): >+ log.debug("started") >+ rsp = self.fapi.photos_licenses_getInfo() >+ if not rsp: >+ log.error("couldn't retrieve licenses; got error %s", rsp.errormsg) >+ return None >+ licenseDict = {} >+ for l in rsp.licenses[0].license: >+ licenseDict[l['id']] = l['name'] >+ keys = licenseDict.keys() >+ keys.sort() >+ sortedLicenseList = [] >+ for k in keys: >+ # Add tuple of license key, and license value. >+ sortedLicenseList.append((k, licenseDict[k])) >+ return sortedLicenseList >+ >+ def setLicense(self, photoId, license): >+ log.debug("id: %s, license: %s", photoId, license) >+ rsp = self.fapi.photos_licenses_setLicense(auth_token=self.authtoken, >+ photo_id=photoId, >+ license_id=license) >+ if not rsp: >+ log.error("couldn't set license info for photo %s; got error %s", >+ photoId, rsp.errormsg) >+ return False >+ return True >+ >+ def getPhoto(self, photoId): >+ log.debug("id: %s", photoId) >+ rsp = self.fapi.photos_getSizes(auth_token=self.authtoken, >+ photo_id=photoId) >+ if not rsp: >+ log.error("error while trying to retrieve size information" >+ " for photo %s", photoId) >+ return None >+ buf = "" >+ for a in rsp.sizes[0].size: >+ if a['label']=='Original': >+ try: >+ f = urllib2.urlopen(a['source']) >+ buf = f.read() >+ except: >+ log.error("exception in getPhoto") >+ log.error(format_exc()) >+ return "" >+ if not buf: >+ f = urllib2.urlopen(rsp.sizes[0].size[-1]['source']) >+ buf = f.read() >+ return buf >+ >+ def removePhotofromSet(self, photoId, photosetId): >+ log.debug("id: %s, setid: %s", photoId, photosetId) >+ rsp = self.fapi.photosets_removePhoto(auth_token=self.authtoken, >+ photo_id=photoId, >+ photoset_id=photosetId) >+ if rsp: >+ log.info("photo %s removed from set %s", photoId, photosetId) >+ else: >+ log.error(rsp.errormsg) >+ >+ >+ def getBandwidthInfo(self): >+ log.debug("retrieving bandwidth information") >+ rsp = self.fapi.people_getUploadStatus(auth_token=self.authtoken) >+ if not rsp: >+ log.error("can't retrieve bandwidth information; got error %s", >+ rsp.errormsg) >+ return (None,None) >+ bw = rsp.user[0].bandwidth[0] >+ log.debug("max bandwidth: %s, bandwidth used: %s", bw['max'], bw['used']) >+ return (bw['max'], bw['used']) >+ >+ def getUserId(self): >+ log.debug("entered") >+ rsp = self.fapi.auth_checkToken(api_key=flickrAPIKey, >+ auth_token=self.authtoken) >+ if not rsp: >+ log.error("unable to get userid; got error %s", rsp.errormsg) >+ return None >+ usr = rsp.auth[0].user[0] >+ log.info("got NSID %s", usr['nsid']) >+ #Set self.user_id to this value >+ self.user_id = usr['nsid'] >+ return usr['nsid'] >+ >+ def getPhotosetList(self): >+ log.debug("entered") >+ if self.user_id is "": >+ self.getUserId() #This will set the value of self.user_id >+ rsp = self.fapi.photosets_getList(auth_token=self.authtoken, >+ user_id=self.user_id) >+ if not rsp: >+ log.error("error getting photoset list; got error %s", rsp.errormsg) >+ return [] >+ if not hasattr(rsp.photosets[0], "photoset"): >+ log.info("no sets found for userid %s", self.user_id) >+ return [] >+ else: >+ log.info("%s sets found for userid %s", >+ len(rsp.photosets[0].photoset), self.user_id) >+ return rsp.photosets[0].photoset >+ >+ def parseInfoFromPhoto(self, photo, perms=None): >+ info = {} >+ info['id'] = photo['id'] >+ info['title'] = photo['title'].replace('/', '_') >+ # Some pics don't contain originalformat attribute, so set it to jpg by default. >+ try: >+ info['format'] = photo['originalformat'] >+ except KeyError: >+ info['format'] = 'jpg' >+ >+ try: >+ info['dupload'] = photo['dateupload'] >+ except KeyError: >+ info['dupload'] = '0' >+ >+ try: >+ info['dupdate'] = photo['lastupdate'] >+ except KeyError: >+ info['dupdate'] = '0' >+ >+ info['perms'] = perms >+ return info >+ >+ def parseInfoFromFullInfo(self, id, fullInfo): >+ info = {} >+ info['id'] = id >+ info['title'] = fullInfo[4] >+ info['format'] = fullInfo[0] >+ info['dupload'] = fullInfo[10] >+ info['dupdate'] = fullInfo[11] >+ info['mode'] = fullInfo[1] >+ return info >+ >+ def getPhotosFromPhotoset(self, photoset_id): >+ log.debug("set id: %s", photoset_id) >+ photosPermsMap = {} >+ # I'm not utilizing the value part of this dictionary. Its arbitrarily >+ # set to i. >+ for i in range(0,3): >+ page = 1 >+ while True: >+ rsp = self.fapi.photosets_getPhotos(auth_token=self.authtoken, >+ photoset_id=photoset_id, >+ extras=self.extras, >+ page=str(page), >+ privacy_filter=str(i)) >+ if not rsp: >+ break >+ if not hasattr(rsp.photoset[0], 'photo'): >+ log.error("photoset %s doesn't have attribute photo", rsp.photoset[0]['id']) >+ break >+ for p in rsp.photoset[0].photo: >+ photosPermsMap[p] = str(i) >+ page += 1 >+ if page > int(rsp.photoset[0]['pages']): break >+ if photosPermsMap: break >+ return photosPermsMap >+ >+ def getPhotoStream(self, user_id): >+ log.debug("userid: %s", user_id) >+ retList = [] >+ pageNo = 1 >+ maxPage = 1 >+ while pageNo<=maxPage: >+ log.info("retreiving page number %s of %s", pageNo, maxPage) >+ rsp = self.fapi.photos_search(auth_token=self.authtoken, >+ user_id=user_id, per_page="500", >+ page=str(pageNo), extras=self.extras) >+ if not rsp: >+ log.error("can't retrive photos from your stream; got error %s", >+ rsp.errormsg) >+ return retList >+ if not hasattr(rsp.photos[0], 'photo'): >+ log.error("photos.search response doesn't have attribute photos; " >+ "returning list acquired so far") >+ return retList >+ for a in rsp.photos[0].photo: >+ retList.append(a) >+ maxPage = int(rsp.photos[0]['pages']) >+ pageNo = pageNo + 1 >+ return retList >+ >+ def getTaggedPhotos(self, tags, user_id=None): >+ log.debug("tags: %s user_id: %s", tags, user_id) >+ kw = kwdict(auth_token=self.authtoken, tags=tags, tag_mode="all", >+ extras=self.extras, per_page="500") >+ if user_id is not None: >+ kw = kwdict(user_id=user_id, **kw) >+ rsp = self.fapi.photos_search(**kw) >+ log.debug("search for photos with tags %s has been" >+ " successfully finished" % tags) >+ if not rsp: >+ log.error("couldn't search for the photos; got error %s", rsp.errormsg) >+ return >+ if not hasattr(rsp.photos[0], 'photo'): >+ return [] >+ return rsp.photos[0].photo >diff -Naur flickrfs-1.3.9.orig/flickrfs.py flickrfs-cvs.orig/flickrfs.py >--- flickrfs-1.3.9.orig/flickrfs.py 2007-02-04 06:08:09.000000000 +0100 >+++ flickrfs-cvs.orig/flickrfs.py 1970-01-01 01:00:00.000000000 +0100 >@@ -1,1084 +0,0 @@ >-#=============================================================================== >-# flickrfs - Virtual Filesystem for Flickr >-# Copyright (c) 2005,2006 Manish Rai Jain <manishrjain@gmail.com> >-# >-# This program can be distributed under the terms of the GNU GPL version 2, or >-# its later versions. >-# >-# DISCLAIMER: The API Key and Shared Secret are provided by the author in >-# the hope that it will prevent unnecessary trouble to the end-user. The >-# author will not be liable for any misuse of this API Key/Shared Secret >-# through this application/derived apps/any 3rd party apps using this key. >-#=============================================================================== >- >-__author__ = "Manish Rai Jain (manishrjain@gmail.com)" >-__license__ = "GPLv2 (details at http://www.gnu.org/licenses/licenses.html#GPL)" >- >-import thread, string, ConfigParser, mimetypes, codecs >-import time, logging, logging.handlers, os, sys >-from glob import glob >-from errno import * >-from traceback import format_exc >-from fuse import Fuse >-import threading >-import random, commands >-from urllib2 import URLError >-from transactions import TransFlickr >-import inodes >- >-#Some global definitions and functions >-NUMRETRIES = 3 >- >-#Set up the .flickfs directory. >-homedir = os.getenv('HOME') >-flickrfsHome = os.path.join(homedir, '.flickrfs') >-dbPath = os.path.join(flickrfsHome, '.inode.bdb') >- >-if not os.path.exists(flickrfsHome): >- os.mkdir(os.path.join(flickrfsHome)) >-else: >- # Remove previous metadata files from ~/.flickrfs >- for a in glob(os.path.join(flickrfsHome, '.*')): >- os.remove(os.path.join(flickrfsHome, a)) >- try: >- os.remove(dbPath) >- except: >- pass >- >-# Added by Varun Hiremath >-if not os.path.exists(flickrfsHome + "/config.txt"): >- fconfig = open(flickrfsHome+"/config.txt",'w') >- fconfig.write("[configuration]\n") >- fconfig.write("browser:/usr/bin/firefox\n") >- fconfig.write("image.size:\n") >- fconfig.write("sets.sync.int:300\n") >- fconfig.write("stream.sync.int:300\n") >- fconfig.close() >- >-# Set up logging >-log = logging.getLogger('flickrfs') >-loghdlr = logging.handlers.RotatingFileHandler( >- os.path.join(flickrfsHome,'log'), "a", 5242880, 3) >-logfmt = logging.Formatter("%(asctime)s %(levelname)-10s %(message)s", "%x %X") >-loghdlr.setFormatter(logfmt) >-log.addHandler(loghdlr) >-log.setLevel(logging.DEBUG) >- >-cp = ConfigParser.ConfigParser() >-cp.read(flickrfsHome + '/config.txt') >-_resizeStr = "" >-sets_sync_int = 600.0 >-stream_sync_int = 600.0 >-try: >- _resizeStr = cp.get('configuration', 'image.size') >-except: >- print 'No default size of image found. Will upload original size of images.' >-try: >- sets_sync_int = float(cp.get('configuration', 'sets.sync.int')) >-except: >- pass >-try: >- stream_sync_int = float(cp.get('configuration', 'stream.sync.int')) >-except: >- pass >-try: >- browserName = cp.get('configuration', 'browser') >-except: >- pass >- >-# Retrive the resize string. >-def GetResizeStr(): >- return _resizeStr >- >-#Utility functions. >-def _log_exception_wrapper(func, *args, **kw): >- """Call 'func' with args and kws and log any exception it throws. >- """ >- for i in range(0, NUMRETRIES): >- log.debug("Retry attempt %s for func %s" % (i, func)) >- try: >- func(*args, **kw) >- return >- except: >- log.error("Exception in function %s" % func) >- log.error(format_exc()) >- >-def background(func, *args, **kw): >- """Run 'func' as a thread, logging any exceptions it throws. >- >- To run >- >- somefunc(arg1, arg2='value') >- >- as a thread, do: >- >- background(somefunc, arg1, arg2='value') >- >- Any exceptions thrown are logged as errors, and the traceback is logged. >- """ >- thread.start_new_thread(_log_exception_wrapper, (func,)+args, kw) >- >-def timerThread(func, func1, interval): >- '''Execute func now, followed by func1 every interval seconds >- ''' >- t = threading.Timer(0.0, func) >- try: >- t.run() >- except: pass >- while(interval): >- t = threading.Timer(interval, func1) >- try: >- t.run() >- except: pass >- >-def retryFlickrOp(isNone, func, *args): >- # This function helps in retrying the flickr transactions, in case they fail. >- result = None >- for i in range(0, NUMRETRIES): >- log.debug("Retry attempt %s for func %s" % (i, func)) >- try: >- result = func(*args) >- if result is None: >- if isNone: >- return result >- else: >- continue >- else: >- return result >- except URLError, detail: >- log.error("Failure in function %s with error: %s" % (func, detail)) >- # We've utilized all our attempts, send out the result whatever it is. >- return result >- >-class Flickrfs(Fuse): >- >- def __init__(self, *args, **kw): >- >- Fuse.__init__(self, *args, **kw) >- log.info("flickrfs.py:Flickrfs:mountpoint: %s" % repr(self.mountpoint)) >- log.info("flickrfs.py:Flickrfs:unnamed mount options: %s" % self.optlist) >- log.info("flickrfs.py:Flickrfs:named mount options: %s" % self.optdict) >- >- self.inodeCache = inodes.InodeCache(dbPath) # Inodes need to be stored. >- self.imgCache = inodes.ImageCache() >- self.NSID = "" >- self.transfl = TransFlickr(log, browserName) >- >- # Set some variables to be utilized by statfs function. >- self.statfsCounter = -1 >- self.max = 0L >- self.used = 0L >- >- self.NSID = self.transfl.getUserId() >- if self.NSID is None: >- log.error("Initialization:Can't retrieve user information") >- sys.exit(-1) >- >- log.info('Getting list of licenses available') >- self.licenses = self.transfl.getLicenses() >- if self.licenses is None: >- log.error("Initialization:Can't retrieve license information") >- sys.exit(-1) >- >- # do stuff to set up your filesystem here, if you want >- self._mkdir("/") >- self._mkdir("/tags") >- self._mkdir("/tags/personal") >- self._mkdir("/tags/public") >- background(timerThread, self.sets_thread, >- self.sync_sets_thread, sets_sync_int) #sync every 2 minutes >- >- >- def imageResize(self, bufData): >- # If no resizing information is present, then return the buffer directly. >- if GetResizeStr() == "": >- return bufData >- >- # Else go ahead and do the conversion. >- im = '/tmp/flickrfs-' + str(int(random.random()*1000000000)) >- f = open(im, 'w') >- f.write(bufData) >- f.close() >- cmd = 'identify -format "%%w" %s'%(im,) >- status,ret = commands.getstatusoutput(cmd) >- if status!=0: >- print "identify command not found. Install Imagemagick" >- log.error("identify command not found. Install Imagemagick") >- return bufData >- try: >- if int(ret)<int(GetResizeStr().split('x')[0]): >- log.info('Image size is smaller than specified in config.txt.' >- ' Taking original size') >- return bufData >- except: >- log.error('Invalid format of image.size in config.txt') >- return bufData >- log.debug("Resizing image %s to size %s" % (im, GetResizeStr())) >- cmd = 'convert %s -resize %s %s-conv'%(im, GetResizeStr(), im) >- ret = os.system(cmd) >- if ret!=0: >- print "convert Command not found. Install Imagemagick" >- log.error("convert Command not found. Install Imagemagick") >- return bufData >- else: >- f = open(im + '-conv') >- return f.read() >- >- >- def writeMetaInfo(self, id, INFO): >- #The metadata may be unicode strings, so we need to encode them on write >- filePath = os.path.join(flickrfsHome, '.'+id) >- f = codecs.open(filePath, 'w', 'utf8') >- f.write('# Metadata file : flickrfs - Virtual filesystem for flickr\n') >- f.write('# Photo owner: %s NSID: %s\n' % (INFO[7], INFO[8])) >- f.write('# Handy link to photo: %s\n'%(INFO[9])) >- f.write('# Licences available: \n') >- for (k, v) in self.licenses: >- f.write('# %s : %s\n' % (k, v)) >- f.write('[metainfo]\n') >- f.write("%s:%s\n"%('title', INFO[4])) >- f.write("%s:%s\n"%('description', INFO[3])) >- tags = ','.join(INFO[5]) >- f.write("%s:%s\n"%('tags', tags)) >- f.write("%s:%s\n"%('license',INFO[6])) >- f.close() >- f = open(filePath) >- f.read() >- fileSize = f.tell() >- f.close() >- return fileSize >- >- def __populate_set(self, set_id, curdir): >- # Exception handling will be done by background function. >- photosInSet = self.transfl.getPhotosFromPhotoset(set_id) >- for b,p in photosInSet.iteritems(): >- info = self.transfl.parseInfoFromPhoto(b,p) >- self._mkfileWithMeta(curdir, info) >- log.info("Set %s populated." % curdir) >- >- def sets_thread(self): >- """ >- The beauty of the FUSE python implementation is that with the >- python interpreter running in foreground, you can have threads >- """ >- print "Sets are being populated in the background." >- log.info("sets_thread: started") >- self._mkdir("/sets") >- for a in self.transfl.getPhotosetList(): >- title = a.title[0].elementText.replace('/', '_') >- log.info("Populating set %s." % title) >- curdir = "/sets/" + title >- if title.strip()=='': >- curdir = "/sets/" + a['id'] >- set_id = a['id'] >- self._mkdir(curdir, id=set_id) >- background(self.__populate_set, set_id, curdir) >- >- def _sync_code(self, psetOnline, curdir): >- psetLocal = set(map(lambda x: x[0], self.getdir(curdir, False))) >- for b in psetOnline: >- info = self.transfl.parseInfoFromPhoto(b) >- imageTitle = info.get('title','') >- imageTitle = self.__getImageTitle(imageTitle, >- b['id'], b['originalformat']) >- path = "%s/%s"%(curdir, imageTitle) >- inode = self.inodeCache.get(path) >- # This exception throwing is just for debugging. >- if inode == None and self.inodeCache.has_key(path): >- e = OSError("Path %s present in inodeCache" % path) >- e.errno = ENOENT >- raise e >- if inode == None: # Image inode not present in the set. >- log.debug("New image found: %s"%(path)) >- self._mkfileWithMeta(curdir, info) >- else: >- if inode.mtime != int(info.get('dupdate')): >- log.debug("Image %s changed"%(path)) >- self.inodeCache.pop(path) >- if self.inodeCache.has_key(path + ".meta"): >- self.inodeCache.pop(path + ".meta") >- self._mkfileWithMeta(curdir, info) >- psetLocal.discard(imageTitle) >- if len(psetLocal)>0: >- log.info('%s photos have been deleted online' % len(psetLocal)) >- for c in psetLocal: >- log.info('deleting:%s' % c) >- self.unlink("%s/%s" % (curdir, c), False) >- >- def __sync_set_in_background(self, set_id, curdir): >- # Exception handling will be done by background function. >- log.info("Syncing set %s" % curdir) >- psetOnline = self.transfl.getPhotosFromPhotoset(set_id) >- self._sync_code(psetOnline, curdir) >- log.info("Set %s sync successfully finished." % curdir) >- >- def sync_sets_thread(self): >- log.info("sync_sets_thread: started") >- setListOnline = self.transfl.getPhotosetList() >- setListLocal = self.getdir('/sets', False) >- >- for a in setListOnline: >- title = a.title[0].elementText.replace('/', '_') >- if title.strip()=="": >- title = a['id'] >- if (title,0) not in setListLocal: #New set added online >- log.info("%s set has been added online."%(title,)) >- self._mkdir('/sets/'+title, a['id']) >- else: #Present Online >- setListLocal.remove((title,0)) >- for a in setListLocal: #List of sets present locally, but not online >- log.info('Recursively deleting set %s'%(a,)) >- self.rmdir('/sets/'+a[0], online=False, recr=True) >- >- for a in setListOnline: >- title = a.title[0].elementText.replace('/', '_') >- curdir = "/sets/" + title >- if title.strip()=='': >- curdir = "/sets/" + a['id'] >- set_id = a['id'] >- background(self.__sync_set_in_background, set_id, curdir) >- log.info('sync_sets_thread finished') >- >- def sync_stream_thread(self): >- log.info('sync_stream_thread started') >- psetOnline = self.transfl.getPhotoStream(self.NSID) >- self._sync_code(psetOnline, '/stream') >- log.info('sync_stream_thread finished') >- >- def stream_thread(self): >- log.info("stream_thread started") >- print "Populating photostream" >- for b in self.transfl.getPhotoStream(self.NSID): >- info = self.transfl.parseInfoFromPhoto(b) >- self._mkfileWithMeta('/stream', info) >- log.info("stream_thread finished") >- print "Photostream population finished." >- >- def tags_thread(self, path): >- ind = string.rindex(path, '/') >- tagName = path[ind+1:] >- if tagName.strip()=='': >- log.error("The tagName:%s: doesn't contain any tags"%(tagName)) >- return >- log.info("tags_thread:" + tagName + ":started") >- sendtagList = ','.join(tagName.split(':')) >- if(path.startswith('/tags/personal')): >- user_id = self.NSID >- else: >- user_id = None >- for b in self.transfl.getTaggedPhotos(sendtagList, user_id): >- info = self.transfl.parseInfoFromPhoto(b) >- self._mkfileWithMeta(path, info) >- >- def getUnixPerms(self, info): >- mode = info.get('mode') >- if mode is not None: >- return mode >- perms = info.get('perms') >- if perms is None: >- return 0644 >- if perms is "1": # public >- return 0755 >- elif perms is "2": # friends only. Add 1 to 4 in middle letter. >- return 0754 >- elif perms is "3": # family only. Add 2 to 4 in middle letter. >- return 0764 >- elif perms is "4": # friends and family. Add 1+2 to 4 in middle letter. >- return 0774 >- else: >- return 0744 # private >- >- def __getImageTitle(self, title, id, format = "jpg"): >- temp = title.replace('/', '') >-# return "%s_%s.%s" % (temp[:32], id, format) >- # Store the photos original name. Thus, when pictures are uploaded >- # their names would remain as it is, allowing easy resumption of >- # uploading of images, in case some of the photos fail uploading. >- return "%s.%s" % (temp, format) >- >- def _mkfileWithMeta(self, path, info): >- # Don't write the meta information here, because it requires access to >- # the full INFO. Only do with the smaller version of information that >- # is provided. >- if info is None: >- return >- title = info.get("title", "") >- id = info.get("id", "") >- ext = info.get("format", "jpg") >- title = self.__getImageTitle(title, id, ext) >- >- # Refactor this section of code, so that it can be called >- # from read. >- # Figure out a way to retrieve information, which can be >- # used in _mkfile. >- mtime = info.get("dupdate") >- ctime = info.get("dupload") >- perms = self.getUnixPerms(info) >- self._mkfile(path+"/"+title, id=id, mode=perms, mtime=mtime, ctime=ctime) >- self._mkfile(path+'/.'+title+'.meta', id) >- >- def _parsepathid(self, path, id=""): >- #Path and Id may be unicode strings, so encode them to utf8 now before >- #we use them, otherwise python will throw errors when we combine them >- #with regular strings. >- path = path.encode('utf8') >- if id!=0: id = id.encode('utf8') >- parentDir, name = os.path.split(path) >- if parentDir=='': >- parentDir = '/' >- log.debug("parentDir:" + parentDir + ":") >- return path, id, parentDir, name >- >- def _mkdir(self, path, id="", mtime=None, ctime=None): >- path, id, parentDir, name = self._parsepathid(path, id) >- log.debug("Creating directory:" + path) >- self.inodeCache[path] = inodes.DirInode(path, id, mtime=mtime, ctime=ctime) >- if path!='/': >- pinode = self.getInode(parentDir) >- pinode.nlink += 1 >- self.updateInode(parentDir, pinode) >- log.debug("nlink of %s is now %s" % (parentDir, pinode.nlink)) >- >- def _mkfile(self, path, id="", mode=None, >- comm_meta="", mtime=None, ctime=None): >- path, id, parentDir, name = self._parsepathid(path, id) >- log.debug("Creating file:" + path + ":with id:" + id) >- image_name, extension = os.path.splitext(name) >- if not extension: >- log.error("Can't create file without extension") >- return >- fInode = inodes.FileInode(path, id, mode=mode, comm_meta=comm_meta, >- mtime=mtime, ctime=ctime) >- self.inodeCache[path] = fInode >- # Now create the meta info inode if the meta info file exists >- # refactoring: create the meta info inode, regardless of the >- # existence of datapath. >-# path = os.path.join(parentDir, '.' + image_name + '.meta') >-# datapath = os.path.join(flickrfsHome, '.'+id) >-# if os.path.exists(datapath): >-# size = os.path.getsize(datapath) >-# self.inodeCache[path] = FileInode(path, id) >- >- def getattr(self, path): >- # getattr is being called 4-6 times every second for '/' >- # Don't log those calls, as they clutter up the log file. >- if path != "/": >- log.debug("getattr:" + path + ":") >- templist = path.split('/') >- if path.startswith('/sets/'): >- templist[2] = templist[2].split(':')[0] >- elif path.startswith('/stream'): >- templist[1] = templist[1].split(':')[0] >- path = '/'.join(templist) >- >- inode=self.getInode(path) >- if inode: >- #log.debug("inode "+str(inode)) >- statTuple = (inode.mode,inode.ino,inode.dev,inode.nlink, >- inode.uid,inode.gid,inode.size,inode.atime,inode.mtime,inode.ctime) >- #log.debug("statsTuple "+str(statTuple)) >- return statTuple >- else: >- e = OSError("No such file"+path) >- e.errno = ENOENT >- raise e >- >- def readlink(self, path): >- log.debug("readlink") >- return os.readlink(path) >- >- def getdir(self, path, hidden=True): >- log.debug("getdir:" + path) >- templist = [] >- if hidden: >- templist = ['.', '..'] >- for a in self.inodeCache.keys(): >- ind = a.rindex('/') >- if path=='/': >- path="" >- if path==a[:ind]: >- name = a.split('/')[-1] >- if name=="": >- continue >- if hidden and name.startswith('.'): >- templist.append(name) >- elif not name.startswith('.'): >- templist.append(name) >- return map(lambda x: (x,0), templist) >- >- def unlink(self, path, online=True): >- log.debug("unlink:%s:" % (path)) >- if self.inodeCache.has_key(path): >- inode = self.inodeCache.pop(path) >- # Remove the meta data file as well if it exists >- if self.inodeCache.has_key(path + ".meta"): >- self.inodeCache.pop(path + ".meta") >- >- typesinfo = mimetypes.guess_type(path) >- if typesinfo[0] is None or typesinfo[0].count('image')<=0: >- log.debug("unlinked a non-image file:%s:"%(path,)) >- return >- >- if path.startswith('/sets/'): >- ind = path.rindex('/') >- pPath = path[:ind] >- pinode = self.getInode(pPath) >- if online: >- self.transfl.removePhotofromSet(photoId=inode.photoId, >- photosetId=pinode.setId) >- log.info("Photo %s removed from set"%(path,)) >- del inode >- else: >- log.error("Can't find what you want to remove") >- #Dont' raise an exception. Not useful when >- #using editors like Vim. They make loads of >- #crap buffer files >- >- def rmdir(self, path, online=True, recr=False): >- log.debug("rmdir:%s:"%(path)) >- if self.inodeCache.has_key(path): >- for a in self.inodeCache.keys(): >- if a.startswith(path+'/'): >- if recr: >- self.unlink(a, online) >- else: >- e = OSError("Directory not empty") >- e.errno = ENOTEMPTY >- raise e >- else: >- log.error("Can't find the directory you want to remove") >- e = OSError("No such folder"+path) >- e.errno = ENOENT >- raise e >- >- if path=='/sets' or path=='/tags' or path=='/tags/personal' \ >- or path=='/tags/public' or path=='/stream': >- log.debug("rmdir on the framework! I refuse to do anything! <Stubborn>") >- e = OSError("Removal of folder %s not allowed" % (path)) >- e.errno = EPERM >- raise e >- >- ind = path.rindex('/') >- pPath = path[:ind] >- inode = self.inodeCache.pop(path) >- if online and path.startswith('/sets/'): >- self.transfl.deleteSet(inode.setId) >- del inode >- pInode = self.getInode(pPath) >- pInode.nlink -= 1 >- self.updateInode(pPath, pInode) >- >- def symlink(self, path, path1): >- log.debug("symlink") >- return os.symlink(path, path1) >- >- def rename(self, path, path1): >- log.debug("rename:path:%s:to path1:%s:"%(path,path1)) >- #Donot allow Vim to create a file~ >- #Check for .meta in both paths >- if path.count('~')>0 or path1.count('~')>0: >- log.debug("This seems Vim working") >- try: >- #Get inode, but _dont_ remove from cache >- inode = self.getInode(path) >- if inode is not None: >- self.inodeCache[path1] = inode >- except: >- log.debug("Couldn't find inode for:%s:"%(path,)) >- return >- >- #Read from path >- inode = self.getInode(path) >- if inode is None or not hasattr(inode, 'photoId'): >- return >- fname = os.path.join(flickrfsHome, '.'+inode.photoId) >- f = open(fname, 'r') >- buf = f.read() >- f.close() >- >- #Now write to path1 >- inode = self.getInode(path1) >- if inode is None or not hasattr(inode, 'photoId'): >- return >- fname = os.path.join(flickrfsHome, '.'+inode.photoId) >- f = open(fname, 'w') >- f.write(buf) >- f.close() >- inode.size = os.path.getsize(fname) >- self.updateInode(path1, inode) >- retinfo = self.parse(fname, inode.photoId) >- if retinfo.count('Error')>0: >- log.error(retinfo) >- >- def link(self, srcpath, destpath): >- log.debug("link: %s:%s"%(srcpath, destpath)) >- #Add image from stream to set, w/o retrieving >- slist = srcpath.split('/') >- sname_file = slist.pop(-1) >- dlist = destpath.split('/') >- dname_file = dlist.pop(-1) >- error = 0 >- if sname_file=="" or sname_file.startswith('.'): >- error = 1 >- if dname_file=="" or dname_file.startswith('.'): >- error = 1 >- if not destpath.startswith('/sets/'): >- error = 1 >- if error is 1: >- log.error("Linking is allowed only between 2 image files") >- return >- sinode = self.getInode(srcpath) >- self._mkfile(destpath, id=sinode.id, mode=sinode.mode, >- comm_meta=sinode.comm_meta, mtime=sinode.mtime, >- ctime=sinode.ctime) >- parentPath = '/'.join(dlist) >- pinode = self.getInode(parentPath) >- if pinode.setId==0: >- try: >- pinode.setId = self.transfl.createSet(parentPath, sinode.photoId) >- self.updateInode(parentPath, pinode) >- except: >- e = OSError("Can't create a new set") >- e.errno = EIO >- raise e >- else: >- self.transfl.put2Set(pinode.setId, sinode.photoId) >- >- >- def chmod(self, path, mode): >- log.debug("chmod:%s" % path) >- inode = self.getInode(path) >- typesinfo = mimetypes.guess_type(path) >- >- if inode.comm_meta is None: >- log.debug("chmod on directory? No use la!") >- return >- >- elif typesinfo[0] is None or typesinfo[0].count('image')<=0: >- >- os.chmod(path, mode) >- return >- >- elif self.transfl.setPerm(inode.photoId, mode, inode.comm_meta)==True: >- inode.mode = mode >- self.updateInode(path, inode) >- return >- >- def chown(self, path, user, group): >- log.debug("chown. Are you of any use in flickrfs?") >- >- def truncate(self, path, size): >- log.debug("truncate?? Okay okay! I accept your usage:%s:%s"%(path,size)) >- ind = path.rindex('/') >- name_file = path[ind+1:] >- >- typeinfo = mimetypes.guess_type(path) >- if typeinfo[0] is None or typeinfo[0].count('image')<=0: >- inode = self.getInode(path) >- filePath = os.path.join(flickrfsHome, '.'+inode.photoId) >- f = open(filePath, 'w+') >- return f.truncate(size) >- >- def mknod(self, path, mode, dev): >- """ Python has no os.mknod, so we can only do some things """ >- log.debug("mknod? OK! Had a close encounter!!:%s:"%(path,)) >- templist = path.split('/') >- name_file = templist[-1] >- >- if name_file.startswith('.') and name_file.count('.meta') > 0: >- # We need to handle the special case, where some meta files are being >- # created through mknod. Creation of meta files is done when adding >- # images automatically; and they should not go through mknod system call. >- # Editors like Vim, try to generate random swap files when reading >- # meta; and this should be *disallowed*. >- log.debug("mknod for meta file %s? No use!" % path) >- return >- >- if path.startswith('/sets/'): >- templist[2] = templist[2].split(':')[0] >- elif path.startswith('/stream'): >- templist[1] = templist[1].split(':')[0] >- path = '/'.join(templist) >- >- log.debug("mknod: Modified file path %s" % path) >- #Lets guess what kind of a file is this. >- #Is it an image file? or, some other temporary file >- #created by the tools you're using. >- typeinfo = mimetypes.guess_type(path) >- if typeinfo[0] is None or typeinfo[0].count('image') <= 0: >- f = open(os.path.join(flickrfsHome,'.'+name_file), 'w') >- f.close() >- # TODO(manishrjain): This should not be FileInode, it should rather be >- # Inode. >- self.inodeCache[path] = inodes.FileInode(path, name_file, mode=mode) >- else: >- self._mkfile(path, id="NEW", mode=mode) >- >- def mkdir(self, path, mode): >- log.debug("mkdir:" + path + ":") >- if path.startswith("/tags"): >- if path.count('/')==3: #/tags/personal (or private)/dirname ONLY >- self._mkdir(path) >- background(self.tags_thread, path) >- else: >- e = OSError("Not allowed to create directory %s" % path) >- e.errno = EACCES >- raise e >- elif path.startswith("/sets"): >- if path.count('/')==2: #Only allow creation of new set /sets/newset >- self._mkdir(path, id=0) >- #id=0 means that not yet created online >- else: >- e = OSError("Not allowed to create directory %s" % path) >- e.errno = EACCES >- raise e >- elif path=='/stream': >- self._mkdir(path) >- background(timerThread, self.stream_thread, >- self.sync_stream_thread, stream_sync_int) >- >- else: >- e = OSError("Not allowed to create directory %s" % path) >- e.errno = EACCES >- raise e >- >- def utime(self, path, times): >- inode = self.getInode(path) >- inode.atime = times[0] >- inode.mtime = times[1] >- self.updateInode(path, inode) >- return 0 >- >- def open(self, path, flags): >- log.info("open: " + path) >- ind = path.rindex('/') >- name_file = path[ind+1:] >- if name_file.startswith('.') and name_file.endswith('.meta'): >- self.handleAccessToNonImage(path) >- return 0 >- typesinfo = mimetypes.guess_type(path) >- if typesinfo[0] is None or typesinfo[0].count('image')<=0: >- log.debug('open: non-image file found %s' % path) >- self.handleAccessToNonImage(path) >- return 0 >- >- templist = path.split('/') >- if path.startswith('/sets/'): >- templist[2] = templist[2].split(':')[0] >- elif path.startswith('/stream'): >- templist[1] = templist[1].split(':')[0] >- path = '/'.join(templist) >- log.debug("open:After modifying:%s:" % (path)) >- >- inode = self.getInode(path) >- if inode.photoId=="NEW": #Just skip if new (i.e. uploading) >- return 0 >- if self.imgCache.getBuffer(inode.photoId)=="": >- log.debug("Retrieving image from flickr: " + inode.photoId) >- self.imgCache.setBuffer(inode.photoId, >- str(self.transfl.getPhoto(inode.photoId))) >- inode.size = self.imgCache.getBufLen(inode.photoId) >- log.debug("Size of image: " + str(inode.size)) >- self.updateInode(path, inode) >- return 0 >- >- def read(self, path, length, offset): >- log.debug("read:%s:offset:%s:length:%s:" % (path,offset,length)) >- ind = path.rindex('/') >- name_file = path[ind+1:] >- if name_file.startswith('.') and name_file.endswith('.meta'): >- # Check if file is not present. If not, retrieve and >- # create the file locally. >- buf = self.handleReadNonImage(path, length, offset) >- return buf >- typesinfo = mimetypes.guess_type(path) >- if typesinfo[0] is None or typesinfo[0].count('image')<=0: >- return self.handleReadNonImage(path, length, offset) >- return self.handleReadImage(path, length, offset) >- >- def parse(self, fname, photoId): >- cp = ConfigParser.ConfigParser() >- log.debug("Parsing file %s" % fname) >- cp.read(fname) >- log.debug("Read file %s for parsing." % fname) >- options = cp.options('metainfo') >- title='' >- desc='' >- tags='' >- license='' >- if 'description' in options: >- desc = cp.get('metainfo', 'description') >- if 'tags' in options: >- tags = cp.get('metainfo', 'tags') >- if 'title' in options: >- title = cp.get('metainfo', 'title') >- if 'license' in options: >- license = cp.get('metainfo', 'license') >- >- log.debug("Setting metadata for file %s" % fname) >- if self.transfl.setMeta(photoId, title, desc)==False: >- return "Error:Can't set Meta information" >- >- log.debug("Setting tags:%s:"%(fname,)) >- if self.transfl.setTags(photoId, tags)==False: >- log.debug("Setting tags FAILED : %s" % fname) >- return "Error:Can't set tags" >- >- log.debug("Setting license:%s:"%(fname,)) >- if self.transfl.setLicense(photoId, license)==False: >- return "Error:Can't set license" >- >- # except: >- # log.error("Can't parse file:%s:"%(fname,)) >- # return "Error:Can't parse" >- return 'Success:Updated photo:%s:%s:'%(fname,photoId) >- >- ################################################## >- # 'handle' Functions for handling read and writes. >- ################################################## >- def handleAccessToNonImage(self, path): >- inode = self.getInode(path) >- if inode is None: >- log.error("inode doesn't exist:%s:"%(path,)) >- e = OSError("No inode found") >- e.errno = EIO >- raise e >- fname = os.path.join(flickrfsHome, '.'+inode.photoId) #ext >- # Handle the case when file already exists. >- if not os.path.exists(fname) or os.path.getsize(fname) == 0L: >- log.info("Retrieving meta information for file %s and photo id %s" % >- (fname, inode.photoId)) >- INFO = self.transfl.getPhotoInfo(inode.photoId) >- size = self.writeMetaInfo(inode.photoId, INFO) >- log.info("Information has been written for photo id %s" % inode.photoId) >- inode.size = size >- self.updateInode(path, inode) >- time.sleep(1) # Enough time for OS to call for getattr again. >- return inode >- >- def handleReadNonImage(self, path, length, offset): >- inode = self.handleAccessToNonImage(path) >- f = open(os.path.join(flickrfsHome, '.'+inode.photoId), 'r') >- f.seek(offset) >- return f.read(length) >- >- def handleReadImage(self, path, length, offset): >- inode = self.getInode(path) >- if inode is None: >- log.error("inode doesn't exist:%s:"%(path,)) >- e = OSError("No inode found") >- e.errno = EIO >- raise e >- if self.imgCache.getBufLen(inode.photoId) is 0: >- log.debug("Retrieving image from flickr: " + inode.photoId) >- buf = retryFlickrOp(False, self.transfl.getPhoto, >- inode.photoId) >- if len(buf) == 0: >- log.error("Can't retrieve image %s"%(inode.photoId,)) >- e = OSError("Unable to retrieve image.") >- e.errno = EIO >- raise e >- self.imgCache.setBuffer(inode.photoId, buf) >- inode.size = self.imgCache.getBufLen(inode.photoId) >- temp = self.imgCache.getBuffer(inode.photoId, offset, offset+length) >- if len(temp) < length: >- self.imgCache.popBuffer(inode.photoId) >- self.updateInode(path, inode) >- return temp >- >- def handleWriteToNonImage(self, path, buf, off): >- inode = self.handleAccessToNonImage(path) >- fname = os.path.join(flickrfsHome, '.'+inode.photoId) #ext >- log.debug("Writing to :%s:"%(fname,)) >- f = open(fname, 'r+') >- f.seek(off) >- f.write(buf) >- f.close() >- if len(buf)<4096: >- inode.size = os.path.getsize(fname) >- retinfo = self.parse(fname, inode.photoId) >- if retinfo.count('Error')>0: >- e = OSError(retinfo.split(':')[1]) >- e.errno = EIO >- raise e >- self.updateInode(path, inode) >- return len(buf) >- >- def handleUploadingImage(self, path, inode, taglist): >- tags = [ '"%s"'%(a,) for a in taglist] >- tags.append('flickrfs') >- taglist = ' '.join(tags) >- log.info('uploading %s with len %s' % >- (path, self.imgCache.getBufLen(inode.photoId))) >- id = None >- bufData = self.imgCache.getBuffer(inode.photoId) >- bufData = self.imageResize(bufData) >- id = retryFlickrOp(False, self.transfl.uploadfile, >- path, taglist, bufData, inode.mode) >- if id is None: >- log.error("unable to upload file:%s:"%(inode.photoId,)) >- e = OSError("Unable to upload file.") >- e.errno = EIO >- raise e >- self.imgCache.popBuffer(inode.photoId) >- inode.photoId = id >- self.updateInode(path, inode) >- return inode >- >- def handleWriteToBuffer(self, path, buf): >- inode = self.getInode(path) >- if inode is None: >- log.error("inode doesn't exist:%s:"%(path,)) >- e = OSError("No inode found") >- e.errno = EIO >- raise e >- self.imgCache.addBuffer(inode.photoId, buf) >- return inode >- >- def handleWriteAddToSet(self, parentPath, pinode, inode): >- #Create set if it doesn't exist online (i.e. if id=0) >- if pinode.setId is 0: >- # Retry creation of set if unsuccessful. >- pinode.setId = retryFlickrOp(False, self.transfl.createSet, >- parentPath, inode.photoId) >- # If the set is created, then return. >- if pinode.setId is not None: >- self.updateInode(parentPath, pinode) >- return >- else: >- log.error("Unable to create set:%s"%(parentPath,)) >- e = OSError("Unable to create set.") >- e.errno = EIO >- raise e >- else: >- # If the operation put2Set doesn't throw exception, that means >- # that the picture has been successfully added to set. >- # Return in that case, retry otherwise. >- retryFlickrOp(True, self.transfl.put2Set, >- pinode.setId, inode.photoId) >- return >- >- ############################# >- # End of 'handle' Functions. >- ############################# >- >- def write(self, path, buf, off): >- log.debug("write:%s:%s"%(path, off)) >- ind = path.rindex('/') >- name_file = path[ind+1:] >- if name_file.startswith('.') and name_file.count('.meta')>0: >- return self.handleWriteToNonImage(path, buf, off) >- typesinfo = mimetypes.guess_type(path) >- if typesinfo[0] is None or typesinfo[0].count('image')<=0: >- return self.handleWriteToNonImage(path, buf, off) >- templist = path.split('/') >- inode = None >- if path.startswith('/tags'): >- e = OSError("Copying to tags not allowed") >- e.errno = EIO >- raise e >- if path.startswith('/stream'): >- tags = templist[1].split(':') >- templist[1] = tags.pop(0) >- path = '/'.join(templist) >- inode = self.handleWriteToBuffer(path, buf) >- if len(buf) < 4096: >- self.handleUploadingImage(path, inode, tags) >- elif path.startswith('/sets/'): >- setnTags = templist[2].split(':') >- setName = setnTags.pop(0) >- templist[2] = setName >- path = '/'.join(templist) >- inode = self.handleWriteToBuffer(path, buf) >- if len(buf) < 4096: >- templist.pop(-1) >- parentPath = '/'.join(templist) >- pinode = self.getInode(parentPath) >- inode = self.handleUploadingImage(path, inode, setnTags) >- self.handleWriteAddToSet(parentPath, pinode, inode) >- log.debug("After modifying write:%s:%s"%(path, off)) >- if len(buf)<4096: >- templist = path.split('/') >- templist.pop(-1) >- parentPath = '/'.join(templist) >- try: >- self.inodeCache.pop(path) >- except: >- pass >- INFO = self.transfl.getPhotoInfo(inode.photoId) >- info = self.transfl.parseInfoFromFullInfo(inode.photoId, INFO) >- self._mkfileWithMeta(parentPath, info) >- self.writeMetaInfo(inode.photoId, INFO) >- return len(buf) >- >- def getInode(self, path): >- if self.inodeCache.has_key(path): >- #log.debug("Got cached inode: " + path) >- return self.inodeCache[path] >- else: >- #log.debug("No inode??? I DIE!!!") >- return None >- >- def updateInode(self, path, inode): >- self.inodeCache[path] = inode >- >- def release(self, path, flags): >- log.debug("flickrfs.py:Flickrfs:release: %s %s" % (path,flags)) >- return 0 >- >- def statfs(self): >- """ >- Should return a tuple with the following elements in respective order: >- >- F_BSIZE - Preferred file system block size. (int) >- F_FRSIZE - Fundamental file system block size. (int) >- F_BLOCKS - Total number of blocks in the filesystem. (long) >- F_BFREE - Total number of free blocks. (long) >- F_BAVAIL - Free blocks available to non-super user. (long) >- F_FILES - Total number of file nodes. (long) >- F_FFREE - Total number of free file nodes. (long) >- F_FAVAIL - Free nodes available to non-super user. (long) >- F_FLAG - Flags. System dependent: see statvfs() man page. (int) >- F_NAMEMAX - Maximum file name length. (int) >- Feel free to set any of the above values to 0, which tells >- the kernel that the info is not available. >- """ >- block_size = 1024 >- blocks = 0L >- blocks_free = 0L >- files = 0L >- files_free = 0L >- namelen = 255 >- # statfs is being called repeatedly at least once a second. >- # The bandwidth information doesn't change that often, so >- # save upon communication with flickr servers to retrieve this >- # information. Only retrieve it once in a while. >- if self.statfsCounter >= 500 or self.statfsCounter is -1: >- (self.max, self.used) = self.transfl.getBandwidthInfo() >- self.statfsCounter = 0 >- log.info('statfs: Retrieved Bandwidth info: max %s used %s' % >- (self.max, self.used)) >- self.statfsCounter = self.statfsCounter + 1 >- >- if self.max is not None: >- blocks = long(self.max)/block_size >- blocks_used = long(self.used)/block_size >- blocks_free = blocks - blocks_used >- blocks_available = blocks_free >- return (block_size, blocks, blocks_free, blocks_available, >- files, files_free, namelen) >- >- def fsync(self, path, isfsyncfile): >- log.debug("flickrfs.py:Flickrfs:fsync: path=%s, isfsyncfile=%s" % >- (path,isfsyncfile)) >- return 0 >- >- >-if __name__ == '__main__': >- try: >- server = Flickrfs() >- server.multithreaded = 1; >- server.main() >- except KeyError: >- log.error('Got key error. Exiting...') >- sys.exit(0) >diff -Naur flickrfs-1.3.9.orig/inodes.py flickrfs-cvs.orig/inodes.py >--- flickrfs-1.3.9.orig/inodes.py 2007-02-04 06:08:09.000000000 +0100 >+++ flickrfs-cvs.orig/inodes.py 1970-01-01 01:00:00.000000000 +0100 >@@ -1,134 +0,0 @@ >-#=============================================================================== >-# flickrfs - Virtual Filesystem for Flickr >-# Copyright (c) 2005,2006 Manish Rai Jain <manishrjain@gmail.com> >-# >-# This program can be distributed under the terms of the GNU GPL version 2, or >-# its later versions. >-# >-# DISCLAIMER: The API Key and Shared Secret are provided by the author in >-# the hope that it will prevent unnecessary trouble to the end-user. The >-# author will not be liable for any misuse of this API Key/Shared Secret >-# through this application/derived apps/any 3rd party apps using this key. >-#=============================================================================== >- >-__author__ = "Manish Rai Jain (manishrjain@gmail.com)" >-__license__ = "GPLv2 (details at http://www.gnu.org/licenses/licenses.html#GPL)" >- >-import os, sys, time >-from stat import * >-import cPickle >- >-DEFAULTBLOCKSIZE = 4*1024 # 4 KB >- >-class Inode(object): >- """Common base class for all file system objects >- """ >- def __init__(self, path=None, id='', mode=None, >- size=0L, mtime=None, ctime=None): >- self.nlink = 1 >- self.size = size >- self.id = id >- self.mode = mode >- self.ino = long(time.time()) >- self.dev = 409089L >- self.uid = int(os.getuid()) >- self.gid = int(os.getgid()) >- now = int(time.time()) >- self.atime = now >- if mtime is None: >- self.mtime = now >- else: >- self.mtime = int(mtime) >- if ctime is None: >- self.ctime = now >- else: >- self.ctime = int(ctime) >- self.blocksize = DEFAULTBLOCKSIZE >- >-class DirInode(Inode): >- def __init__(self, path=None, id="", mode=None, mtime=None, ctime=None): >- if mode is None: mode = 0755 >- super(DirInode, self).__init__(path, id, mode, 0L, mtime, ctime) >- self.mode = S_IFDIR | self.mode >- self.nlink += 1 >- self.dirfile = "" >- self.setId = self.id >- >- >-class FileInode(Inode): >- def __init__(self, path=None, id="", mode=None, comm_meta="", >- size=0L, mtime=None, ctime=None): >- if mode is None: mode = 0644 >- super(FileInode, self).__init__(path, id, mode, size, mtime, ctime) >- self.mode = S_IFREG | self.mode >- self.photoId = self.id >- self.comm_meta = comm_meta >- >- >-class ImageCache: >- def __init__(self): >- self.bufDict = {} >- >- def setBuffer(self, id, buf): >- self.bufDict[id] = buf >- >- def addBuffer(self, id, inc): >- buf = self.getBuffer(id) >- self.setBuffer(id, buf+inc) >- >- def getBuffer(self, id, start=0, end=0): >- if end == 0: >- return self.bufDict.get(id, "")[start:] >- else: >- return self.bufDict.get(id, "")[start:end] >- >- def getBufLen(self, id): >- return long(len(self.bufDict.get(id, ""))) >- >- def popBuffer(self, id): >- if id in self.bufDict: >- return self.bufDict.pop(id) >- >- >-class InodeCache(dict): >- def __init__(self, dbPath): >- dict.__init__(self) >- try: >- import bsddb >- # If bsddb is available, utilize that package >- # and store the inodes in database. >- self.db = bsddb.btopen(dbPath, flag='c') >- except: >- # Otherwise, store the inodes in memory. >- self.db = {} >- # Keep the keys in memory. >- self.keysCache = set() >- >- def __getitem__(self, key, d=None): >- # key k may be unicode, so convert it to >- # a normal string first. >- if not self.has_key(key): >- return d >- valObjStr = self.db.get(str(key)) >- return cPickle.loads(valObjStr) >- >- def __setitem__(self, key, value): >- self.keysCache.add(key) >- self.db[str(key)] = cPickle.dumps(value) >- >- def get(self, k, d=None): >- return self.__getitem__(k, d) >- >- def keys(self): >- return list(self.keysCache) >- >- def pop(self, k, *args): >- # key k may be unicode, so convert it to >- # a normal string first. >- valObjStr = self.db.pop(str(k), *args) >- self.keysCache.discard(k) >- if valObjStr != None: >- return cPickle.loads(valObjStr) >- >- def has_key(self, k): >- return k in self.keysCache >diff -Naur flickrfs-1.3.9.orig/README flickrfs-cvs.orig/README >--- flickrfs-1.3.9.orig/README 2007-02-04 06:09:01.000000000 +0100 >+++ flickrfs-cvs.orig/README 2008-01-31 20:32:33.000000000 +0100 >@@ -61,3 +61,15 @@ > copy of the extenion name tacked on. > - bugfix: fixed inability to retrieve certain photos. > - bugfix: added missing import of 'sys' module (only used during certain error exits) >+ - 20080127RDM bugfix: added fuse-python 0.2 compatibility by setting the >+ python_fuse_api to 0.1. >+ - 20080128RDM bugfix: use only one thread to populate photos in sets, >+ rather than one thread per set. This seems to fix the problem of >+ having no photos appear when there are lots of sets. >+ - 20080128RDM bugfix: handle the case of the photo not having an >+ 'originalformat' property in getPhotoInfo (default to jpg). >+ - 20080129RDM bugfix: removed limit of 500 images per directory >+ in the 'sets' directory. Bug still exists for other directories. >+ - 20080131RDM feature: made the logging message formats more consistent, and >+ made fuller use of the python logging facility features. >+ - 20080131RDM bugfix: log exceptions in timerThread if they happen >diff -Naur flickrfs-1.3.9.orig/setup.py flickrfs-cvs.orig/setup.py >--- flickrfs-1.3.9.orig/setup.py 1970-01-01 01:00:00.000000000 +0100 >+++ flickrfs-cvs.orig/setup.py 2007-03-29 16:44:25.000000000 +0200 >@@ -0,0 +1,25 @@ >+###################################################################### >+## >+## Copyright (C) 2006, Varun Hiremath <varunhiremath@gmail.com> >+## >+## Filename: setup.py >+## Author: Varun Hiremath <varunhiremath@gmail.com> >+## Description: Installation script >+## License: GPL >+###################################################################### >+ >+import os, sys >+from distutils.core import setup >+ >+PROGRAM_NAME = "flickrfs" >+PROGRAM_VERSION = "1.3.9" >+PROGRAM_URL = "http://manishrjain.googlepages.com/flickrfs" >+ >+setup(name='%s' % (PROGRAM_NAME).lower(), >+ version='%s' % (PROGRAM_VERSION), >+ description="virtual filesystem for flickr online photosharing service", >+ author="Manish Rai Jain", >+ license='GPL-2', >+ url="%s" % (PROGRAM_URL), >+ author_email=" <manishrjain@gmail.com>", >+ packages = ['flickrfs']) >diff -Naur flickrfs-1.3.9.orig/transactions.py flickrfs-cvs.orig/transactions.py >--- flickrfs-1.3.9.orig/transactions.py 2007-02-04 06:08:09.000000000 +0100 >+++ flickrfs-cvs.orig/transactions.py 1970-01-01 01:00:00.000000000 +0100 >@@ -1,395 +0,0 @@ >-#=============================================================================== >-# flickrfs - Virtual Filesystem for Flickr >-# Copyright (c) 2005,2006 Manish Rai Jain <manishrjain@gmail.com> >-# >-# This program can be distributed under the terms of the GNU GPL version 2, or >-# its later versions. >-# >-# DISCLAIMER: The API Key and Shared Secret are provided by the author in >-# the hope that it will prevent unnecessary trouble to the end-user. The >-# author will not be liable for any misuse of this API Key/Shared Secret >-# through this application/derived apps/any 3rd party apps using this key. >-#=============================================================================== >- >-__author__ = "Manish Rai Jain (manishrjain@gmail.com)" >-__license__ = "GPLv2 (details at http://www.gnu.org/licenses/licenses.html#GPL)" >- >-from flickrapi import FlickrAPI >-from traceback import format_exc >-import urllib2 >-import sys >-import string >-import os >-import time >- >-# flickr auth information >-flickrAPIKey = "f8aa9917a9ae5e44a87cae657924f42d" # API key >-flickrSecret = "3fbf7144be7eca28" # shared "secret" >- >-# Utility functions >-def kwdict(**kw): return kw >- >-#Transactions with flickr, wraps FlickrAPI >-# calls in Flickfs-specialized functions. >-class TransFlickr: >- >- extras = "original_format,date_upload,last_update" >- >- def __init__(self, logg, browserName): >- global log >- log = logg >- self.fapi = FlickrAPI(flickrAPIKey, flickrSecret) >- self.user_id = "" >- # proceed with auth >- # TODO use auth.checkToken function if available, >- # and wait after opening browser. >- print "Authorizing with flickr..." >- log.info("Authorizing with flickr...") >- try: >- self.authtoken = self.fapi.getToken(browser=browserName) >- except: >- print ("Can't retrieve token from browser:%s:"%(browserName,)) >- print ("\tIf you're behind a proxy server," >- " first set http_proxy environment variable.") >- print "\tPlease close all your browser windows, and try again" >- log.error(format_exc()) >- log.error("Can't retrieve token from browser:%s:"%(browserName,)) >- sys.exit(-1) >- if self.authtoken == None: >- log.error('Not able to authorize. Exiting...') >- sys.exit(-1) >- #Add some authorization checks here(?) >- print "Authorization complete." >- log.info('Authorization complete') >- >- def uploadfile(self, filepath, taglist, bufData, mode): >- #Set public 4(always), 1(public). Public overwrites f&f. >- public = mode&1 >- #Set friends and family 4(always), 2(family), 1(friends). >- friends = mode>>3 & 1 >- family = mode>>4 & 1 >- #E.g. 745 - 4:No f&f, but 5:public >- #E.g. 754 - 5:friends, but not public >- #E.g. 774 - 7:f&f, but not public >- >- log.info("Uploading file %s with data of len %s" % (filepath, len(bufData))) >- log.info("and tags %s" % str(taglist)) >- log.info("Permissions: Family %s Friends %s Public %s" % >- (family,friends,public)) >- filename = os.path.splitext(os.path.basename(filepath))[0] >- rsp = self.fapi.upload(filename=filepath, jpegData=bufData, >- title=filename, >- tags=taglist, >- is_public=public and "1" or "0", >- is_friend=friends and "1" or "0", >- is_family=family and "1" or "0") >- >- if rsp is None: >- log.error("Seems like we could't write file %s." >- " Running checks." % filepath) >- recent_rsp = None >- trytimes = 2 >- while(trytimes): >- log.info("Trying to retrieve the recently updated photo." >- " Sleeping for 3 seconds...") >- time.sleep(3) >- trytimes -= 1 >- # Keep on trying to retrieve the recently uploaded photo, till we >- # actually get the information, or the function throws an exception. >- while(recent_rsp is None or not recent_rsp): >- recent_rsp = self.fapi.photos_recentlyUpdated( >- auth_token=self.authtoken, min_date='1', per_page='1') >- >- pic = recent_rsp.photos[0].photo[0] >- log.info('Pic looking for is %s' % filename) >- log.info('Most recently updated pic is %s' % pic['title']) >- if filename == pic['title']: >- id = pic['id'] >- log.info("File uploaded %s with photoid %s" % (filepath, id)) >- return id >- return None >- else: >- id = rsp.photoid[0].elementText >- log.info("File uploaded %s with photoid %s" % (filepath, id)) >- return id >- >- def put2Set(self, set_id, photo_id): >- log.info("Uploading photo %s to set id %s" % (photo_id, set_id)) >- rsp = self.fapi.photosets_addPhoto(auth_token=self.authtoken, >- photoset_id=set_id, photo_id=photo_id) >- if rsp: >- log.info("Uploaded photo to set") >- else: >- log.error(rsp.errormsg) >- >- def createSet(self, path, photo_id): >- log.info("Creating set %s with primary photo %s" % (path,photo_id)) >- path, title = os.path.split(path) >- rsp = self.fapi.photosets_create(auth_token=self.authtoken, >- title=title, primary_photo_id=photo_id) >- if rsp: >- log.info("Created set %s" % title) >- return rsp.photoset[0]['id'] >- else: >- log.error(rsp.errormsg) >- >- def deleteSet(self, set_id): >- log.info("Deleting set %s" % set_id) >- if str(set_id)=="0": >- log.info("The set %s is non-existant online." % set_id) >- return >- rsp = self.fapi.photosets_delete(auth_token=self.authtoken, >- photoset_id=set_id) >- if rsp: >- log.info("Deleted set %s." % set_id) >- else: >- log.error(rsp.errormsg) >- >- def getPhotoInfo(self, photoId): >- rsp = self.fapi.photos_getInfo(auth_token=self.authtoken, photo_id=photoId) >- if not rsp: >- log.error("Can't retrieve information about photo %s, with error %s" % >- (photoId, rsp.errormsg)) >- return None >- format = rsp.photo[0]['originalformat'] >- perm_public = rsp.photo[0].visibility[0]['ispublic'] >- perm_family = rsp.photo[0].visibility[0]['isfamily'] >- perm_friend = rsp.photo[0].visibility[0]['isfriend'] >- if perm_public == '1': >- mode = 0755 >- else: >- b_cnt = 4 >- if perm_family == '1': >- b_cnt += 2 >- if perm_friend == '1': >- b_cnt += 1 >- mode = "07" + str(b_cnt) + "4" >- mode = int(mode) >- >- if hasattr(rsp.photo[0],'permissions'): >- permcomment = rsp.photo[0].permissions[0]['permcomment'] >- permaddmeta = rsp.photo[0].permissions[0]['permaddmeta'] >- else: >- permcomment = permaddmeta = [None] >- >- commMeta = '%s%s' % (permcomment,permaddmeta) # Required for chmod. >- desc = rsp.photo[0].description[0].elementText >- title = rsp.photo[0].title[0].elementText >- if hasattr(rsp.photo[0].tags[0], "tag"): >- taglist = [ a.elementText for a in rsp.photo[0].tags[0].tag ] >- else: >- taglist = [] >- license = rsp.photo[0]['license'] >- owner = rsp.photo[0].owner[0]['username'] >- ownerNSID = rsp.photo[0].owner[0]['nsid'] >- url = rsp.photo[0].urls[0].url[0].elementText >- posted = rsp.photo[0].dates[0]['posted'] >- lastupdate = rsp.photo[0].dates[0]['lastupdate'] >- return (format, mode, commMeta, desc, title, taglist, >- license, owner, ownerNSID, url, int(posted), int(lastupdate)) >- >- def setPerm(self, photoId, mode, comm_meta="33"): >- public = mode&1 #Set public 4(always), 1(public). Public overwrites f&f >- #Set friends and family 4(always), 2(family), 1(friends) >- friends = mode>>3 & 1 >- family = mode>>4 & 1 >- if len(comm_meta)<2: >- # This wd patch string index out of range bug, caused >- # because some photos may not have comm_meta value set. >- comm_meta="33" >- rsp = self.fapi.photos_setPerms(auth_token=self.authtoken, >- is_public=str(public), >- is_friend=str(friends), >- is_family=str(family), >- perm_comment=comm_meta[0], >- perm_addmeta=comm_meta[1], >- photo_id=photoId) >- if not rsp: >- log.error("Couldn't set permission for photo %s with error %s" % >- (photoId,rsp.errormsg)) >- return False >- log.info("Permission has been set for photo %s" % photoId) >- return True >- >- def setTags(self, photoId, tags): >- templist = [ '"%s"'%(a,) for a in string.split(tags, ',')] + ['flickrfs'] >- tagstring = ' '.join(templist) >- rsp = self.fapi.photos_setTags(auth_token=self.authtoken, >- photo_id=photoId, tags=tagstring) >- if not rsp: >- log.error("Couldn't set tags %s with error %s" % >- (photoId, rsp.errormsg)) >- return False >- return True >- >- def setMeta(self, photoId, title, desc): >- rsp = self.fapi.photos_setMeta(auth_token=self.authtoken, >- photo_id=photoId, title=title, >- description=desc) >- if not rsp: >- log.error("Couldn't set meta info for photo %s with error" % >- (photoId, rsp.errormsg)) >- return False >- return True >- >- def getLicenses(self): >- rsp = self.fapi.photos_licenses_getInfo() >- if not rsp: >- log.error("Couldn't retrieve licenses with error %s" % rsp.errormsg) >- return None >- licenseDict = {} >- for l in rsp.licenses[0].license: >- licenseDict[l['id']] = l['name'] >- keys = licenseDict.keys() >- keys.sort() >- sortedLicenseList = [] >- for k in keys: >- # Add tuple of license key, and license value. >- sortedLicenseList.append((k, licenseDict[k])) >- return sortedLicenseList >- >- def setLicense(self, photoId, license): >- rsp = self.fapi.photos_licenses_setLicense(auth_token=self.authtoken, >- photo_id=photoId, >- license_id=license) >- if not rsp: >- log.error("Couldn't set license info for photo %s with error %s" % >- (photoId, rsp.errormsg)) >- return False >- return True >- >- def getPhoto(self, photoId): >- rsp = self.fapi.photos_getSizes(auth_token=self.authtoken, >- photo_id=photoId) >- if not rsp: >- log.error("Error while trying to retrieve size information" >- " for photo %s" % photoId) >- return None >- buf = "" >- for a in rsp.sizes[0].size: >- if a['label']=='Original': >- try: >- f = urllib2.urlopen(a['source']) >- buf = f.read() >- except: >- log.error("Exception in getPhoto") >- log.error(format_exc()) >- return "" >- if not buf: >- f = urllib2.urlopen(rsp.sizes[0].size[-1]['source']) >- buf = f.read() >- return buf >- >- def removePhotofromSet(self, photoId, photosetId): >- rsp = self.fapi.photosets_removePhoto(auth_token=self.authtoken, >- photo_id=photoId, >- photoset_id=photosetId) >- if rsp: >- log.info("Photo %s removed from set %s" % (photoId, photosetId)) >- else: >- log.error(rsp.errormsg) >- >- >- def getBandwidthInfo(self): >- log.debug("Retrieving bandwidth information") >- rsp = self.fapi.people_getUploadStatus(auth_token=self.authtoken) >- if not rsp: >- log.error("Can't retrieve bandwidth information: %s" % rsp.errormsg) >- return (None,None) >- bw = rsp.user[0].bandwidth[0] >- log.debug("Bandwidth: max:" + bw['max']) >- log.debug("Bandwidth: used:" + bw['used']) >- return (bw['max'], bw['used']) >- >- def getUserId(self): >- rsp = self.fapi.auth_checkToken(api_key=flickrAPIKey, >- auth_token=self.authtoken) >- if not rsp: >- log.error("Unable to get userid:" + rsp.errormsg) >- return None >- usr = rsp.auth[0].user[0] >- log.info("Got NSID:"+ usr['nsid'] + ":") >- #Set self.user_id to this value >- self.user_id = usr['nsid'] >- return usr['nsid'] >- >- def getPhotosetList(self): >- if self.user_id is "": >- self.getUserId() #This will set the value of self.user_id >- rsp = self.fapi.photosets_getList(auth_token=self.authtoken, >- user_id=self.user_id) >- if not rsp: >- log.error("Error getting photoset list: %s" % (rsp.errormsg)) >- return [] >- if not hasattr(rsp.photosets[0], "photoset"): >- return [] >- return rsp.photosets[0].photoset >- >- def parseInfoFromPhoto(self, photo, perms=None): >- info = {} >- info['id'] = photo['id'] >- info['title'] = photo['title'].replace('/', '_') >- info['format'] = photo['originalformat'] >- info['dupload'] = photo['dateupload'] >- info['dupdate'] = photo['lastupdate'] >- info['perms'] = perms >- return info >- >- def parseInfoFromFullInfo(self, id, fullInfo): >- info = {} >- info['id'] = id >- info['title'] = fullInfo[4] >- info['format'] = fullInfo[0] >- info['dupload'] = fullInfo[10] >- info['dupdate'] = fullInfo[11] >- info['mode'] = fullInfo[1] >- return info >- >- def getPhotosFromPhotoset(self, photoset_id): >- photosPermsMap = {} >- for i in range(1,6): >- rsp = self.fapi.photosets_getPhotos(auth_token=self.authtoken, >- photoset_id=photoset_id, >- extras=self.extras, >- privacy_filter=str(i)) >- if not rsp: >- continue >- for p in rsp.photoset[0].photo: >- photosPermsMap[p] = str(i) >- return photosPermsMap >- >- def getPhotoStream(self, user_id): >- retList = [] >- pageNo = 1 >- maxPage = 1 >- while pageNo<=maxPage: >- log.info("maxPage:%s pageNo:%s"%(maxPage, pageNo)) >- rsp = self.fapi.photos_search(auth_token=self.authtoken, >- user_id=user_id, per_page="500", >- page=str(pageNo), extras=self.extras) >- if not rsp: >- log.error("Can't retrive photos from your stream: %s" % rsp.errormsg) >- return retList >- if not hasattr(rsp.photos[0], 'photo'): >- log.error("Doesn't have attribute photos. Page requested %s" % pageNo) >- return retList >- for a in rsp.photos[0].photo: >- retList.append(a) >- maxPage = int(rsp.photos[0]['pages']) >- pageNo = pageNo + 1 >- return retList >- >- def getTaggedPhotos(self, tags, user_id=None): >- kw = kwdict(auth_token=self.authtoken, tags=tags, tag_mode="all", >- extras=self.extras, per_page="500") >- if user_id is not None: >- kw = kwdict(user_id=user_id, **kw) >- rsp = self.fapi.photos_search(**kw) >- log.debug("Search for photos with tags %s has been" >- " successfully finished." % tags) >- if not rsp: >- log.error("Couldn't search for the photos: %s" % rsp.errormsg) >- return >- if not hasattr(rsp.photos[0], 'photo'): >- return [] >- return rsp.photos[0].photo
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 189000
:
128178
|
168816
| 168818 |
168820