openmeta.rb 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2012-10-12
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
data/Manifest.txt ADDED
@@ -0,0 +1,20 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.md
4
+ Rakefile
5
+ bin/tag
6
+ lib/openmeta.rb
7
+ lib/openmeta/cli.rb
8
+ lib/openmeta/framework/OpenMeta.framework/Headers
9
+ lib/openmeta/framework/OpenMeta.framework/OpenMeta
10
+ lib/openmeta/framework/OpenMeta.framework/Resources
11
+ lib/openmeta/framework/OpenMeta.framework/Versions/A/Headers/OpenMeta.h
12
+ lib/openmeta/framework/OpenMeta.framework/Versions/A/Headers/OpenMetaBackup.h
13
+ lib/openmeta/framework/OpenMeta.framework/Versions/A/Headers/OpenMetaPrefs.h
14
+ lib/openmeta/framework/OpenMeta.framework/Versions/A/OpenMeta
15
+ lib/openmeta/framework/OpenMeta.framework/Versions/A/Resources/Info.plist
16
+ lib/openmeta/framework/OpenMeta.framework/Versions/A/Resources/en.lproj/InfoPlist.strings
17
+ lib/openmeta/framework/OpenMeta.framework/Versions/Current
18
+ lib/openmeta/friendly_errors.rb
19
+ lib/openmeta/ui.rb
20
+ lib/openmeta/version.rb
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # openmeta.rb
2
+
3
+ * https://github.com/zhaocai/openmeta.rb
4
+
5
+
6
+ ## DESCRIPTION:
7
+
8
+ openMeta in Ruby (MacRuby, more exactly!)
9
+
10
+
11
+ ## SYNOPSIS:
12
+
13
+ Port openMeta api to macruby using framework.
14
+
15
+ ## REQUIREMENTS:
16
+
17
+ * macruby
18
+ * thor
19
+
20
+ ## INSTALL:
21
+
22
+ * `gem install openmeta.rb`
23
+
24
+
25
+
26
+
27
+ ## DEVELOPERS:
28
+
29
+ After checking out the source, run:
30
+
31
+ $ rake newb
32
+
33
+ This task will install any missing dependencies, run the tests/specs,
34
+ and generate the RDoc.
35
+
36
+ ## LICENSE:
37
+
38
+ Copyright (c) 2013 Zhao Cai <caizhaoff@gmail.com>
39
+
40
+ This program is free software: you can redistribute it and/or modify it under
41
+ the terms of the GNU General Public License as published by the Free Software
42
+ Foundation, either version 3 of the License, or (at your option)
43
+ any later version.
44
+
45
+ This program is distributed in the hope that it will be useful, but WITHOUT
46
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
47
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
48
+
49
+ You should have received a copy of the GNU General Public License along with
50
+ this program. If not, see <http://www.gnu.org/licenses/>.
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ # require 'rake/distribute'
6
+
7
+ Hoe.plugin :gemspec
8
+ Hoe.plugin :git
9
+ Hoe.plugin :test
10
+
11
+ Hoe.spec 'openmeta.rb' do
12
+
13
+ developer('Zhao Cai', 'caizhaoff@gmail.com')
14
+
15
+ extra_deps << ['thor', '~> 0.17.0']
16
+ end
17
+
18
+ # distribute :FileItem do
19
+ # from "bin/openmeta"
20
+ # to "/usr/local/bin/om", :mode => 0755
21
+ # end
22
+
23
+ bindir="/usr/local/bin"
24
+
25
+ desc "install"
26
+ task :install do
27
+ open("#{bindir}/om", "w") { |f|
28
+ f.puts "#!/bin/sh",
29
+ %Q{VM_OPT_LEVEL=0 macruby #{File.expand_path("bin/openmeta")} "$@"}
30
+ }
31
+ chmod 0755, "#{bindir}/om"
32
+ end
33
+
34
+ desc "uninstall"
35
+ task :uninstall do
36
+ safe_unlink "#{bindir}/om"
37
+ end
38
+
39
+ # vim: syntax=ruby
data/bin/tag ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env macruby
2
+
3
+ ($LOAD_PATH << File.expand_path("../../lib", __FILE__)).uniq!
4
+ require 'rubygems' unless defined? Gem # rubygems is only needed in 1.8
5
+
6
+ require 'openmeta'
7
+
8
+ require 'openmeta/cli'
9
+ require 'openmeta/friendly_errors'
10
+ Openmeta.with_friendly_errors { Openmeta::CLI.start }
@@ -0,0 +1,110 @@
1
+ require 'thor'
2
+ require 'openmeta/ui'
3
+
4
+ module Openmeta
5
+
6
+ class CLI < Thor
7
+
8
+ def initialize(*)
9
+ super
10
+ the_shell = (options["no-color"] || (not STDOUT.tty?) ? Thor::Shell::Basic.new : shell)
11
+ Openmeta.ui = UI::Shell.new(the_shell)
12
+ Openmeta.ui.debug! if options["verbose"]
13
+ end
14
+
15
+ desc "recent", "print recent tags"
16
+ method_option :format,
17
+ :aliases => "-f" ,
18
+ :lazy_default => nil ,
19
+ :type => :string ,
20
+ :desc => "print as yaml/plist"
21
+ def recent
22
+ tags = OpenMetaPrefs.recentTags
23
+
24
+ puts_to(options[:format], tags)
25
+ end
26
+
27
+ desc "clone", "clone openmeta tags and rating"
28
+ method_option :from,
29
+ :aliases => "-f",
30
+ :required => true,
31
+ :type => :string,
32
+ :desc => "clone openmeta tags and rating from the FILE [required]"
33
+ def clone(file)
34
+ unless File.exist?(from_file)
35
+ raise Openmeta::PathError, "#{from_file} does not exist!"
36
+ end
37
+
38
+ tags = Openmeta.get_tags(from_file)
39
+ rating = Openmeta.get_rating(from_file)
40
+
41
+ Openmeta.set_tags(tags, file) unless tags.empty?
42
+ Openmeta.set_rating(rating, file) unless rating == 0.0
43
+ end
44
+
45
+
46
+ desc "get", "get openmeta tags and rating"
47
+ method_option :format,
48
+ :aliases => "-f" ,
49
+ :lazy_default => nil ,
50
+ :type => :string ,
51
+ :desc => "print as yaml/plist"
52
+ def get(file)
53
+
54
+ tags = {
55
+ :tags => Openmeta.get_tags(file),
56
+ :rating => Openmeta.get_rating(file)
57
+ }
58
+
59
+ puts_to(options[:format], tags)
60
+ end
61
+
62
+ desc "clear", "clear openmeta tags and rating"
63
+ def clear(file)
64
+ Openmeta.set_tags([], file)
65
+ Openmeta.set_rating(0.0, file)
66
+ end
67
+
68
+
69
+ desc "set", "set openmeta tags, use ',' to separate multiple tags"
70
+ method_option :tag,
71
+ :aliases => "-t" ,
72
+ :lazy_default => '' ,
73
+ :type => :string ,
74
+ :desc => "set tags, use ',' to separate multiple tags"
75
+ def set(file)
76
+ tags = options[:tag].split(',')
77
+ Openmeta.set_tags(tags, file)
78
+ end
79
+
80
+ desc "rate", "set openmeta rating"
81
+ method_option :rate,
82
+ :aliases => "-r" ,
83
+ :lazy_default => 0.0 ,
84
+ :type => :numeric ,
85
+ :desc => "set rating"
86
+ def rate(file)
87
+ if options[:rate] > 5.0
88
+ raise RangeError, "rating is between [0.0 - 5.0]"
89
+ end
90
+ Openmeta.set_rating(options[:rate], file)
91
+ end
92
+
93
+ private
94
+ def puts_to(format, data)
95
+ unless format
96
+ puts data
97
+ else
98
+ require 'yaml' if format == 'yaml'
99
+ to_format = "to_#{format}"
100
+
101
+ begin
102
+ puts data.send(to_format)
103
+ rescue NoMethodError
104
+ raise Openmeta::NoMethodError, "#{to_format} has not implemented!"
105
+ end
106
+ end
107
+
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,250 @@
1
+ //
2
+ // OpenMeta.h
3
+ // OpenMeta
4
+ //
5
+ // Created by Tom Andersen on 17/07/08.
6
+ //
7
+ /*
8
+ Copyright (c) 2009 ironic software
9
+
10
+ Permission is hereby granted, free of charge, to any person
11
+ obtaining a copy of this software and associated documentation
12
+ files (the "Software"), to deal in the Software without
13
+ restriction, including without limitation the rights to use,
14
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the
16
+ Software is furnished to do so, subject to the following
17
+ conditions:
18
+
19
+ The above copyright notice and this permission notice shall be
20
+ included in all copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
24
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
26
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
27
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
29
+ OTHER DEALINGS IN THE SOFTWARE.
30
+ */
31
+
32
+ #import <Cocoa/Cocoa.h>
33
+
34
+ /*
35
+ Open Meta - why duplicate attributes that are already defined?
36
+
37
+ To answer this, look at an example. kMDItemKeywords:
38
+
39
+ When a file with keywords embedded in it is created or lands on the computer, say for example a PDF file, Spotlight
40
+ will import it. The keywords will be stored under kMDItemKeywords in the Spotlight DB.
41
+
42
+ Now a user wants to set keywords (ie tags) on a file - any file on their computer - whether or not
43
+ the file type supports keywords or not. If Open Meta used kMDItemKeywords to store these - it will work pretty well,
44
+ until the user stored their own tags, on that PDF file that already had embedded keywords. Then all sorts of problems happen:
45
+ 1) The existing keywords are hidden from the user, as keywords set on the xattr will override the ones set in the meta data.
46
+ 2) These hidden keywords will come back when the file is viewed with Preview, or Acrobat, etc.
47
+ 3) If the keywords on the the file are changed inside Preview, then these changes will not show up in spotlight
48
+
49
+ There are two solutions to this sort of problem.
50
+
51
+ One is to edit the 'actual keywords' inside the PDF. This solution quickly gets
52
+ complicated, as for each file type there may be none (eg: text file), one (eg:PDF), several (eg: jpeg, word?)
53
+ places to store keywords, and the software to read and write keywords into all supported file types
54
+ quickly grows to be unmanagable. The solution for text and other non keywordable files
55
+ is to write the tags somewhere else (eg sidecar files).
56
+
57
+ The other solution is the tact taken by Open Meta.
58
+ Keywords are written to their own tag, which is indexed by Spotlight, (kMDItemOMUserTags).
59
+ These tags are independent of kMDItemkeywords.
60
+ They can be written in the exact same very simple manner to each and every file on the file system.
61
+ They do not hide the keywords set on the file.
62
+ Since they are stored in xattrs, they can easily be included or excluded from a file, when
63
+ that file is for instance shipped off to a third party.
64
+ This is useful in order to keep metadata 'in house'. BUT - the data set by OpenMeta is not 'in the file' the same
65
+ way that tags set on a jpeg are 'in' the EXIF portion of the file when bridge does it.
66
+ The Open Meta tags follow the file around on the OS - through backups, copies and moves.
67
+
68
+ Other keys
69
+ ----------------
70
+ I used to consider it 'wrong' to be able to override kMDItem* stuff with OpenMeta, and it is for tags. Tags though are a special case,
71
+ in that there could be for instance 6 keywords (relevant - ish) set on a PDF, and you want to add the open meta tag 'special' to it. You don't want to
72
+ lose the 6 keywords do you? There are also lots of images from image houses that have a lot of keyword 'noise' in them that you might not want
73
+ cluttering up your tags that you have set. i have found png files with 50 keywords set. html can also be bad for this. So users want the ability to only look at tags that they have set,
74
+ or a combination of keywords and tags.
75
+ BUT - look at ratings - ratings are just one number - it is likely that you don't want to as a user, have to think about 2 different places ratings
76
+ could be stored, (like tags vs keywords), but would rather have just the one concept of 'rating'. It is also ok, even deisrable, to be able to override the rating
77
+ on a file. So ratings _should_ use kMDItemStarRating.
78
+ Also look at 'less used' keys - like camera (kMDItemAcquisitionMake and kMDItemAcquisitionModel) - although they will be set on perhaps thousands of photos in
79
+ what if you run into a PDF that is a picture taken with a camera, and you want to tag that? openmeta will allow you to to tag it with kMDItemAcquisitionMake and kMDItemAcquisitionModel
80
+ so that searches for camera make an model do not have to 'know about openmeta' to work.
81
+ Plus it's always good to keep the number of keys down.
82
+
83
+ What about namespaces?
84
+ ----------------------
85
+ Open Meta is a clean simple way to set user entered searchable metadata on any file on Mac OS X.
86
+ Concepts like namespaces are not encouraged, as most users have no idea what a namespace is. The tradeoff is a
87
+ small amount of _understandable_ ambiguity - searching for Tags:apple (i.e. kMDItemOMUserTags == "apple"cd) will find
88
+ all files having to do with both the fruit one can eat, and the company that makes computers. Users expect this.
89
+ With namespaces an improperly constructed query will usually result in 'no matches'.
90
+ */
91
+
92
+
93
+ /*
94
+ Note on Backup.
95
+
96
+ Whenever you set an item with kMDItemOM* as the key, openmeta will make sure that there is a backup made of the tags, etc, that
97
+ you have set on the file. The backups go into the folder ~/Library/Application Support/OpenMeta/backups.noindex/2009 etc. The backups are
98
+ one file per item, and are on a month by month basis. This may be all the backup you need, as time machine will back these up.
99
+ The .noindex suffix on the backup folder stops Spotlight from indexing the files.
100
+
101
+ Note on Backup - "Time Machine", etc.
102
+ When you set an xattr on a file, the modification date on the file is NOT changed,
103
+ but something called the status change time - does change.
104
+ Time Machine, however, will only back up on modified time changes - i.e. it only looks at st_mtimespec, ignoring st_ctimespec.
105
+ This is a deliberate decision on TimeMachine's part.
106
+ So if you want your xattrs backed up - you need to set the modifcation date on the file to a newer date - utimes() is the call for this.
107
+ It may be that you do not want to change the file modification date for each OpenMeta item that you write. If you do change the modification date,
108
+ it may make sense to not change it 'by much' - thus preserving most of the
109
+ meaning of the modification date, while still allowing TimeMachine to back the file up.
110
+ */
111
+
112
+
113
+ // OpenMeta: Open metadata format for OS X. Data is stored in xattr.
114
+ // Some items are reflected in the Spotlight Database, others not.
115
+ // ---------------------
116
+ // This system allows someone to set metadata on a file, and have that metadata searchable in spotlight.
117
+ // Several open meta keys are defined: See the schema.xml file in the OpenMeta spotlight plugin for the complete list.
118
+ //
119
+ // User Entered Tags: Tags that users have added for workflow and organizational reasons.
120
+ //
121
+ // Bookmarks: URLs associated with a document
122
+ //
123
+ // Workflow: people, companies, etc that are in the workflow for this document
124
+ //
125
+ // Projects: Projects that this file is relevant to
126
+
127
+ // on success return nil error
128
+ // If setting/getting user tags failed we return a negative error code for one of our errors, or the error code from the underlying api (setxattr)
129
+ // If there is errno set after a call we return that. errno codes seem to be positive numbers
130
+ // We use NSError, and you can pass in nil if you are not interested in an error.
131
+ #define OM_ParamError (-1)
132
+ #define OM_NoDataFromPropertyListError (-2)
133
+ #define OM_NoMDItemFoundError (-3)
134
+ #define OM_CantSetMetadataError (-4)
135
+ #define OM_MetaTooBigError (-5)
136
+ // A very common errno error code is ENOATTR - the attribute is not set on the file. - which we don't consider an error
137
+
138
+ extern NSString* const kMDItemOMUserTags;
139
+ extern NSString* const kMDItemOMUserTagTime;
140
+ extern NSString* const kMDItemOMDocumentDate;
141
+ extern NSString* const kMDItemOMBookmarks; // list of urls - bookmarks as nsarray nsstring
142
+ extern NSString* const kMDItemOMUserTagApplication;
143
+
144
+ extern const double kMDItemOMMaxRating;
145
+
146
+ // kMDItemKeywords
147
+ @interface OpenMeta : NSObject {
148
+
149
+ }
150
+
151
+ // User tags - an array of tags as entered by the user. This is not the place to
152
+ // store program generated gook, like GUIDs or urls, etc.
153
+ // It is not nice to erase tags that are already set (unless the user has deleted them using your UI)
154
+ // so ususally you would do a getUserTags, then merge/edit/ etc, followed by a setUserTags
155
+ // Tags - NSStrings - conceptually utf8 - any characters allowed, spaces, commas, etc.
156
+ // Case sensitive or not? Case preserving. Order of tags is not guaranteed, but it is attempted.
157
+ // setUserTags will remove duplicates from the array, using case preserving rules.
158
+ +(NSError*)setUserTags:(NSArray*)tags path:(NSString*)path;
159
+ +(NSArray*)getUserTags:(NSString*)path error:(NSError**)error;
160
+ +(NSError*)addUserTags:(NSArray*)tags path:(NSString*)path;
161
+ +(NSError*)clearUserTags:(NSArray*)tags path:(NSString*)path;
162
+
163
+
164
+ // To change tags on groups of files:
165
+ // You first obtain the common tags in a list of files,
166
+ // then edit those common tags, then set the changes
167
+ // you need to pass in the original common tags when writing the new tags out,
168
+ // so that we can be sure we are not overwriting other changes made by other users, etc
169
+ // during the edit cycle:
170
+ // These calls are case preserving and case insensitive. So Apple and apple will both be the 'same' tag on reading
171
+ +(NSError*)setCommonUserTags:(NSArray*)paths originalCommonTags:(NSArray*)originalTags replaceWith:(NSArray*)newTags;
172
+ +(NSArray*)getCommonUserTags:(NSArray*)paths error:(NSError**)error;
173
+
174
+
175
+ // Ratings are 0 - 5 stars. Setting a rating to 0 is the same as having no rating.
176
+ // rating is 0 - 5 floating point, so you have plenty of room. I clamp 0 - 5 on setting it.
177
+ // passing 0 to setRating removes the rating. Also I return 0 if I can't find a rating.
178
+ // Users have a hard time conceptualizing the difference between having no rating set and a rating of 0
179
+ +(NSError*)setRating:(double)rating05 path:(NSString*)path;
180
+ +(double)getRating:(NSString*)path error:(NSError**)error;
181
+
182
+ // simplest way to set metadata that will be picked up by spotlight:
183
+ // The string will be stored in the spotlight datastore. Likely you will not want
184
+ // to set large strings with this, as it will be hard on the spotlight db.
185
+ +(NSError*)setString:(NSString*)string keyName:(NSString*)keyName path:(NSString*)path;
186
+ +(NSString*)getString:(NSString*)keyName path:(NSString*)path error:(NSError**)error;
187
+
188
+ +(NSError*)setNumber:(NSNumber*)number keyName:(NSString *)keyName path:(NSString *)path;
189
+ +(NSNumber *)getNumber:(NSString*)keyName path:(NSString*)path error:(NSError**)error;
190
+
191
+ +(NSError*)setBoolValue:(BOOL)boolValue keyName:(NSString *)keyName path:(NSString *)path;
192
+ +(BOOL)getBoolValue:(NSString*)keyName path:(NSString*)path error:(NSError**)error;
193
+
194
+ +(NSError*)setDate:(NSDate *)date keyName:(NSString *)keyName path:(NSString *)path;
195
+ +(NSDate *)getDate:(NSString*)keyName path:(NSString*)path error:(NSError**)error;
196
+
197
+ +(NSError*)setData:(NSData *)data keyName:(NSString *)keyName path:(NSString *)path;
198
+ +(NSData *)getData:(NSString*)keyName path:(NSString*)path error:(NSError**)error;
199
+
200
+
201
+ // If you have a 'lot' (ie 200 bytes to 4k) to set as a metadata on a file, then what you want to do
202
+ // is use the setDictionaries call. You organize your data in an array of dictionaries,
203
+ // and each dict will be put into the metadata store and NOT be indexed by spotlight.
204
+ // In each dictionary, you optionally set one item with the key @"name" and THAT information will be stored in the spotlight DB
205
+ // Say you set keyName to 'kMDItemOMLastPrintedInfo', then there would be an xattr with the name 'kMDItemOMLastPrintedInfo' that is an array of nsdictionaries
206
+ // AND an xattr set on the file with com.apple.metadata:kMDItemOMLastPrintedInfo' which will be an array of the 'names' from the dicts.
207
+ // the 'name' would usually be used for search purposes. Other data can be 'anything'
208
+
209
+
210
+ // for meta data in arrays: The add call weeds out duplicates
211
+ +(NSArray*)getNSArrayMetaData:(NSString*)metaDataKey path:(NSString*)path error:(NSError**)error;
212
+ +(NSError*)setNSArrayMetaData:(NSArray*)array metaDataKey:(NSString*)metaDataKey path:(NSString*)path;
213
+ +(NSError*)addToNSArrayMetaData:(NSArray*)itemsToAdd metaDataKey:(NSString*)metaDataKey path:(NSString*)path;
214
+
215
+
216
+ // extended attributes:
217
+ // These getters and setters are to set xattr data that will be read and indexed by spotlight
218
+ // If you pass large amounts of data or objects like dictionaries that spotlght cannot index, results are undefined.
219
+ // The only things that spotlight can handle (as far as I know) are small arrays and nsstrings, nsdates, and nsnumbers
220
+ +(id)getXAttrMetaData:(NSString*)metaDataKey path:(NSString*)path error:(NSError**)error;
221
+ +(NSError*)setXAttrMetaData:(id)plistObject metaDataKey:(NSString*)metaDataKey path:(NSString*)path;
222
+
223
+ // dictionaries:
224
+ // Spotlight can't have dictionaries in it's database.
225
+ // remember that. You can store dictionaries using getXAttrMetaDataNoSpotlightMirror, or just setXAttr
226
+
227
+
228
+ // to set data with no spotlight mirror, use this. omKey is something like kMDItemOMSomeKeyThatsNotNeededInSpotlight
229
+ +(id)getXAttrMetaDataNoSpotlightMirror:(NSString*)omKey path:(NSString*)path error:(NSError**)error;
230
+ +(NSError*)setXAttrNoSpotlightMirror:(id)plistObject omKey:(NSString*)omKey path:(NSString*)path;
231
+
232
+ // These getters and setters are to set xattr data that will be NOT read and indexed by spotlight - nor are openmeta time stamps set, nor is restore done on backup.
233
+ // The passed plist object will be converted to data as a binary plist object. (plist object is for example an nsdictionary or nsarray)
234
+ // You can pass data up to 4k (or close to that depending on how much the data takes up in binary plist format)
235
+ +(id)getXAttr:(NSString*)inKeyName path:(NSString*)path error:(NSError**)error;
236
+ +(NSError*)setXAttr:(id)plistObject forKey:(NSString*)inKeyName path:(NSString*)path;
237
+
238
+
239
+ // utils
240
+ +(NSArray*)orderedArrayWithDict:(NSDictionary*)inTags sortHint:(NSArray*)inSortedItems;
241
+
242
+ // this call makes sure that all the keys in the OpenMeta kMDItemOM group get seen by Spotlight, so that the associated strings.xml. strings.schema, etc can work.
243
+ +(void)registerUsualOMAttributes; // just call this on launch if you want to be sure.
244
+ +(void)registerOMAttributes:(NSDictionary*)typicalAttributes forAppName:(NSString*)appName;
245
+
246
+ +(NSString*)spotlightKey:(NSString*)inKeyName;
247
+ +(NSString*)openmetaKey:(NSString*)inKeyName;
248
+ +(NSString*)openmetaTimeKey:(NSString*)inKeyName;
249
+
250
+ @end
@@ -0,0 +1,106 @@
1
+ //
2
+ // OpenMetaBackup.h
3
+ // Fresh
4
+ //
5
+ // Created by Tom Andersen on 26/01/09.
6
+ // Copyright 2009 __MyCompanyName__. All rights reserved.
7
+ //
8
+
9
+ #import <Cocoa/Cocoa.h>
10
+
11
+ /*
12
+ Backing up xattr:
13
+
14
+ Extended Attributes are supposed to 'not dissapear' they are connected to a file. When a user does a 'save' on a file, the default unix and cocoa apis will
15
+ preserve all the xattrs. BUT there are applications (notably Adobe CS 4 products, like Photoshop) which do a save in such a way as to wipe out all xattrs on each file
16
+ at each save. Not good. Also when using tools like subversion, etc where files are downloaded from the internet, xattrs can be easily lost.
17
+
18
+ So OpenMeta makes a backup file for every edit to xattrs when using the API.
19
+
20
+ The exact details may change in the future, the idea though is that when we go to retrieve tags for a file, if we find no tags, we check to see if there is a
21
+ backup file we can restore from. If no backup file, then there really are no tags on the file.
22
+
23
+ The backups are stored in the home folder of the person who made the change to the file, in ~/Library/OpenMeta/backups.noindex. This works well for most usage scenarios,
24
+ but with network volumes or mobile volumes, it may be better to store the backups in a predetermined place on the volume.
25
+
26
+ */
27
+
28
+ @interface OpenMetaBackup : NSObject {
29
+
30
+ }
31
+
32
+ // individual file handling:
33
+ // ---------------------------
34
+
35
+ // backup is called automatically each time you set any attribute with kMDItemOM*, so you actually don't have to call this.
36
+ +(void)backupMetadata:(NSString*)inPath;
37
+
38
+ // restore is called for you ONLY on tags and ratings. If you are using OpenMeta and not using tags or ratings, you need to call this first, in case the
39
+ // OpenMeta data has been deleted by an application like Adobe Photoshop CS4 which removes all metadata on files.
40
+ +(void)restoreMetadata:(NSString*)inPath;
41
+
42
+
43
+ // Restoring all metadata
44
+ // ---------------------------
45
+ // call this to restore all backed up meta data. Call when? On every launch is likely too much.
46
+ // if you call to tell user when done (ie initiated from a menu command, or similar), then the process runs at full speed, putting up a dialog when done.
47
+ // it does run in a thread, though..
48
+ +(void)restoreAllMetadataOnBackgroundThread:(BOOL)tellUserWhenDone;
49
+
50
+
51
+ // live repair
52
+ // Adobe products in particular can erase all xattrs when performing a simple 'Save' on a file - yeuuch
53
+ // Open Meta will only usually restore tags if a file is re-opened for tagging. This
54
+ // thread attempts to repair the files as they are damaged
55
+ // Right now i don't personally run this thread in any ironic apps, other than our 'OM fix' app.
56
+ +(void)startLiveRepairThread;
57
+ +(void)stopLiveRepairThread; // you can't start/stop the live repair
58
+
59
+ // upgrade to open meta kMDItemOM user tags.
60
+ +(void)upgradeOpenMetaTokMDItemOM;
61
+
62
+ // shutting down OpenMeta backup and restore systems
63
+ // ---------------------------
64
+
65
+ // call this on quit, to leave time for any restores to safely, possibly, partially finish
66
+ +(void)appIsTerminating;
67
+
68
+ // for OpenMeta.m use
69
+ +(BOOL)attributeKeyMeansAutomaticBackup:(NSString*)attrName;
70
+ +(void)copyTagsFromOldKeyTokMDItemOMIfNeeded:(NSString*)inPath;
71
+
72
+ // these calls are async and send a notification on main thread when they are done.
73
+ +(void)backupMetadataToSingleFile:(NSArray*)inKeys toFile:(NSString*)toFile;
74
+ +(void)restoreMetadataFromSingleFile:(NSString*)inBackupFile;
75
+
76
+ @end
77
+
78
+ // class for single file bullk backup
79
+ @interface OpenMetaBackupOperation : NSOperation
80
+ {
81
+ NSString* singleFile;
82
+ NSArray* keysToSearch;
83
+ NSDictionary* returnDict;
84
+ BOOL writeFile;
85
+ }
86
+
87
+ @property (retain) NSString* singleFile;
88
+ @property (retain) NSArray* keysToSearch;
89
+ @property (retain) NSDictionary* returnDict;
90
+ @property BOOL writeFile;
91
+
92
+ @end
93
+
94
+
95
+ // class for converting from kOMUserTags to kMDItemOMUserTags
96
+ // it actually erases no data, it just adds a few fields of new data
97
+ @interface OpenMetaUpgradeOperation : NSOperation
98
+ {
99
+ NSString* mdQueryString;
100
+ NSString* keyToSet;
101
+ }
102
+ @property (retain) NSString* mdQueryString;
103
+ @property (retain) NSString* keyToSet;
104
+ @end
105
+
106
+
@@ -0,0 +1,29 @@
1
+ //
2
+ // OpenMetaPrefs.h
3
+ // adobeFix
4
+ //
5
+ // Created by Tom Andersen on 24/04/09.
6
+ // Copyright 2009 __MyCompanyName__. All rights reserved.
7
+ //
8
+
9
+ #import <Cocoa/Cocoa.h>
10
+
11
+
12
+ @interface OpenMetaPrefs : NSObject {
13
+
14
+ }
15
+ // prefs support - this allows a common set of recently entered tags to be kept:
16
+ // recently entered tags support:
17
+ + (NSArray*)recentTags; // an array of NSStrings, sorted by most recently added at the top. Case preserved.
18
+
19
+ // this call is how you maintain the list of recent tags. When a user edits a list of tags on a doc, pass in the originals as 'old' and the entire set of changed ones as new.
20
+ + (void)updatePrefsRecentTags:(NSArray*)oldTags newTags:(NSArray*)newTags;
21
+
22
+ // To be really sure that the prefs loaded correctly for recentTags, you could call this, but calling it at each keystroke as someone types will likely get slow.
23
+ // note that it is automatically called every few seconds when you call recentTags, so you likely don't need to call this.
24
+ + (void)synchPrefs;
25
+
26
+ // for app store, etc - you can set the prefs to use your own app only.. DONT call with .plist on the prefs file name..
27
+ +(void)setPrefsFile:(NSString*)prefsFileToUse;
28
+
29
+ @end
@@ -0,0 +1,40 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>BuildMachineOSBuild</key>
6
+ <string>12C60</string>
7
+ <key>CFBundleDevelopmentRegion</key>
8
+ <string>English</string>
9
+ <key>CFBundleExecutable</key>
10
+ <string>OpenMeta</string>
11
+ <key>CFBundleIdentifier</key>
12
+ <string>com.openmeta.OpenMeta</string>
13
+ <key>CFBundleInfoDictionaryVersion</key>
14
+ <string>6.0</string>
15
+ <key>CFBundleName</key>
16
+ <string>OpenMeta</string>
17
+ <key>CFBundlePackageType</key>
18
+ <string>FMWK</string>
19
+ <key>CFBundleShortVersionString</key>
20
+ <string>1.0</string>
21
+ <key>CFBundleSignature</key>
22
+ <string>????</string>
23
+ <key>CFBundleVersion</key>
24
+ <string>1</string>
25
+ <key>DTCompiler</key>
26
+ <string></string>
27
+ <key>DTPlatformBuild</key>
28
+ <string>4G2008a</string>
29
+ <key>DTPlatformVersion</key>
30
+ <string>GM</string>
31
+ <key>DTSDKBuild</key>
32
+ <string>12C37</string>
33
+ <key>DTSDKName</key>
34
+ <string>macosx10.8</string>
35
+ <key>DTXcode</key>
36
+ <string>0452</string>
37
+ <key>DTXcodeBuild</key>
38
+ <string>4G2008a</string>
39
+ </dict>
40
+ </plist>
@@ -0,0 +1,23 @@
1
+ module Openmeta
2
+ def self.with_friendly_errors
3
+ begin
4
+ yield
5
+ rescue Openmeta::OpenmetaError => e
6
+ Openmeta.ui.error e.message
7
+ Openmeta.ui.debug e.backtrace.join("\n")
8
+ exit e.status_code
9
+ rescue Interrupt => e
10
+ Openmeta.ui.error "\nQuitting..."
11
+ Openmeta.ui.debug e.backtrace.join("\n")
12
+ exit 1
13
+ rescue SystemExit => e
14
+ exit e.status
15
+ rescue Exception => e
16
+ Openmeta.ui.error(
17
+ "Unfortunately, a fatal error has occurred. Please see the Openmeta \n" \
18
+ "troubleshooting documentation. Thanks!\n #{e.inspect} #{e.backtrace.join("\n")}\n")
19
+ raise e
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,73 @@
1
+ module Openmeta
2
+ class UI
3
+ def warn(message, newline = nil)
4
+ end
5
+
6
+ def debug(message, newline = nil)
7
+ end
8
+
9
+ def error(message, newline = nil)
10
+ end
11
+
12
+ def info(message, newline = nil)
13
+ end
14
+
15
+ def confirm(message, newline = nil)
16
+ end
17
+
18
+ def debug?
19
+ false
20
+ end
21
+
22
+ class Shell < UI
23
+ attr_writer :shell
24
+
25
+ def initialize(shell)
26
+ @shell = shell
27
+ @quiet = false
28
+ @debug = ENV['DEBUG']
29
+ end
30
+
31
+ def info(msg, newline = nil)
32
+ tell_me(msg, nil, newline) if !@quiet
33
+ end
34
+
35
+ def confirm(msg, newline = nil)
36
+ tell_me(msg, :green, newline) if !@quiet
37
+ end
38
+
39
+ def warn(msg, newline = nil)
40
+ tell_me(msg, :yellow, newline)
41
+ end
42
+
43
+ def error(msg, newline = nil)
44
+ tell_me(msg, :red, newline)
45
+ end
46
+
47
+ def be_quiet!
48
+ @quiet = true
49
+ end
50
+
51
+ def debug?
52
+ # needs to be false instead of nil to be newline param to other methods
53
+ !!@debug && !@quiet
54
+ end
55
+
56
+ def debug!
57
+ @debug = true
58
+ end
59
+
60
+ def debug(msg, newline = nil)
61
+ tell_me(msg, nil, newline) if debug?
62
+ end
63
+
64
+ private
65
+ # valimism
66
+ def tell_me(msg, color = nil, newline = nil)
67
+ newline.nil? ? @shell.say(msg, color) : @shell.say(msg, color, newline)
68
+ end
69
+ end
70
+
71
+ end
72
+ end
73
+
@@ -0,0 +1,4 @@
1
+ module Openmeta
2
+ VERSION = '1.0.0'
3
+ end
4
+
data/lib/openmeta.rb ADDED
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env macruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ def is_macruby?
5
+ begin
6
+ MACRUBY_VERSION
7
+ return true
8
+ rescue
9
+ return false
10
+ end
11
+ end
12
+
13
+ if is_macruby?
14
+ ($LOAD_PATH << File.expand_path("../openmeta/framework", __FILE__)).uniq!
15
+ framework "OpenMeta"
16
+ else
17
+ raise NotImplementedError, "[Openmeta] only work with macruby"
18
+ end
19
+
20
+ require 'openmeta/version'
21
+ require 'openmeta/ui'
22
+ module Openmeta
23
+ class OpenmetaError < RuntimeError
24
+ def self.status_code(code)
25
+ define_method(:status_code) { code }
26
+ end
27
+ end
28
+
29
+ class ObjCError < OpenmetaError; status_code(1) ; end
30
+ class RangeError < OpenmetaError; status_code(2) ; end
31
+ class NoMethodError < OpenmetaError; status_code(13) ; end
32
+ class PathError < OpenmetaError; status_code(14) ; end
33
+ class DslError < OpenmetaError; status_code(15) ; end
34
+
35
+
36
+ class << self
37
+ attr_writer :ui, :error
38
+
39
+ def ui
40
+ @ui ||= UI.new
41
+ end
42
+
43
+ def error
44
+ @error ||= error = Pointer.new(:id)
45
+ end
46
+
47
+ def get_tags(path)
48
+ tags = OpenMeta.getUserTags(File.expand_path(path), error:error)
49
+ if error.value
50
+ raise Openmeta::ObjCError, error.value.inspect
51
+ end
52
+ tags
53
+ end
54
+
55
+ def set_tags(tags, path)
56
+ e = OpenMeta.setUserTags(tags, path:File.expand_path(path))
57
+ if e
58
+ raise Openmeta::ObjCError, e.inspect
59
+ end
60
+ end
61
+
62
+ def get_rating(path)
63
+ rating = OpenMeta.getRating(File.expand_path(path), error:error)
64
+ if error.value
65
+ raise Openmeta::ObjCError, error.value.inspect
66
+ end
67
+ rating
68
+ end
69
+
70
+ def set_rating(rating, path)
71
+ e = OpenMeta.setRating(rating, path:File.expand_path(path))
72
+ if e
73
+ raise Openmeta::ObjCError, e.inspect
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+
81
+
82
+
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: openmeta.rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Zhao Cai
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: thor
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.17.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 0.17.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rdoc
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '3.10'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '3.10'
46
+ - !ruby/object:Gem::Dependency
47
+ name: hoe
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '3.5'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '3.5'
62
+ description: openMeta in Ruby (MacRuby, more exactly!)
63
+ email:
64
+ - caizhaoff@gmail.com
65
+ executables:
66
+ - tag
67
+ extensions: []
68
+ extra_rdoc_files:
69
+ - History.txt
70
+ - Manifest.txt
71
+ files:
72
+ - History.txt
73
+ - Manifest.txt
74
+ - README.md
75
+ - Rakefile
76
+ - bin/tag
77
+ - lib/openmeta.rb
78
+ - lib/openmeta/cli.rb
79
+ - lib/openmeta/framework/OpenMeta.framework/OpenMeta
80
+ - lib/openmeta/framework/OpenMeta.framework/Versions/A/Headers/OpenMeta.h
81
+ - lib/openmeta/framework/OpenMeta.framework/Versions/A/Headers/OpenMetaBackup.h
82
+ - lib/openmeta/framework/OpenMeta.framework/Versions/A/Headers/OpenMetaPrefs.h
83
+ - lib/openmeta/framework/OpenMeta.framework/Versions/A/OpenMeta
84
+ - lib/openmeta/framework/OpenMeta.framework/Versions/A/Resources/Info.plist
85
+ - lib/openmeta/framework/OpenMeta.framework/Versions/A/Resources/en.lproj/InfoPlist.strings
86
+ - lib/openmeta/friendly_errors.rb
87
+ - lib/openmeta/ui.rb
88
+ - lib/openmeta/version.rb
89
+ homepage: https://github.com/zhaocai/openmeta.rb
90
+ licenses: []
91
+ post_install_message:
92
+ rdoc_options:
93
+ - --main
94
+ - README.md
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project: openmeta.rb
111
+ rubygems_version: 1.8.24
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: openMeta in Ruby (MacRuby, more exactly!)
115
+ test_files: []