Adds Language Model Manager.

The reference of the global language models were stored in the class
InputMethodController, however, the global models are global but not a
part of the input method controller, and the input method controller
only use one of the models (McBopomofo/Plain Bopomofo). I guess it
somehow violates SRP and there should be a better place for the global
models.
This commit is contained in:
zonble 2022-01-11 17:12:58 +08:00
parent f339948219
commit 144d133463
7 changed files with 224 additions and 147 deletions

View File

@ -36,6 +36,7 @@
6AE210B215FC63CC003659FE /* PlainBopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6AE210B015FC63CC003659FE /* PlainBopomofo.tiff */; };
6AE210B315FC63CC003659FE /* PlainBopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6AE210B115FC63CC003659FE /* PlainBopomofo@2x.tiff */; };
6AFF97F2253B299E007F1C49 /* NonModalAlertWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */; };
D41355D8278D74B5005E5CBD /* LanguageModelManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = D41355D7278D7409005E5CBD /* LanguageModelManager.mm */; };
D427A9C125ED28CC005D43E0 /* OpenCCBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */; };
D427F76A278C9E29004A2160 /* CandidateUI in Frameworks */ = {isa = PBXBuildFile; productRef = D427F769278C9E29004A2160 /* CandidateUI */; };
D427F76C278CA2B0004A2160 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427F76B278CA1BA004A2160 /* AppDelegate.swift */; };
@ -155,6 +156,8 @@
6AE210B015FC63CC003659FE /* PlainBopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = PlainBopomofo.tiff; sourceTree = "<group>"; };
6AE210B115FC63CC003659FE /* PlainBopomofo@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "PlainBopomofo@2x.tiff"; sourceTree = "<group>"; };
6AFF97F0253B299E007F1C49 /* NonModalAlertWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NonModalAlertWindowController.xib; sourceTree = "<group>"; };
D41355D6278D7409005E5CBD /* LanguageModelManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LanguageModelManager.h; sourceTree = "<group>"; };
D41355D7278D7409005E5CBD /* LanguageModelManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LanguageModelManager.mm; sourceTree = "<group>"; };
D427A9BF25ED28CC005D43E0 /* McBopomofo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "McBopomofo-Bridging-Header.h"; sourceTree = "<group>"; };
D427A9C025ED28CC005D43E0 /* OpenCCBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenCCBridge.swift; sourceTree = "<group>"; };
D427F768278C9D0D004A2160 /* CandidateUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = CandidateUI; path = Packages/CandidateUI; sourceTree = "<group>"; };
@ -228,6 +231,8 @@
6A0D4F4715FC0EB900ABF4B3 /* Resources */,
6A0D4EC615FC0D6400ABF4B3 /* InputMethodController.h */,
6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */,
D41355D6278D7409005E5CBD /* LanguageModelManager.h */,
D41355D7278D7409005E5CBD /* LanguageModelManager.mm */,
6A0D4EC815FC0D6400ABF4B3 /* main.m */,
D427F76B278CA1BA004A2160 /* AppDelegate.swift */,
D47F7DD4278C25A0002F9DD7 /* InputSourceHelper.swift */,
@ -551,6 +556,7 @@
D47F7DD3278C1263002F9DD7 /* UserOverrideModel.cpp in Sources */,
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */,
6A0421A815FEF3F50061ED63 /* FastLM.cpp in Sources */,
D41355D8278D74B5005E5CBD /* LanguageModelManager.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -51,8 +51,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControlle
private var updateNextStepURL: URL?
func applicationDidFinishLaunching(_ notification: Notification) {
LTLoadLanguageModel()
LTLoadUserLanguageModelFile()
LanguageModelManager.loadDataModels()
LanguageModelManager.loadUserPhrasesModel()
// LTLoadLanguageModel()
// LTLoadUserLanguageModelFile()
if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil {
UserDefaults.standard.set(true, forKey: kCheckUpdateAutomatically)

View File

@ -49,15 +49,15 @@
Formosa::Gramambular::FastLM *_languageModel;
Formosa::Gramambular::FastLM *_userPhrasesModel;
// user override model
McBopomofo::UserOverrideModel *_userOverrideModel;
// the grid (lattice) builder for the unigrams (and bigrams)
Formosa::Gramambular::BlockReadingBuilder* _builder;
// latest walked path (trellis) using the Viterbi algorithm
std::vector<Formosa::Gramambular::NodeAnchor> _walkedNodes;
// user override model
McBopomofo::UserOverrideModel *_uom;
// the latest composing buffer that is updated to the foreground app
NSMutableString *_composingBuffer;
NSInteger _latestReadingCursor;
@ -78,7 +78,3 @@
BOOL _chineseConversionEnabled;
}
@end
// the shared language model object
extern "C" void LTLoadLanguageModel();
extern "C" void LTLoadUserLanguageModelFile();

View File

@ -38,13 +38,12 @@
#import <set>
#import "OVStringHelper.h"
#import "OVUTF8Helper.h"
#import "LanguageModelManager.h"
#import "McBopomofo-Swift.h"
@import CandidateUI;
@import OpenCC;
//@import SwiftUI;
// C++ namespace usages
using namespace std;
using namespace Formosa::Mandarin;
@ -111,62 +110,6 @@ VTCandidateController *gCurrentCandidateController = nil;
static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot";
#endif
// shared language model object that stores our phrase-term probability database
FastLM gLanguageModel;
FastLM gLanguageModelPlainBopomofo;
FastLM gUserPhraseLanguageModel;
static const int kUserOverrideModelCapacity = 500;
static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr.
McBopomofo::UserOverrideModel gUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife);
static NSString *LTUserDataFolderPath()
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDirectory, YES);
NSString *appSupportPath = [paths objectAtIndex:0];
NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"McBopomofo"];
return userDictPath;
}
static NSString *LTUserPhrasesDataPath()
{
return [LTUserDataFolderPath() stringByAppendingPathComponent:@"data.txt"];
}
static BOOL LTCheckIfUserLanguageModelFileExists() {
NSString *folderPath = LTUserDataFolderPath();
BOOL isFolder = NO;
BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder];
if (folderExist && !isFolder) {
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error];
if (error) {
NSLog(@"Failed to remove folder %@", error);
return NO;
}
folderExist = NO;
}
if (!folderExist) {
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:&error];
if (error) {
NSLog(@"Failed to create folder %@", error);
return NO;
}
}
NSString *filePath = LTUserPhrasesDataPath();
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePath atomically:YES];
if (!result) {
NSLog(@"Failed to write file");
return NO;
}
}
return YES;
}
// https://clang-analyzer.llvm.org/faq.html
__attribute__((annotate("returns_localized_nsstring")))
static inline NSString *LocalizationNotNeeded(NSString *s) {
@ -174,17 +117,12 @@ static inline NSString *LocalizationNotNeeded(NSString *s) {
}
// private methods
@interface McBopomofoInputMethodController () <VTCandidateControllerDelegate>
@interface McBopomofoInputMethodController ()
+ (VTHorizontalCandidateController *)horizontalCandidateController;
+ (VTVerticalCandidateController *)verticalCandidateController;
@end
- (void)collectCandidates;
- (size_t)actualCandidateCursorIndex;
- (void)_showCandidateWindowUsingVerticalMode:(BOOL)useVerticalMode client:(id)client;
- (void)beep;
- (BOOL)handleInputText:(NSString*)inputText key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)client;
- (BOOL)handleCandidateEventWithInputText:(NSString *)inputText charCode:(UniChar)charCode keyCode:(NSUInteger)keyCode;
@interface McBopomofoInputMethodController (VTCandidateController) <VTCandidateControllerDelegate>
@end
// sort helper
@ -237,10 +175,11 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
_bpmfReadingBuffer = new BopomofoReadingBuffer(BopomofoKeyboardLayout::StandardLayout());
// create the lattice builder
_languageModel = &gLanguageModel;
_userPhrasesModel = &gUserPhraseLanguageModel;
_languageModel = [LanguageModelManager languageModelMcBopomofo];
_userPhrasesModel = [LanguageModelManager userPhraseLanguageModel];
_userOverrideModel = [LanguageModelManager userOverrideModel];
_builder = new BlockReadingBuilder(_languageModel, _userPhrasesModel);
_uom = &gUserOverrideModel;
// each Mandarin syllable is separated by a hyphen
_builder->setJoinSeparator("-");
@ -380,17 +319,17 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
{
NSString *newInputMode;
Formosa::Gramambular::FastLM *newLanguageModel;
Formosa::Gramambular::FastLM *userPhraseModel;
Formosa::Gramambular::FastLM *newUserPhraseModel;
if ([value isKindOfClass:[NSString class]] && [value isEqual:kPlainBopomofoModeIdentifier]) {
newInputMode = kPlainBopomofoModeIdentifier;
newLanguageModel = &gLanguageModelPlainBopomofo;
userPhraseModel = NULL;
newLanguageModel = [LanguageModelManager languageModelPlainBopomofo];
newUserPhraseModel = NULL;
}
else {
newInputMode = kBopomofoModeIdentifier;
newLanguageModel = &gLanguageModel;
userPhraseModel = &gUserPhraseLanguageModel;
newLanguageModel = [LanguageModelManager languageModelMcBopomofo];
newUserPhraseModel = [LanguageModelManager userPhraseLanguageModel];
}
// Only apply the changes if the value is changed
@ -406,7 +345,7 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
_inputMode = newInputMode;
_languageModel = newLanguageModel;
_userPhrasesModel = userPhraseModel;
_userPhrasesModel = newUserPhraseModel;
if (!_bpmfReadingBuffer->isEmpty()) {
_bpmfReadingBuffer->clear();
@ -432,8 +371,7 @@ static double FindHighestScore(const vector<NodeAnchor>& nodes, double epsilon)
// if it's Terminal, we don't commit at the first call (the client of which will not be IPMDServerClientWrapper)
// then we defer the update in the next runloop round -- so that the composing buffer is not
// meaninglessly flushed, an annoying bug in Terminal.app since Mac OS X 10.5
if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && ![NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"])
{
if ([[client bundleIdentifier] isEqualToString:@"com.apple.Terminal"] && ![NSStringFromClass([client class]) isEqualToString:@"IPMDServerClientWrapper"]) {
if (_currentDeferredClient) {
[self performSelector:@selector(updateClientComposingBuffer:) withObject:_currentDeferredClient afterDelay:0.0];
}
@ -532,7 +470,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
// i.e. the client app needs to take care of where to put ths composing buffer
[client setMarkedText:attrString selectionRange:NSMakeRange((NSInteger)_builder->markerCursorIndex(), 0) replacementRange:NSMakeRange(NSNotFound, NSNotFound)];
_latestReadingCursor = (NSInteger)_builder->markerCursorIndex();
} else {
}
else {
// we must use NSAttributedString so that the cursor is visible --
// can't just use NSString
NSDictionary *attrDict = @{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),
@ -560,13 +499,13 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
reverse(_walkedNodes.begin(), _walkedNodes.end());
// if DEBUG is defined, a GraphViz file is written to kGraphVizOutputfile
#if DEBUG
#if DEBUG
string dotDump = _builder->grid().dumpDOT();
NSString *dotStr = [NSString stringWithUTF8String:dotDump.c_str()];
NSError *error = nil;
BOOL __unused success = [dotStr writeToFile:kGraphVizOutputfile atomically:YES encoding:NSUTF8StringEncoding error:&error];
#endif
#endif
}
- (void)popOverflowComposingTextAndWalk:(id)client
@ -681,29 +620,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
- (BOOL)_writeUserPhrase
{
if (!LTCheckIfUserLanguageModelFileExists()) {
return NO;
}
NSString *currentMarkedPhrase = [self _currentMarkedText];
if (![currentMarkedPhrase length]) {
return NO;
}
currentMarkedPhrase = [currentMarkedPhrase stringByAppendingString:@"\n"];
NSString *path = LTUserPhrasesDataPath();
NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:path];
if (!file) {
return NO;
}
[file seekToEndOfFile];
NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding];
[file writeData:data];
[file closeFile];
LTLoadUserLanguageModelFile();
return YES;
return [LanguageModelManager writeUserPhrase:currentMarkedPhrase];
}
- (BOOL)handleInputText:(NSString*)inputText key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)client
@ -801,7 +723,8 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
if (charCode == 13) {
if ([self _writeUserPhrase]) {
_builder->setMarkerCursorIndex(SIZE_MAX);
} else {
}
else {
[self beep];
}
[self updateClientComposingBuffer:client];
@ -868,9 +791,9 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
[self popOverflowComposingTextAndWalk:client];
// get user override model suggestion
string overrideValue =
(_inputMode == kPlainBopomofoModeIdentifier) ? "" :
_uom->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]);
string overrideValue = (_inputMode == kPlainBopomofoModeIdentifier) ? "" :
_userOverrideModel->suggest(_walkedNodes, _builder->cursorIndex(), [[NSDate date] timeIntervalSince1970]);
if (!overrideValue.empty()) {
size_t cursorIndex = [self actualCandidateCursorIndex];
vector<NodeAnchor> nodes = _builder->grid().nodesCrossingOrEndingAt(cursorIndex);
@ -1188,9 +1111,9 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
- (BOOL)handleCandidateEventWithInputText:(NSString *)inputText charCode:(UniChar)charCode keyCode:(NSUInteger)keyCode
{
BOOL cancelCandidateKey =
(charCode == 27) ||
((_inputMode == kPlainBopomofoModeIdentifier) &&
(charCode == 8 || keyCode == kDeleteKeyCode));
(charCode == 27) ||
((_inputMode == kPlainBopomofoModeIdentifier) &&
(charCode == 8 || keyCode == kDeleteKeyCode));
if (cancelCandidateKey) {
gCurrentCandidateController.visible = NO;
@ -1343,7 +1266,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
string punctuation = string("_punctuation_") + string(1, (char)charCode);
BOOL shouldAutoSelectCandidate = _bpmfReadingBuffer->isValidKey((char)charCode) || _languageModel->hasUnigramsForKey(customPunctuation) ||
_languageModel->hasUnigramsForKey(punctuation);
_languageModel->hasUnigramsForKey(punctuation);
if (shouldAutoSelectCandidate) {
NSUInteger candidateIndex = [gCurrentCandidateController candidateIndexAtKeyLabelIndex:0];
@ -1569,13 +1492,13 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
- (void)openUserPhrases:(id)sender
{
NSLog(@"openUserPhrases called");
if (!LTCheckIfUserLanguageModelFileExists()) {
NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), LTUserDataFolderPath()];
if (![LanguageModelManager checkIfUserLanguageModelFileExists] ) {
NSString *content = [NSString stringWithFormat:NSLocalizedString(@"Please check the permission of at \"%@\".", @""), [LanguageModelManager dataFolderPath]];
[[NonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Unable to create the user phrase file.", @"") content:content confirmButtonTitle:NSLocalizedString(@"OK", @"") cancelButtonTitle:nil cancelAsDefault:NO delegate:nil];
return;
}
NSString *path = LTUserPhrasesDataPath();
NSString *path = [LanguageModelManager userPhrasesDataPath];
NSLog(@"Open %@", path);
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
[[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:path atomically:YES];
@ -1587,7 +1510,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
- (void)reloadUserPhrases:(id)sender
{
NSLog(@"reloadUserPhrases called");
LTLoadUserLanguageModelFile();
[LanguageModelManager loadUserPhrasesModel];
}
- (void)showAbout:(id)sender
@ -1602,6 +1525,12 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
[[NSUserDefaults standardUserDefaults] setBool:_chineseConversionEnabled forKey:kChineseConversionEnabledKey];
}
@end
#pragma mark -
@implementation McBopomofoInputMethodController (VTCandidateController)
- (NSUInteger)candidateCountForController:(VTCandidateController *)controller
{
return [_candidates count];
@ -1622,7 +1551,7 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
size_t cursorIndex = [self actualCandidateCursorIndex];
_builder->grid().fixNodeSelectedCandidate(cursorIndex, selectedValue);
if (_inputMode != kPlainBopomofoModeIdentifier) {
_uom->observe(_walkedNodes, cursorIndex, selectedValue, [[NSDate date] timeIntervalSince1970]);
_userOverrideModel->observe(_walkedNodes, cursorIndex, selectedValue, [[NSDate date] timeIntervalSince1970]);
}
[_candidates removeAllObjects];
@ -1637,28 +1566,3 @@ NS_INLINE size_t max(size_t a, size_t b) { return a > b ? a : b; }
}
@end
static void LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM &lm)
{
NSString *dataPath = [[NSBundle bundleForClass:[McBopomofoInputMethodController class]] pathForResource:filenameWithoutExtension ofType:@"txt"];
bool result = lm.open([dataPath UTF8String]);
if (!result) {
NSLog(@"Failed opening language model: %@", dataPath);
}
}
void LTLoadLanguageModel()
{
LTLoadLanguageModelFile(@"data", gLanguageModel);
LTLoadLanguageModelFile(@"data-plain-bpmf", gLanguageModelPlainBopomofo);
}
void LTLoadUserLanguageModelFile()
{
gUserPhraseLanguageModel.close();
bool result = gUserPhraseLanguageModel.open([LTUserPhrasesDataPath() UTF8String]);
if (!result) {
NSLog(@"Failed opening language model for user phrases.");
}
}

View File

@ -0,0 +1,23 @@
#import <Foundation/Foundation.h>
#import "FastLM.h"
#import "UserOverrideModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface LanguageModelManager : NSObject
+ (void)loadDataModels;
+ (void)loadUserPhrasesModel;
+ (BOOL)checkIfUserLanguageModelFileExists;
+ (BOOL)writeUserPhrase:(NSString *)userPhrase;
@property (class, readonly, nonatomic) NSString *dataFolderPath;
@property (class, readonly, nonatomic) NSString *userPhrasesDataPath;
@property (class, readonly, nonatomic) Formosa::Gramambular::FastLM *languageModelMcBopomofo;
@property (class, readonly, nonatomic) Formosa::Gramambular::FastLM *languageModelPlainBopomofo;
@property (class, readonly, nonatomic) Formosa::Gramambular::FastLM *userPhraseLanguageModel;
@property (class, readonly, nonatomic) McBopomofo::UserOverrideModel *userOverrideModel;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,140 @@
#import "LanguageModelManager.h"
#import <fstream>
#import <iostream>
#import <set>
#import "OVStringHelper.h"
#import "OVUTF8Helper.h"
using namespace std;
using namespace Formosa::Gramambular;
using namespace OpenVanilla;
static const int kUserOverrideModelCapacity = 500;
static const double kObservedOverrideHalflife = 5400.0; // 1.5 hr.
FastLM globalLanguageModel;
FastLM globalLanguageModelPlainBopomofo;
FastLM globalUserPhraseLanguageModel;
McBopomofo::UserOverrideModel globalUserOverrideModel(kUserOverrideModelCapacity, kObservedOverrideHalflife);
@implementation LanguageModelManager
static bool LTLoadLanguageModelFile(NSString *filenameWithoutExtension, FastLM &lm)
{
Class cls = NSClassFromString(@"McBopomofoInputMethodController");
NSString *dataPath = [[NSBundle bundleForClass:cls] pathForResource:filenameWithoutExtension ofType:@"txt"];
bool result = lm.open([dataPath UTF8String]);
return (BOOL)result;
}
+ (void)loadDataModels
{
bool dataOpenResult = LTLoadLanguageModelFile(@"data", globalLanguageModel);
if (!dataOpenResult) {
NSLog(@"Failed to open language model.");
}
bool plainBpmfOpenResult = LTLoadLanguageModelFile(@"data-plain-bpmf", globalLanguageModelPlainBopomofo);
if (!plainBpmfOpenResult) {
NSLog(@"Failed to open language model for plain bpmf.");
}
}
+ (void)loadUserPhrasesModel
{
globalUserPhraseLanguageModel.close();
bool result = globalUserPhraseLanguageModel.open([[self userPhrasesDataPath] UTF8String]);
if (!result) {
NSLog(@"Failed to open user phrases.");
}
}
+ (BOOL)checkIfUserLanguageModelFileExists
{
NSString *folderPath = [self dataFolderPath];
BOOL isFolder = NO;
BOOL folderExist = [[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:&isFolder];
if (folderExist && !isFolder) {
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtPath:folderPath error:&error];
if (error) {
NSLog(@"Failed to remove folder %@", error);
return NO;
}
folderExist = NO;
}
if (!folderExist) {
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:&error];
if (error) {
NSLog(@"Failed to create folder %@", error);
return NO;
}
}
NSString *filePath = [self userPhrasesDataPath];
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
BOOL result = [[@"" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePath atomically:YES];
if (!result) {
NSLog(@"Failed to write file");
return NO;
}
}
return YES;
}
+ (BOOL)writeUserPhrase:(NSString *)userPhrase
{
if (![self checkIfUserLanguageModelFileExists]) {
return NO;
}
NSString *currentMarkedPhrase = [userPhrase stringByAppendingString:@"\n"];
NSString *path = [self userPhrasesDataPath];
NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:path];
if (!file) {
return NO;
}
[file seekToEndOfFile];
NSData *data = [currentMarkedPhrase dataUsingEncoding:NSUTF8StringEncoding];
[file writeData:data];
[file closeFile];
[self loadUserPhrasesModel];
return YES;
}
+ (NSString *)dataFolderPath
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDirectory, YES);
NSString *appSupportPath = [paths objectAtIndex:0];
NSString *userDictPath = [appSupportPath stringByAppendingPathComponent:@"McBopomofo"];
return userDictPath;
}
+ (NSString *)userPhrasesDataPath
{
return [[self dataFolderPath] stringByAppendingPathComponent:@"data.txt"];
}
+ (Formosa::Gramambular::FastLM *)languageModelMcBopomofo
{
return &globalLanguageModel;
}
+ (Formosa::Gramambular::FastLM *)languageModelPlainBopomofo
{
return &globalLanguageModelPlainBopomofo;
}
+ (Formosa::Gramambular::FastLM *)userPhraseLanguageModel
{
return &globalUserPhraseLanguageModel;
}
+ (McBopomofo::UserOverrideModel *)userOverrideModel
{
return &globalUserOverrideModel;
}
@end

View File

@ -2,7 +2,13 @@
// Use this file to import your target's public headers that you would like to expose to Swift.
//
extern void LTLoadLanguageModel(void);
extern void LTLoadUserLanguageModelFile(void);
//extern void LTLoadLanguageModel(void);
//extern void LTLoadUserLanguageModelFile(void);
@import Foundation;
@interface LanguageModelManager : NSObject
+ (void)loadDataModels;
+ (void)loadUserPhrasesModel;
+ (BOOL)checkIfUserLanguageModelFileExists;
@end