blob: 6b8e6f415b3d4c71e08c3b9599db75f827dba2cd [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "rlz/mac/lib/rlz_value_store_mac.h"
#include "base/mac/foundation_util.h"
#include "base/file_path.h"
#include "base/logging.h"
#include "base/sys_string_conversions.h"
#include "rlz/lib/assert.h"
#include "rlz/lib/lib_values.h"
#include "rlz/lib/rlz_lib.h"
#include "rlz/lib/recursive_cross_process_lock_posix.h"
#import <Foundation/Foundation.h>
#include <pthread.h>
using base::mac::ObjCCast;
namespace rlz_lib {
// These are written to disk and should not be changed.
NSString* const kPingTimeKey = @"pingTime";
NSString* const kAccessPointKey = @"accessPoints";
NSString* const kProductEventKey = @"productEvents";
NSString* const kStatefulEventKey = @"statefulEvents";
namespace {
NSString* GetNSProductName(Product product) {
return base::SysUTF8ToNSString(GetProductName(product));
}
NSString* GetNSAccessPointName(AccessPoint p) {
return base::SysUTF8ToNSString(GetAccessPointName(p));
}
// Retrieves a subdictionary in |p| for key |k|, creating it if necessary.
// If the dictionary contains an object for |k| that is not a mutable
// dictionary, that object is replaced with an empty mutable dictinary.
NSMutableDictionary* GetOrCreateDict(
NSMutableDictionary* p, NSString* k) {
NSMutableDictionary* d = ObjCCast<NSMutableDictionary>([p objectForKey:k]);
if (!d) {
d = [NSMutableDictionary dictionaryWithCapacity:0];
[p setObject:d forKey:k];
}
return d;
}
} // namespace
RlzValueStoreMac::RlzValueStoreMac(NSMutableDictionary* dict,
NSString* plist_path)
: dict_([dict retain]), plist_path_([plist_path retain]) {
}
RlzValueStoreMac::~RlzValueStoreMac() {
}
bool RlzValueStoreMac::HasAccess(AccessType type) {
NSFileManager* manager = [NSFileManager defaultManager];
switch (type) {
case kReadAccess: return [manager isReadableFileAtPath:plist_path_];
case kWriteAccess: return [manager isWritableFileAtPath:plist_path_];
}
}
bool RlzValueStoreMac::WritePingTime(Product product, int64 time) {
NSNumber* n = [NSNumber numberWithLongLong:time];
[ProductDict(product) setObject:n forKey:kPingTimeKey];
return true;
}
bool RlzValueStoreMac::ReadPingTime(Product product, int64* time) {
if (NSNumber* n =
ObjCCast<NSNumber>([ProductDict(product) objectForKey:kPingTimeKey])) {
*time = [n longLongValue];
return true;
}
return false;
}
bool RlzValueStoreMac::ClearPingTime(Product product) {
[ProductDict(product) removeObjectForKey:kPingTimeKey];
return true;
}
bool RlzValueStoreMac::WriteAccessPointRlz(AccessPoint access_point,
const char* new_rlz) {
NSMutableDictionary* d = GetOrCreateDict(WorkingDict(), kAccessPointKey);
[d setObject:base::SysUTF8ToNSString(new_rlz)
forKey:GetNSAccessPointName(access_point)];
return true;
}
bool RlzValueStoreMac::ReadAccessPointRlz(AccessPoint access_point,
char* rlz,
size_t rlz_size) {
// Reading a non-existent access point counts as success.
if (NSDictionary* d = ObjCCast<NSDictionary>(
[WorkingDict() objectForKey:kAccessPointKey])) {
NSString* val = ObjCCast<NSString>(
[d objectForKey:GetNSAccessPointName(access_point)]);
if (!val) {
if (rlz_size > 0)
rlz[0] = '\0';
return true;
}
std::string s = base::SysNSStringToUTF8(val);
if (s.size() >= rlz_size) {
rlz[0] = 0;
ASSERT_STRING("GetAccessPointRlz: Insufficient buffer size");
return false;
}
strncpy(rlz, s.c_str(), rlz_size);
return true;
}
if (rlz_size > 0)
rlz[0] = '\0';
return true;
}
bool RlzValueStoreMac::ClearAccessPointRlz(AccessPoint access_point) {
if (NSMutableDictionary* d = ObjCCast<NSMutableDictionary>(
[WorkingDict() objectForKey:kAccessPointKey])) {
[d removeObjectForKey:GetNSAccessPointName(access_point)];
}
return true;
}
bool RlzValueStoreMac::AddProductEvent(Product product,
const char* event_rlz) {
[GetOrCreateDict(ProductDict(product), kProductEventKey)
setObject:[NSNumber numberWithBool:YES]
forKey:base::SysUTF8ToNSString(event_rlz)];
return true;
}
bool RlzValueStoreMac::ReadProductEvents(Product product,
std::vector<std::string>* events) {
if (NSDictionary* d = ObjCCast<NSDictionary>(
[ProductDict(product) objectForKey:kProductEventKey])) {
for (NSString* s in d)
events->push_back(base::SysNSStringToUTF8(s));
return true;
}
return true;
}
bool RlzValueStoreMac::ClearProductEvent(Product product,
const char* event_rlz) {
if (NSMutableDictionary* d = ObjCCast<NSMutableDictionary>(
[ProductDict(product) objectForKey:kProductEventKey])) {
[d removeObjectForKey:base::SysUTF8ToNSString(event_rlz)];
return true;
}
return false;
}
bool RlzValueStoreMac::ClearAllProductEvents(Product product) {
[ProductDict(product) removeObjectForKey:kProductEventKey];
return true;
}
bool RlzValueStoreMac::AddStatefulEvent(Product product,
const char* event_rlz) {
[GetOrCreateDict(ProductDict(product), kStatefulEventKey)
setObject:[NSNumber numberWithBool:YES]
forKey:base::SysUTF8ToNSString(event_rlz)];
return true;
}
bool RlzValueStoreMac::IsStatefulEvent(Product product,
const char* event_rlz) {
if (NSDictionary* d = ObjCCast<NSDictionary>(
[ProductDict(product) objectForKey:kStatefulEventKey])) {
return [d objectForKey:base::SysUTF8ToNSString(event_rlz)] != nil;
}
return false;
}
bool RlzValueStoreMac::ClearAllStatefulEvents(Product product) {
[ProductDict(product) removeObjectForKey:kStatefulEventKey];
return true;
}
void RlzValueStoreMac::CollectGarbage() {
NOTIMPLEMENTED();
}
NSDictionary* RlzValueStoreMac::dictionary() {
return dict_.get();
}
NSMutableDictionary* RlzValueStoreMac::WorkingDict() {
std::string brand(SupplementaryBranding::GetBrand());
if (brand.empty())
return dict_;
NSString* brand_ns =
[@"brand_" stringByAppendingString:base::SysUTF8ToNSString(brand)];
return GetOrCreateDict(dict_.get(), brand_ns);
}
NSMutableDictionary* RlzValueStoreMac::ProductDict(Product p) {
return GetOrCreateDict(WorkingDict(), GetNSProductName(p));
}
namespace {
RecursiveCrossProcessLock g_recursive_lock =
RECURSIVE_CROSS_PROCESS_LOCK_INITIALIZER;
// This is set during test execution, to write RLZ files into a temporary
// directory instead of the user's Application Support folder.
NSString* g_test_folder;
// RlzValueStoreMac keeps its data in memory and only writes it to disk when
// ScopedRlzValueStoreLock goes out of scope. Hence, if several
// ScopedRlzValueStoreLocks are nested, they all need to use the same store
// object.
// This counts the nesting depth.
int g_lock_depth = 0;
// This is the store object that might be shared. Only set if g_lock_depth > 0.
RlzValueStoreMac* g_store_object = NULL;
NSString* CreateRlzDirectory() {
NSFileManager* manager = [NSFileManager defaultManager];
NSArray* paths = NSSearchPathForDirectoriesInDomains(
NSApplicationSupportDirectory, NSUserDomainMask, /*expandTilde=*/YES);
NSString* folder = nil;
if ([paths count] > 0)
folder = ObjCCast<NSString>([paths objectAtIndex:0]);
if (!folder)
folder = [@"~/Library/Application Support" stringByStandardizingPath];
folder = [folder stringByAppendingPathComponent:@"Google/RLZ"];
if (g_test_folder)
folder = [g_test_folder stringByAppendingPathComponent:folder];
[manager createDirectoryAtPath:folder
withIntermediateDirectories:YES
attributes:nil
error:nil];
return folder;
}
// Returns the path of the rlz plist store, also creates the parent directory
// path if it doesn't exist.
NSString* RlzPlistFilename() {
NSString* const kRlzFile = @"RlzStore.plist";
return [CreateRlzDirectory() stringByAppendingPathComponent:kRlzFile];
}
// Returns the path of the rlz lock file, also creates the parent directory
// path if it doesn't exist.
NSString* RlzLockFilename() {
NSString* const kRlzLockfile = @"flockfile";
return [CreateRlzDirectory() stringByAppendingPathComponent:kRlzLockfile];
}
} // namespace
ScopedRlzValueStoreLock::ScopedRlzValueStoreLock() {
bool got_distributed_lock = g_recursive_lock.TryGetCrossProcessLock(
FilePath([RlzLockFilename() fileSystemRepresentation]));
// At this point, we hold the in-process lock, no matter the value of
// |got_distributed_lock|.
++g_lock_depth;
if (!got_distributed_lock) {
// Give up. |store_| isn't set, which signals to callers that acquiring
// the lock failed. |g_recursive_lock| will be released by the
// destructor.
CHECK(!g_store_object);
return;
}
if (g_lock_depth > 1) {
// Reuse the already existing store object. Note that it can be NULL when
// lock acquisition succeeded but the rlz data file couldn't be read.
store_.reset(g_store_object);
return;
}
CHECK(!g_store_object);
NSString* plist = RlzPlistFilename();
// Create an empty file if none exists yet.
NSFileManager* manager = [NSFileManager defaultManager];
if (![manager fileExistsAtPath:plist isDirectory:NULL])
[[NSDictionary dictionary] writeToFile:plist atomically:YES];
NSMutableDictionary* dict =
[NSMutableDictionary dictionaryWithContentsOfFile:plist];
VERIFY(dict);
if (dict) {
store_.reset(new RlzValueStoreMac(dict, plist));
g_store_object = (RlzValueStoreMac*)store_.get();
}
}
ScopedRlzValueStoreLock::~ScopedRlzValueStoreLock() {
--g_lock_depth;
CHECK(g_lock_depth >= 0);
if (g_lock_depth > 0) {
// Other locks are still using store_, don't free it yet.
ignore_result(store_.release());
return;
}
if (store_.get()) {
g_store_object = NULL;
NSDictionary* dict =
static_cast<RlzValueStoreMac*>(store_.get())->dictionary();
VERIFY([dict writeToFile:RlzPlistFilename() atomically:YES]);
}
// Check that "store_ set" => "file_lock acquired". The converse isn't true,
// for example if the rlz data file can't be read.
if (store_.get())
CHECK(g_recursive_lock.file_lock_ != -1);
if (g_recursive_lock.file_lock_ == -1)
CHECK(!store_.get());
g_recursive_lock.ReleaseLock();
}
RlzValueStore* ScopedRlzValueStoreLock::GetStore() {
return store_.get();
}
namespace testing {
void SetRlzStoreDirectory(const FilePath& directory) {
base::mac::ScopedNSAutoreleasePool pool;
[g_test_folder release];
if (directory.empty()) {
g_test_folder = nil;
} else {
// Not Unsafe on OS X.
g_test_folder =
[[NSString alloc] initWithUTF8String:directory.AsUTF8Unsafe().c_str()];
}
}
std::string RlzStoreFilenameStr() {
@autoreleasepool {
return std::string([RlzPlistFilename() fileSystemRepresentation]);
}
}
} // namespace testing
} // namespace rlz_lib