diff --git a/.gitignore b/.gitignore index 71a7e03..8ca5365 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,6 @@ Cargo.lock doc/*.html fanling-engine/testfiles* taipo-git-control/testfiles/testrep2.git/ -test testfiles testfiles/testrep2.git/ tools diff --git a/Lowu/.idea/gradle.xml b/Lowu/.idea/gradle.xml index d291b3d..e04498a 100644 --- a/Lowu/.idea/gradle.xml +++ b/Lowu/.idea/gradle.xml @@ -1,15 +1,20 @@ + diff --git a/Lowu/app/src/main/java/hk/jennyemily/work/lowu/MainActivity.java b/Lowu/app/src/main/java/hk/jennyemily/work/lowu/MainActivity.java index e09ea05..ec8afcb 100644 --- a/Lowu/app/src/main/java/hk/jennyemily/work/lowu/MainActivity.java +++ b/Lowu/app/src/main/java/hk/jennyemily/work/lowu/MainActivity.java @@ -34,10 +34,15 @@ public class MainActivity extends AppCompatActivity { private WebView mWebView; private static final int RESULT_SETTINGS = 1; + private String appStatus = "initial"; + private final String APP_STATUS = "appState"; + @Override protected void onCreate(Bundle savedInstanceState) { Log.d(TAG, "on create..."); super.onCreate(savedInstanceState); + if (savedInstanceState != null) appStatus = savedInstanceState.getString(APP_STATUS); + Log.d(TAG, "app status is " + appStatus); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); toolbar.setTitle("Fanling10"); @@ -49,21 +54,22 @@ protected void onCreate(Bundle savedInstanceState) { td = taiposwig.taiposwig.make_data(jsonOptions().toString()); Log.d(TAG, "data made."); - WebView mWebView = findViewById(R.id.webview); + mWebView = findViewById(R.id.webview); WebSettings webSettings = mWebView.getSettings(); webSettings.setJavaScriptEnabled(true); mWebView.addJavascriptInterface(new WebAppInterface(this), "taipo"); //!!! add some code here to set platform-specific javascript (look at the PC version) mWebView.setWebViewClient(new WebViewClient()); - - Log.d(TAG, "web view set"); - String ih = taiposwig.taiposwig.initial_html(td); - Log.d(TAG, "have initial html"); + Log.d(TAG, "web view set"); + // if (savedInstanceState != null) mWebView.restoreState(savedInstanceState); + if (appStatus.equals("initial")) { + String ih = taiposwig.taiposwig.initial_html(td); + Log.d(TAG, "have initial html, displaying..."); // Log.d(TAG, "initial html: "+ih); - mWebView.loadData(ih, null, null); - + mWebView.loadData(ih, null, null); + appStatus = "started"; + } taiposwig.taiposwig.handle_event(td, CCycleEvent.Start); - Log.d(TAG, "on create done"); } @@ -72,19 +78,20 @@ void setInitialPreferencesIfRequired() { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); Log.d(TAG, "in main using: " + PreferenceManager.getDefaultSharedPreferencesName(getBaseContext())); if (sp.getString("unique_prefix", "") == "") { - SharedPreferences.Editor ed = sp.edit(); - ed.putString("database_path", getApplicationContext().getDataDir() + "/search.db"); - ed.putString("git_path", getApplicationContext().getDataDir() + "/test2.git"); - ed.putString("git_branch", "master"); + SharedPreferences.Editor ed = sp.edit(); + ed.putString("database_path", getApplicationContext().getDataDir() + "/search.db"); + ed.putString("git_path", getApplicationContext().getDataDir() + "/test2.git"); + ed.putString("git_branch", "master"); ed.putBoolean("git_has_url", true); - ed.putString("git_url", "git@test.jennyemily.hk:martin/data1.git"); - ed.putString("git_name", "martin"); - ed.putString("git_email", "m.e@acm.org"); - ed.putString("unique_prefix", "x"); - ed.putString("ssh_path", getApplicationContext().getDataDir() + "/id_rsa"); - ed.putBoolean("slurp_ssh", true); - ed.apply(); - Log.d(TAG, "set initial preferences."); + ed.putString("git_url", "git@test.jennyemily.hk:martin/data1.git"); + ed.putString("git_name", "martin"); + ed.putString("git_email", "m.e@acm.org"); + ed.putString("unique_prefix", "x"); + ed.putString("ssh_path", getApplicationContext().getDataDir() + "/id_rsa"); + ed.putBoolean("slurp_ssh", true); + ed.putBoolean("auto_link", false); + ed.apply(); + Log.d(TAG, "set initial preferences."); } else { Log.d(TAG, "preferences already set"); } @@ -106,6 +113,7 @@ JSONObject jsonOptions() { json.put("unique_prefix", sp.getString("unique_prefix", "??")); json.put("ssh_path", sp.getString("ssh_path", "??")); json.put("slurp_ssh", sp.getBoolean("slurp_ssh", true)); + json.put("auto_link", sp.getBoolean("auto_link", false)); Log.d(TAG, "options set, prefix is " + sp.getString("unique_prefix", "??")); } catch (JSONException e) { e.printStackTrace(); @@ -113,6 +121,25 @@ JSONObject jsonOptions() { return json; } + @Override + public void onRestoreInstanceState(Bundle savedState) { + Log.d(TAG, "restoring instance state..."); + super.onRestoreInstanceState(savedState); + String restoredState = savedState.getString(APP_STATUS); + if (restoredState != appStatus) + Log.e(TAG, "restored state is " + restoredState + " but current state is " + appStatus); + mWebView.restoreState(savedState); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + Log.d(TAG, "saving instance state..."); + outState.putString(APP_STATUS, appStatus); + if (mWebView == null) Log.e(TAG, "no web view, cannot save"); + else mWebView.saveState(outState); + super.onSaveInstanceState(outState); + } + @Override protected void onPause() { Log.d(TAG, "pausing..."); @@ -214,8 +241,9 @@ class WebAppInterface { @JavascriptInterface public void execute(String body) { taiposwig.taiposwig.execute(td, body); + Log.d(TAG, "execute done [from Java]."); if (taiposwig.taiposwig.is_shutdown_required(td)) { - Log.d(TAG, "shutdown required!"); + Log.d(TAG, "shutdown required! [from Java]"); finish(); } } diff --git a/Lowu/app/src/main/java/hk/jennyemily/work/lowu/PreferencesActivity.java b/Lowu/app/src/main/java/hk/jennyemily/work/lowu/PreferencesActivity.java index c86f81b..3888df4 100644 --- a/Lowu/app/src/main/java/hk/jennyemily/work/lowu/PreferencesActivity.java +++ b/Lowu/app/src/main/java/hk/jennyemily/work/lowu/PreferencesActivity.java @@ -21,7 +21,7 @@ public class PreferencesActivity extends PreferenceActivity { private static final String[] keys = { "git_path", "git_branch", "git_has_url", "git_url", "git_name", "git_email", "database_path", - "unique_prefix", "ssh_path", "slurp_ssh" + "unique_prefix", "ssh_path", "slurp_ssh", "auto_link" }; public final static int NOT_CHANGED_RESULT = RESULT_FIRST_USER; public final static int CHANGED_RESULT = RESULT_FIRST_USER + 1; diff --git a/Lowu/app/src/main/res/layout/content_main.xml b/Lowu/app/src/main/res/layout/content_main.xml index eb94eae..57aa6d4 100644 --- a/Lowu/app/src/main/res/layout/content_main.xml +++ b/Lowu/app/src/main/res/layout/content_main.xml @@ -16,6 +16,7 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent" + android:saveEnabled="true"/> \ No newline at end of file diff --git a/Lowu/app/src/main/res/xml/prefs.xml b/Lowu/app/src/main/res/xml/prefs.xml index afcc7aa..8ea3461 100644 --- a/Lowu/app/src/main/res/xml/prefs.xml +++ b/Lowu/app/src/main/res/xml/prefs.xml @@ -50,5 +50,10 @@ android:key="slurp_ssh" android:summary="Slurp SSH" android:title="Slurp SSH" /> + \ No newline at end of file diff --git a/Lowu/build.gradle b/Lowu/build.gradle index a5bb815..dcaeef2 100644 --- a/Lowu/build.gradle +++ b/Lowu/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.5.3' + classpath 'com.android.tools.build:gradle:3.6.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/Lowu/gradle/wrapper/gradle-wrapper.properties b/Lowu/gradle/wrapper/gradle-wrapper.properties index 9c74100..e05209a 100644 --- a/Lowu/gradle/wrapper/gradle-wrapper.properties +++ b/Lowu/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Sep 13 10:00:14 HKT 2019 +#Thu Mar 26 11:37:01 HKT 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/fanling-c-interface/Cargo.toml b/fanling-c-interface/Cargo.toml index c790973..d66d26b 100644 --- a/fanling-c-interface/Cargo.toml +++ b/fanling-c-interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fanling-c-interface" -version = "0.1.1" +version = "0.1.2" authors = ["martin "] edition = "2018" # build = "build.rs" @@ -8,12 +8,12 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -libc = "0.2.67" +libc = "0.2.68" fanling-interface = { path = "../fanling-interface" } fanling-engine = { path = "../fanling-engine" } log = "0.4.8" -serde = "1.0.104" -serde_json = "1.0.48" +serde = "1.0.106" +serde_json = "1.0.50" [target.'cfg(target_os = "android")'.dependencies] android_log = "0.1.3" taipo-git-control = { path = "../taipo-git-control" } diff --git a/fanling-c-interface/src/lib.rs b/fanling-c-interface/src/lib.rs index 85df4ad..e64d9a2 100644 --- a/fanling-c-interface/src/lib.rs +++ b/fanling-c-interface/src/lib.rs @@ -103,6 +103,7 @@ struct FanlingOptions { pub unique_prefix: String, pub ssh_path: String, pub slurp_ssh: bool, + pub auto_link: bool, } #[no_mangle] /// creates the main data structure. If you call this, you should call `delete_data` at the end of the program. Note that we initialise the android log; we can only do this once but this code is called more than once, and we have no easy way to check whether it has been called already, so we just ignore any error. @@ -143,7 +144,7 @@ pub unsafe extern "C" fn make_data(fanling_options_json_c: *const c_char) -> *mu database_path: fanling_options.database_path, }, uniq_pfx: fanling_options.unique_prefix, - auto_link: false, + auto_link: fanling_options.auto_link, }; debug!("options as read {:#?}", engine_options); debug!("making data in rust..."); @@ -184,13 +185,16 @@ fn string_from_c(s: *const c_char) -> String { pub extern "C" fn execute(data: *mut LowuData, body: *const c_char) { // let bs = CStr::from_ptr(body).to_string_lossy().into_owned(); let bs = string_from_c(body); - debug!("executing {}", bs); + debug!("executing {} [from rust-c]", bs); let mut d = unsafe { data.as_mut().expect("bad pointer") }; match &mut d.engine { Some(e) => d.last_response = e.execute(&bs), - None => {} + None => { + debug!("no engine!"); + } } - debug!("execution result {:?}", d.last_response); + debug!("executed [from rust-c]"); + debug!("execution result {:?} [from rust-c]", d.last_response); } // #[no_mangle] diff --git a/fanling-engine/Cargo.toml b/fanling-engine/Cargo.toml index d624b17..b779257 100644 --- a/fanling-engine/Cargo.toml +++ b/fanling-engine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fanling-engine" -version = "0.1.1" +version = "0.1.2" authors = ["martin "] edition = "2018" @@ -8,11 +8,11 @@ edition = "2018" [dependencies] ansi_term = "0.12.1" -askama = "=0.8.0" +askama = "0.8.0" askama_shared = "0.9.1" bitfield = "0.13.2" chrono ={ version = "0.4.11", features = ["serde"] } -diesel = { version = "1.4.3", features = ["sqlite", "chrono"] } +diesel = { version = "1.4.4", features = ["sqlite", "chrono"] } diesel_migrations = "1.4.0" difference = "2.0.0" dotenv = "0.15.0" @@ -20,11 +20,11 @@ fanling-interface = { path = "../fanling-interface" } log = "0.4.8" pulldown-cmark = "0.7.0" quick-error = "1.2.3" -regex = "1.3.5" -rusqlite = { version = "=0.14.0", features = ["bundled"] } -rust-embed = { version = "5.5.0", features = ["debug-embed"] } -serde = { version = "1.0.104", features = ["derive"] } -serde_json = "1.0.48" +regex = "1.3.6" +rusqlite = { version = "0.21.0", features = ["bundled"] } +rust-embed = { version = "5.5.1", features = ["debug-embed"] } +serde = { version = "1.0.106", features = ["derive"] } +serde_json = "1.0.50" serde_yaml = "0.8.11" taipo-git-control = { path = "../taipo-git-control" } diff --git a/fanling-engine/src/item.rs b/fanling-engine/src/item.rs index 2a565f4..433f674 100644 --- a/fanling-engine/src/item.rs +++ b/fanling-engine/src/item.rs @@ -96,8 +96,8 @@ impl Item { self.data.is_open() } /** is the [`Item`] ready to be used? */ - pub fn is_ready(&self) -> bool { - self.data.is_ready() + pub fn is_ready(&mut self, world: &mut World) -> FLResult { + self.data.is_ready(world) } /** classify the item */ pub fn classify(&self) -> String { @@ -152,9 +152,11 @@ impl Item { self.base.item_type.clone() } /** set the Item from YAML data */ - pub fn set_from_yaml(&mut self, yaml: serde_yaml::Value, world: &mut World) -> NullResult { + pub fn set_from_yaml(&mut self, yaml: &serde_yaml::Value, world: &mut World) -> NullResult { fanling_trace!("setting from yaml"); - self.data.set_from_yaml(yaml, world) + self.data.set_from_yaml(&yaml, world)?; + // self.data.fix_data(&yaml, &mut self.base, world)?; + Ok(()) } /** display for editing */ pub fn for_edit( @@ -162,16 +164,24 @@ impl Item { is_for_update: bool, world: &mut World, ) -> fanling_interface::ResponseResult { - self.data.for_edit(&mut self.base, is_for_update, world) + let rr = self.data.for_edit(&mut self.base, is_for_update, world); + fanling_trace!("for edit"); + rr } /** display for show */ pub fn for_show(&mut self, world: &mut World) -> fanling_interface::ResponseResult { - self.data.for_show(&mut self.base, world) + let rr = self.data.for_show(&mut self.base, world); + fanling_trace!("for show"); + rr } /** serialise the Item to YAML */ pub fn to_yaml(&self) -> Result, FanlingError> { self.data.to_yaml(&self.base) } + /** can be turned into an ident */ + pub fn descr_for_ident(&self) -> String { + self.data.descr_for_ident() + } // an English-language description /** a human-readable summary of the Item */ pub fn description(&self) -> String { @@ -256,6 +266,25 @@ impl Item { changes.push(change); Ok(()) } + /** */ + pub fn fix_data(&mut self, yaml: &serde_yaml::Value, world: &mut World) -> NullResult { + self.data.fix_data(yaml, &mut self.base, world) + } + // #[cfg(test)] + // /** generate some data useful in tests */ + // pub fn make_test_data(&self) -> HashMap { + // let mut td = HashMap::new(); + // td.insert("ident".to_string(), self.ident()); + // td.insert( + // "ready".to_string(), + // if self.is_ready() { + // "true".to_string() + // } else { + // "false".to_string() + // }, + // ); + // td + // } } /** attributes of an [`Item`] that are the same for all `Item`s. */ #[derive(Debug, Clone)] @@ -356,7 +385,9 @@ impl ItemBase { self.special.add(SpecialKind::Context); } // do not copy targeted - // TODO: should we copy when_created and when_modified? + self.when_created = base.when_created; + let naive_date_time = Utc::now().naive_utc(); + self.when_modified = naive_date_time; trace("set base from serde."); Ok(()) } @@ -364,6 +395,10 @@ impl ItemBase { pub fn set_parent(&mut self, parent: Option) { self.parent = parent; } + /** has a parent item */ + pub fn has_parent(&self) -> bool { + self.parent.is_some() + } /** can the [`Item`] be used as a parent? */ pub fn get_special(&self, sk: SpecialKind) -> bool { self.special.bit(sk as usize) @@ -394,9 +429,9 @@ impl ItemBase { pub fn get_sort(&self) -> String { self.sort.clone() } - /** get all the children of this [`Item`] */ - pub fn get_item_children(&self, world: &World) -> FLResult { - world.search_ready_children(&self.ident) + /** get all children with open status */ + pub fn get_open_children(&self, world: &World) -> FLResult { + world.search_open_children(&self.ident) } /** copy from another base */ pub fn clone_from(&mut self, other: &Self) { @@ -583,19 +618,26 @@ pub trait ItemData: Debug { /** is the [`Item`] open? */ fn is_open(&self) -> bool; /** is the [`Item`] ready? */ - fn is_ready(&self) -> bool; + fn is_ready(&mut self, world: &mut World) -> FLResult; /** an English-language description */ fn description(&self) -> String; /** copy from another item data */ fn fanling_clone(&self) -> FLResult>; - // where - // Self: Sized; + /** can be turned into an ident */ + fn descr_for_ident(&self) -> String; /** a description that can be used in a list */ fn description_for_list(&self) -> String; /** set the data from a hashmap of values. This can assume that all data is ok, or just return error*/ fn set_data(&mut self, vals: &HashMap, world: &mut World) -> NullResult; /** set the data from YAML data */ - fn set_from_yaml(&mut self, yaml: serde_yaml::Value, world: &mut World) -> NullResult; + fn set_from_yaml(&mut self, yaml: &serde_yaml::Value, world: &mut World) -> NullResult; + /** transitional to fix old data */ + fn fix_data( + &self, + yaml: &serde_yaml::Value, + base: &mut ItemBase, + world: &mut World, + ) -> NullResult; /** do an action */ fn do_action( &mut self, @@ -658,7 +700,32 @@ impl ItemType { ) -> ActionResponse { self.policy.check_valid(base, vals, world) } - /** resolve any conflicts between versions (eg server ls local) */ + /* resolve any conflicts between versions (eg server vs local). + + There are three possible versions of the item: + + * the *ancestor* version (the most recent common ancestor of 'our' and 'their' versions); + * *'our'* version (the version currently in the program), and + * *'their'* version (the version retrieved from the remote repository). + + Of these, + + * at least one must be present; + * if there is an 'our' version and a 'their' version, there will be an ancestor version as well. + + If there is an ancestor version, it may or may not be the same as the 'our' version or the 'their' version. + + In some cases, the 'our' version will be correct already and we do not need to do anything. + + Otherwise, we may need to: + + * change the 'our' version to incorporate changes to the 'their' version; + * delete the 'our' version; or + * create a new version (when there is no 'our' version but there is a 'their' version). + + The [`Conflict`] contains all three versions. + + Any resulting changes to 'our' are to be placed in the [`ChangeList`]. */ pub fn resolve_conflict( &self, world: &mut World, @@ -669,39 +736,46 @@ impl ItemType { trace(&format!("conflict detected {:#?}", &conflict)); // for now, do something simple match &conflict.our { - None => Ok(()), + None => match &conflict.their { + None => Ok(()), // item was in ancestor but we and server have both deleted it + Some(t) => match &conflict.ancestor { + None => { + // item has been added in remote + let (tib, tv) = split_data_parts(&t.data)?; + let type_name = tib.type_name.clone(); + let item_type_rcrc = world.get_item_type(type_name.to_string())?; + let item_type = item_type_rcrc.deref().borrow(); + let item_data_boxed = item_type.from_yaml(&tv, world)?; + Item::change_using_parts( + &tib.type_name, + &tib, + item_data_boxed, + &t.path, + changes, + world, + )?; + Ok(()) + } + Some(_a) => Ok(()), // we have deleted it, don't add it back + }, + }, Some(o) => { let (oib, ov) = split_data_parts(&o.data)?; - // let mut os = Simple::new(); - // os.set_from_yaml_basic(ov)?; match &conflict.their { - None => Ok(()), + None => Ok(()), // we have it but not the remote Some(t) => { let (_tib, tv) = split_data_parts(&t.data)?; match &conflict.ancestor { - None => Ok(()), + None => Ok(()), // we have created it, keep Some(a) => { + // item modified locally or in remote let (_aib, av) = split_data_parts(&a.data)?; - // let mut ts = Simple::new(); - // ts.set_from_yaml_basic(tv)?; - // os.name = merge_strings(&os.name, &ts.name); - // os.text = merge_strings(&os.text, &ts.text); - // trace(&format!("merged to {} and {}", os.name, os.text)); - // // let item_type_rcrc = world.get_item_type(oib.type_name.clone())?; - // // let item = Item:: from_parts(oib, item_type_rcrc , Box::new(os)); - // // let merged_yaml = item.to_yaml()?; - // // let change = taipo_git_control::Change::new( - // // taipo_git_control::ObjectOperation::Modify(String::from_utf8_lossy( &merged_yaml).to_string()), - // // o.path.clone(), - // // "resolve conflict".to_owned(), - // // ); - // // changes.push(change); - // } - let ib = self.policy.resolve_conflict_both(world, av, ov, tv)?; + let item_data_boxed = + self.policy.resolve_conflict_both(world, &av, &ov, &tv)?; Item::change_using_parts( &oib.type_name, &oib, - ib, + item_data_boxed, &o.path, changes, world, @@ -714,6 +788,10 @@ impl ItemType { } } } + /** get item data from serde value */ + pub fn from_yaml(&self, values: &Value, world: &mut World) -> FLResult> { + self.policy.from_yaml(values, world) + } } /** ref counted reference to an [`ItemType`] */ pub type ItemTypeRef = Rc>; @@ -730,13 +808,14 @@ pub trait ItemTypePolicy: Debug { // conflict: &Conflict, // changes: &mut ChangeList, // ) -> NullResult; - /** generate changes to resolve a merge conflict where both version have the item */ + /** generate changes to resolve a merge conflict where both + versions ('ours' and 'theirs') contain the item */ fn resolve_conflict_both( &self, world: &mut World, - ancestor: Value, - ours: Value, - theirs: Value, + ancestor: &Value, + ours: &Value, + theirs: &Value, ) -> FLResult>; /** check whether the Item can be updated using the specified values. It needs to return any errors if any user-supplied value is wrong. */ @@ -746,6 +825,8 @@ pub trait ItemTypePolicy: Debug { vals: &HashMap, world: &mut World, ) -> ActionResponse; + /** get item data from serde value */ + fn from_yaml(&self, values: &Value, world: &mut World) -> FLResult>; } /** simple enum, each [`ItemKind`] has an [`ItemType`]*/ #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] @@ -927,6 +1008,10 @@ impl ItemListEntryList { pub fn has_entries(&self) -> bool { !self.entries.is_empty() } + /** number of entries in list */ + pub fn num_entries(&self) -> usize { + self.entries.len() + } /** create from a list of [`ItemLink`]s */ pub fn from_links(links: &mut Vec, world: &mut World) -> Self { Self::from_vec( @@ -936,6 +1021,33 @@ impl ItemListEntryList { .collect(), ) } + /** */ + pub fn filter_on_item

(&mut self, mut predicate: P, world: &mut World) -> FLResult + where + P: FnMut(&mut Item, &mut World) -> FLResult, + { + self.filter_on_entry( + |ie, world| { + let item_rcrc = ItemLink::from(ie.link.clone()).resolve_link(world)?; + let mut item = item_rcrc.deref().borrow_mut(); + predicate(&mut item, world) + }, + world, + ) + } + /** */ + pub fn filter_on_entry

(&mut self, mut predicate: P, world: &mut World) -> FLResult + where + P: FnMut(&mut ItemListEntry, &mut World) -> FLResult, + { + let mut list: Vec = vec![]; + for ie in &mut self.entries { + if predicate(ie, world)? { + list.push(ie.clone()); + } + } + Ok(Self::from_vec(list)) + } } /** an identifier (actually a String) */ pub type Ident = String; @@ -1013,7 +1125,7 @@ impl ItemLink { pub fn resolve_link(&mut self, world: &mut World) -> FLResult { match &self.data { ItemLinkData::Unresolved(id) => { - let ir = world.get_item(id.to_string())?; + let ir = world.get_item(id.to_string(), "Simple".to_string())?; self.data = ItemLinkData::Resolved(ItemWeakRef::from(&ir)); Ok(ir) } @@ -1021,6 +1133,11 @@ impl ItemLink { } } } +impl PartialEq for ItemLink { + fn eq(&self, other: &Self) -> bool { + self.ident().unwrap_or("?".to_string()) == other.ident().unwrap_or("??".to_string()) + } +} impl From for ItemLink { /** create an ItemLinkForSerde from an ItemLink -- just copy the Ident */ @@ -1125,7 +1242,7 @@ impl ShowBaseTemplate { /** fill in fields */ pub fn from_base(base: &mut ItemBase, world: &mut World) -> FLResult { let parent = base.parent_for_display(world)?; - let children = base.get_item_children(world)?; + let children = base.get_open_children(world)?; let has_children = !children.entries.is_empty(); Ok(Self { ident: base.get_ident(), @@ -1152,7 +1269,7 @@ mod tests { let item_type = super::ItemType::new(Box::new(crate::simple::SimpleTypePolicy {})); reg.register(item_type); let _item_type2 = reg.get(super::ItemKind::Simple)?; - // TODO code me: assert_eq!(item_type.clone().borrow().mark, item_type2.borrow().mark); // make sense? + // TODO code: assert_eq!(item_type.clone().borrow().mark, item_type2.borrow().mark); // make sense? Ok(()) } } diff --git a/fanling-engine/src/lib.rs b/fanling-engine/src/lib.rs index 6e57915..6b32f77 100644 --- a/fanling-engine/src/lib.rs +++ b/fanling-engine/src/lib.rs @@ -63,6 +63,7 @@ mod task; mod world; use crate::item::ItemBaseForSerde; pub use crate::shared::{FLResult, FanlingError, NullResult, Tracer}; +use fanling_interface::error_response_result; use log::trace; pub use search::SearchOptions; use serde::{Deserialize, Serialize}; @@ -88,6 +89,8 @@ pub enum Action { Archive, // Search, /* needs search criteria */ ListReady, + ListOpen, + ListAll, New, NewChild(item::Ident), Create(ItemBaseForSerde, HashMap), @@ -104,15 +107,14 @@ pub enum Action { impl Action { fn kind(&self) -> ActionKind { match self { - Action::Shutdown | Action::PushAndQuit { force: _ } => { - ActionKind::Engine - } + Action::Shutdown | Action::PushAndQuit { force: _ } => ActionKind::Engine, Action::Start | Action::Pull | Action::Create(_, _) | Action::Update(_, _) -// | Action::Search | Action::ListReady + | Action::ListOpen + | Action::ListAll | Action::Delete | Action::GetAll | Action::CheckData @@ -124,8 +126,9 @@ impl Action { | Action::Edit | Action::Archive | Action::Close - | Action::Reopen | Action::BlockBy(_) | - Action::UnblockBy(_)=> ActionKind::Item, + | Action::Reopen + | Action::BlockBy(_) + | Action::UnblockBy(_) => ActionKind::Item, Action::Unknown => panic!("unknown action"), } } @@ -233,7 +236,7 @@ impl FanlingEngine { match basic_request.action { Action::Shutdown => self.shutdown(), Action::PushAndQuit { force } => self.push_and_shutdown(force), - _ => panic!("invalid action {:?}", basic_request.action), + _ => error_response_result(&format!("invalid action {:?}", basic_request.action)), } } fn push_and_shutdown(&mut self, force: bool) -> fanling_interface::ResponseResult { @@ -254,15 +257,16 @@ impl FanlingEngine { } impl fanling_interface::Engine for FanlingEngine { fn execute(&mut self, body: &str) -> fanling_interface::ResponseResult { - fanling_trace!(&format!("executing «{}»", &body)); + fanling_trace!(&format!("executing action «{}»", &body)); let now = SystemTime::now(); - let json_result = serde_json::from_str(&body); - let json_value: serde_json::value::Value = match json_result { + let json_body = serde_json::from_str(&body); + let json_value: serde_json::value::Value = match json_body { Err(e) => { + let msg = format!("fanling error: {:?}", &e); let re = FanlingError::from(e); re.dump(file!(), line!(), column!()); if !cfg!(android) { - panic!("fanling error"); + panic!(msg); } else { serde_json::value::Value::default() } @@ -270,22 +274,22 @@ impl fanling_interface::Engine for FanlingEngine { Ok(v) => v, }; //dump_fanling_error!(serde_json::from_str(&body)); - trace("getting basic request from JSON"); + fanling_trace!("getting basic request from JSON"); let basic_request: BasicRequest = serde_json::from_value(json_value.clone())?; - trace(&format!( - "action: basic request {:?}, kind {:?}", + fanling_trace!(&format!( + "starting execute action: basic request {:?}, kind {:?}", basic_request, basic_request.action.kind() )); let res = match basic_request.action.kind() { ActionKind::Engine => self.do_engine_action(&basic_request), - ActionKind::World | // ActionKind::ItemType | - ActionKind::Item => { + ActionKind::World | ActionKind::Item => { self.world.do_action(&basic_request, json_value) } }; + fanling_trace!("action done"); trace(&format!( - "execute {:?} took {}s ", //giving {:?}", + "execute action done, {:?} took {}s ", //giving {:?}", basic_request.action, now.elapsed()?.as_millis() as f64 / 1000.0, // &res diff --git a/fanling-engine/src/search/mod.rs b/fanling-engine/src/search/mod.rs index 9812243..da33a3c 100644 --- a/fanling-engine/src/search/mod.rs +++ b/fanling-engine/src/search/mod.rs @@ -79,7 +79,6 @@ impl Search { type_name: &itemx.type_name(), name: &itemx.description(), open: itemx.is_open(), - ready: itemx.is_ready(), parent, sort: &itemx.get_sort(), classify: itemx.classify(), @@ -87,6 +86,7 @@ impl Search { targeted: itemx.targeted(), }, )?) + // TODO: create in other tables } /** remove an [`Item`] from the set of searchable values */ pub fn delete_item(&mut self, item: ItemRef) -> NullResult { @@ -94,6 +94,7 @@ impl Search { let ident = itemx.ident(); let num_deleted = diesel::delete(dsl::item.find(&ident)).execute(&self.connect)?; assert_eq!(1, num_deleted); + // TODO: delete from other tables trace(&format!("deleted item '{:?}' from search", &ident)); Ok(()) } @@ -107,7 +108,6 @@ impl Search { .set(( dsl::name.eq(itemx.description()), dsl::open.eq(itemx.is_open()), - dsl::ready.eq(itemx.is_ready()), dsl::parent.eq(itemx.parent_ident()), dsl::sort.eq(itemx.get_sort()), dsl::classify.eq(itemx.classify()), @@ -115,6 +115,7 @@ impl Search { dsl::targeted.eq(itemx.targeted()), )) .execute(&self.connect)?; + // TODO: update other tables trace(&format!("updated item '{:?}' in search", &ident)); Ok(()) } @@ -127,6 +128,16 @@ impl Search { }; Ok(iter) } + /** search everything with hierarchy */ + pub fn search_all_hier(&self) -> FLResult { + let results = models::search_all_hier(&self.connect)?; + trace(&format!("hier found {} entries", results.entries.len())); + let iter = ItemListEntryList { + entries: results.entries, + final_adjust_level: "".to_owned(), + }; + Ok(iter) + } // /** search everything for ready */ // pub fn search_ready(&self) -> FLResult { // let results = models::search_ready(&self.connect)?; @@ -145,9 +156,9 @@ impl Search { }; Ok(iter) } - /** search for children */ - pub fn search_ready_children(&self, parent_ident: &str) -> FLResult { - let results = models::search_ready_children(&self.connect, parent_ident)?; + /** search for children with open status */ + pub fn search_open_children(&self, parent_ident: &str) -> FLResult { + let results = models::search_open_children(&self.connect, parent_ident)?; let iter = ItemListEntryList { entries: results.entries, final_adjust_level: "".to_owned(), @@ -155,8 +166,8 @@ impl Search { Ok(iter) } /** search everything for ready with hierarchy */ - pub fn search_ready_hier(&self) -> FLResult { - let results = models::search_ready_hier(&self.connect)?; + pub fn search_open_hier(&self) -> FLResult { + let results = models::search_open_hier(&self.connect)?; trace(&format!("hier found {} entries", results.entries.len())); let iter = ItemListEntryList { entries: results.entries, diff --git a/fanling-engine/src/search/models.rs b/fanling-engine/src/search/models.rs index 7d272fb..3ab1410 100644 --- a/fanling-engine/src/search/models.rs +++ b/fanling-engine/src/search/models.rs @@ -20,7 +20,6 @@ pub struct DslItem { _type_name: String, name: String, _open: bool, - _ready: bool, _parent: Option, _sort: String, _classify: String, @@ -45,7 +44,6 @@ pub struct NewItem<'a> { pub type_name: &'a str, pub name: &'a str, pub open: bool, - pub ready: bool, pub parent: Option<&'a str>, pub sort: &'a str, pub classify: String, @@ -110,12 +108,12 @@ pub fn search_special(conn: &SqliteConnection, sk: SpecialKind) -> FLResult FLResult { let results = item::dsl::item - .filter(item::columns::ready.and(item::columns::parent.eq(parent_ident))) + .filter(item::columns::open.and(item::columns::parent.eq(parent_ident))) .load::(conn)?; let ilev: Vec = results.into_iter().map(DslItem::into).collect(); Ok(ItemListEntryList::from_vec(ilev)) @@ -133,7 +131,6 @@ pub struct DslItemHier { _type_name: String, name: String, _open: bool, - _ready: bool, _parent: Option, _sort: String, _classify: String, @@ -151,8 +148,14 @@ impl Into for DslItemHier { } } -/** find all ready items in the database in hierarchy */ -pub fn search_ready_hier(conn: &SqliteConnection) -> FLResult { +/** find all items in the database in hierarchy */ +pub fn search_all_hier(conn: &SqliteConnection) -> FLResult { + let results = item_by_level::dsl::item_by_level.load::(conn)?; + let ilev: Vec = results.into_iter().map(DslItemHier::into).collect(); + Ok(ItemListEntryList::from_vec(ilev)) +} +/** find all open items in the database in hierarchy */ +pub fn search_open_hier(conn: &SqliteConnection) -> FLResult { let results = item_by_level::dsl::item_by_level .filter(item_by_level::columns::open) .load::(conn)?; diff --git a/fanling-engine/src/search/schema.rs b/fanling-engine/src/search/schema.rs index f19895c..d11cf49 100644 --- a/fanling-engine/src/search/schema.rs +++ b/fanling-engine/src/search/schema.rs @@ -12,7 +12,6 @@ table! { type_name -> Text, name -> Text, open -> Bool, - ready -> Bool, parent -> Nullable, sort -> Text, classify -> Text, @@ -30,7 +29,6 @@ table! { type_name -> Text, name -> Text, open -> Bool, - ready -> Bool, parent -> Nullable, sort -> Text, classify -> Text, diff --git a/fanling-engine/src/simple.rs b/fanling-engine/src/simple.rs index f168890..1606680 100644 --- a/fanling-engine/src/simple.rs +++ b/fanling-engine/src/simple.rs @@ -35,8 +35,8 @@ impl Simple { text: "".to_owned(), } } - fn set_from_yaml_basic(&mut self, yaml: serde_yaml::Value) -> NullResult { - *self = serde_yaml::from_value(yaml)?; + fn set_from_yaml_basic(&mut self, yaml: &serde_yaml::Value) -> NullResult { + *self = serde_yaml::from_value(yaml.clone())?; // self.fix_data(); Ok(()) } @@ -58,7 +58,10 @@ impl crate::item::ItemData for Simple { let mut resp = fanling_interface::Response::new(); resp.clear_errors(vec!["name-error".to_owned()]); resp.add_tag("content", &(nt.render()?)); - resp.set_ident(base.get_ident()); + #[cfg(test)] + { + resp.set_test_data("ident", &base.get_ident()); + } trace(&format!("for edit {:?}", &resp)); Ok(resp) } @@ -89,8 +92,12 @@ impl crate::item::ItemData for Simple { fn is_open(&self) -> bool { true } - fn is_ready(&self) -> bool { - true + fn is_ready(&mut self, _world: &mut World) -> FLResult { + Ok(true) + } + /** can be turned into an ident */ + fn descr_for_ident(&self) -> String { + self.name.clone() } /** an English-language description */ fn description(&self) -> String { @@ -111,8 +118,8 @@ impl crate::item::ItemData for Simple { } Ok(()) } - fn set_from_yaml(&mut self, yaml: serde_yaml::Value, _world: &mut World) -> NullResult { - *self = serde_yaml::from_value(yaml)?; + fn set_from_yaml(&mut self, yaml: &serde_yaml::Value, _world: &mut World) -> NullResult { + *self = serde_yaml::from_value(yaml.clone())?; Ok(()) } /** do action for simple -- should never get called */ @@ -133,6 +140,23 @@ impl crate::item::ItemData for Simple { text: self.text.clone(), })) } + /** transitional to fix old data */ + fn fix_data( + &self, + _yaml: &serde_yaml::Value, + _base: &mut ItemBase, + _world: &mut World, + ) -> NullResult { + Ok(()) + } +} +impl Default for Simple { + fn default() -> Self { + Self { + name: "".to_owned(), + text: "".to_owned(), + } + } } #[derive(Serialize, Deserialize)] struct SimpleForSerde { @@ -141,23 +165,6 @@ struct SimpleForSerde { #[serde(flatten)] data: Simple, } -// impl SimpleForSerde { -// /** create a new [`SimpleForSerde`] */ -// pub fn new() -> Self { -// Self { -// name: "".to_owned(), -// text: "".to_owned(), -// } -// } -// /** set from YAML data */ -// // // /** ensure consistency of the data */ -// // // fn fix_data(&mut self) { -// // // if self.closed { -// // // self.status = TaskStatus::Closed; -// // // } -// // // // TODO project -> parent -// // // } -// } /** template data for creating a new simple item */ #[derive(Template)] #[template(path = "new-simple.html", print = "none")] @@ -198,14 +205,14 @@ impl crate::item::ItemTypePolicy for SimpleTypePolicy { fn resolve_conflict_both( &self, _world: &mut World, - _ancestor: Value, - ours: Value, - theirs: Value, + _ancestor: &Value, + ours: &Value, + theirs: &Value, ) -> FLResult> { let mut os = Simple::new(); - os.set_from_yaml_basic(ours)?; + os.set_from_yaml_basic(&ours)?; let mut ts = Simple::new(); - ts.set_from_yaml_basic(theirs)?; + ts.set_from_yaml_basic(&theirs)?; os.name = merge_strings(&os.name, &ts.name); os.text = merge_strings(&os.text, &ts.text); Ok(Box::new(os)) @@ -224,6 +231,12 @@ impl crate::item::ItemTypePolicy for SimpleTypePolicy { ); ar } + /** get item data from serde value */ + fn from_yaml(&self, values: &Value, world: &mut World) -> FLResult> { + let mut s = Simple::default(); + s.set_from_yaml(&values, world)?; + Ok(Box::new(s)) + } } /** convenience function for debug traces */ diff --git a/fanling-engine/src/store.rs b/fanling-engine/src/store.rs index 2c3dcec..aecf6a7 100644 --- a/fanling-engine/src/store.rs +++ b/fanling-engine/src/store.rs @@ -159,18 +159,20 @@ impl Store { /** get the raw data for the item, both the parts common to all types of item and the parts specific to this kind of item. */ pub fn get_item_parts(&self, ident: &Ident) -> FLResult<(ItemBaseForSerde, serde_yaml::Value)> { fanling_trace!(&format!("getting parts of '{}'", ident)); - assert_ne!(ident, "", "Ident is blank"); - // let data = self.repo.blob_from_path(&self.path_from_ident(ident))?; + assert!(!ident.is_empty(), "Ident is blank when getting item parts"); let data = self.get_serialised(ident)?; - // let serde_value: serde_yaml::Value = serde_yaml::from_slice(&data)?; - // let base: ItemBaseForSerde = serde_yaml::from_value(serde_value.clone())?; - // trace("got parts."); - // Ok((base, serde_value.clone())) crate::item::split_data_parts(&data) } /** get the serialised data for an item */ fn get_serialised(&self, ident: &Ident) -> FLResult> { - Ok(self.repo.blob_from_path(&self.path_from_ident(ident))?) + let vec_res = self.repo.blob_from_path(&self.path_from_ident(ident)); + trace("result from bfp"); + if let Err(e) = &vec_res { + fanling_trace!("error result"); + trace(&format!("error was {:?}", e)); + } + trace(&format!("as serialised {:?}", &vec_res)); + Ok(vec_res?) } /** create an [`Item`] from YAML and add it to the known map. */ pub fn make_known(&mut self, item_rcrc: ItemRef) -> FLResult { diff --git a/fanling-engine/src/task.rs b/fanling-engine/src/task.rs index 671dc7c..860fb1c 100644 --- a/fanling-engine/src/task.rs +++ b/fanling-engine/src/task.rs @@ -16,6 +16,7 @@ use ansi_term::Colour; use askama::Template; use chrono::offset::TimeZone; use chrono::{NaiveDateTime, Utc}; +use fanling_interface::error_response_result; use log::trace; use serde::de::Deserializer; use serde::{Deserialize, Serialize}; @@ -181,6 +182,35 @@ impl Task { // self.when_closed = Utc::now().naive_utc(); Ok(()) } + /** block this task by the task with the `ident` */ + fn block(&mut self, ident: &str) -> NullResult { + self.blockedby.push(ItemLink::new(ident.to_string())); + // TODO: checks; prevent duplications + Ok(()) + } + /** unblock this task by the task with the `ident` */ + fn unblock(&mut self, ident: &str) -> NullResult { + self.blockedby.retain(|il| il.ident().unwrap() != ident); + Ok(()) + } + #[cfg(test)] + fn set_test_data( + &mut self, + resp: &mut fanling_interface::Response, + base: &mut ItemBase, + world: &mut World, + ) { + resp.set_test_data("ident", &base.get_ident()); + resp.set_test_data( + "ready", + if self.is_ready(world).unwrap() { + "true" + } else { + "false" + }, + ); + resp.set_test_data("open", if self.is_open() { "true" } else { "false" }); + } } impl crate::item::ItemData for Task { fn for_edit( @@ -192,6 +222,7 @@ impl crate::item::ItemData for Task { let broken_text = self.text.replace("\n", " "); trace(&format!("{} converted to {}", self.text, broken_text)); let contexts = self.get_contexts(world)?; + let blockedby = ItemListEntryList::from_links(&mut self.blockedby, world); let nt = NewTaskTemplate { data: &self, base: NewBaseTemplate::from_base(base, is_for_update, world)?, @@ -202,7 +233,7 @@ impl crate::item::ItemData for Task { when_closed: self.when_closed, deadline: self.deadline, show_after_date: self.show_after_date, - blockedby: ItemListEntryList::from_vec(vec![]), /* TODO: code */ + blockedby, }; let mut resp = fanling_interface::Response::new(); resp.clear_errors(vec![ @@ -212,7 +243,10 @@ impl crate::item::ItemData for Task { "".to_owned(), ]); resp.add_tag("content", &(nt.render()?)); - resp.set_ident(base.get_ident()); + #[cfg(test)] + { + self.set_test_data(&mut resp, base, world); + } trace(&format!("for edit {:?}", &resp)); Ok(resp) } @@ -233,13 +267,18 @@ impl crate::item::ItemData for Task { deadline: self.deadline, show_after_date: self.show_after_date, blockedby: ItemListEntryList::from_links(&mut self.blockedby, world), - potential_blockers: world.search_ready_hier()?, + potential_blockers: world.search_open_hier()?, }; let mut resp = fanling_interface::Response::new(); resp.add_tag("content", &(t.render()?)); + #[cfg(test)] + { + self.set_test_data(&mut resp, base, world); + } trace(&format!("for show {:?}", &resp)); Ok(resp) } + fn to_yaml(&self, base: &crate::item::ItemBase) -> Result, FanlingError> { let for_serde = TaskItemForSerde { base: crate::item::ItemBaseForSerde::from_base(base)?, @@ -255,18 +294,40 @@ impl crate::item::ItemData for Task { TaskStatus::Closed => false, } } - fn is_ready(&self) -> bool { + fn is_ready(&mut self, world: &mut World) -> FLResult { if !self.is_open() { - return false; + trace("task is not ready because not open"); + return Ok(false); } let now = Utc::now().naive_utc(); if self.show_after_date != NaiveDateTime::from_timestamp(0, 0) && self.show_after_date.cmp(&now) == Ordering::Greater { - return false; + trace("task is not ready because before show-after date"); + return Ok(false); + } + for bi in &mut self.blockedby { + let bir = bi.resolve_link(world)?; + let bi = bir.deref().borrow(); + if bi.is_open() { + trace(&format!( + "task is not ready because blocked by {}", + bi.ident() + )); + return Ok(false); + } else { + trace(&format!( + "task is not blocked by {} because not open", + bi.ident() + )); + } } - // TODO finish coding (blocking) - true + trace("task is ready"); + Ok(true) + } + /** can be turned into an ident */ + fn descr_for_ident(&self) -> String { + self.name.clone() } /** an English-language description */ fn description(&self) -> String { @@ -301,7 +362,8 @@ impl crate::item::ItemData for Task { }; self.context = match vals.get("context") { Some(c) => { - let context_link: ItemLink = ItemLink::from(world.get_item(c.to_string())?); + let context_link: ItemLink = + ItemLink::from(world.get_item(c.to_string(), "Simple".to_string())?); trace(&format!( "from {:?} made context link {:?} in set data", &c, &context_link @@ -312,15 +374,15 @@ impl crate::item::ItemData for Task { }; self.deadline = match vals.get("deadline") { Some(dl) => Utc.datetime_from_str(dl, "%F %T")?.naive_utc(), - _ => return Err(fanling_error!("unvalidated bad date")), + _ => NaiveDateTime::from_timestamp(0, 0), }; self.show_after_date = match vals.get("show_after_date") { Some(dl) => Utc.datetime_from_str(dl, "%F %T")?.naive_utc(), - _ => return Err(fanling_error!("unvalidated bad date")), + _ => NaiveDateTime::from_timestamp(0, 0), }; Ok(()) } - fn set_from_yaml(&mut self, yaml: serde_yaml::Value, world: &mut World) -> NullResult { + fn set_from_yaml(&mut self, yaml: &serde_yaml::Value, world: &mut World) -> NullResult { // *self = serde_yaml::from_value(yaml)?; trace("setting task from yaml..."); let mut tfs = TaskForSerde::default(); @@ -337,7 +399,6 @@ impl crate::item::ItemData for Task { world: &mut World, ) -> fanling_interface::ResponseResult { match &action { - // TODO block and unblock crate::Action::Close => { self.close(world)?; Ok(self.for_show(base, world)?) @@ -346,9 +407,15 @@ impl crate::item::ItemData for Task { self.reopen(world)?; Ok(self.for_show(base, world)?) } - crate::Action::BlockBy(_ident) => unimplemented!(), - crate::Action::UnblockBy(_ident) => unimplemented!(), - _ => panic!("invalid action {:?}", action), + crate::Action::BlockBy(ident) => { + self.block(&ident)?; + Ok(self.for_show(base, world)?) + } + crate::Action::UnblockBy(ident) => { + self.unblock(&ident)?; + Ok(self.for_show(base, world)?) + } + _ => error_response_result(&format!("invalid action {:?}", action)), } } /** copy from another item data. But status is Open and no blockers. */ @@ -366,6 +433,38 @@ impl crate::item::ItemData for Task { blockedby: vec![], })) } + /** transitional code to fix some old data */ + fn fix_data( + &self, + yaml: &serde_yaml::Value, + base: &mut ItemBase, + world: &mut World, + ) -> NullResult { + // trace("fixing task data..."); + if let Some(project) = yaml.get("project") { + // trace("task has project"); + if let Some(project_name) = project.as_str() { + if !base.has_parent() && !project_name.is_empty() { + trace(&format!( + "no parent and task has project '{}'", + &project_name + )); + let parent_item_rcrc = + world.get_item(project_name.to_owned(), "Task".to_owned())?; + let parent_item = parent_item_rcrc.deref().borrow(); + let parent_link = ItemLink::from(&*parent_item); + trace(&format!( + "setting parent for '{}' to '{}'", + base.get_ident(), + parent_item.ident(), + )); + base.set_parent(Some(parent_link)); + assert_ne!("??", base.get_ident()); + } + } + } + Ok(()) + } } /** task item in a form that can be serialised */ #[derive(Serialize, Deserialize)] @@ -447,8 +546,8 @@ impl Default for TaskForSerde { } impl TaskForSerde { /** set from YAML data */ - fn set_from_yaml(&mut self, yaml: serde_yaml::Value) -> NullResult { - *self = serde_yaml::from_value(yaml)?; + fn set_from_yaml(&mut self, yaml: &serde_yaml::Value) -> NullResult { + *self = serde_yaml::from_value(yaml.clone())?; self.fix_data(); Ok(()) } @@ -457,7 +556,6 @@ impl TaskForSerde { if self.closed { self.status = TaskStatus::Closed; } - // TODO project -> parent } } impl TryFrom<&Task> for TaskForSerde { @@ -469,7 +567,9 @@ impl TryFrom<&Task> for TaskForSerde { context: task .context .as_ref() - .ok_or_else(|| fanling_error!("no context found from task"))? + .ok_or_else(|| { + fanling_error!(&format!("no context found from task: {:#?}", &task)) + })? .ident()?, status: task.status.clone(), priority: task.priority.clone(), @@ -542,9 +642,9 @@ impl crate::item::ItemTypePolicy for TaskTypePolicy { fn resolve_conflict_both( &self, world: &mut World, - _ancestor: Value, - ours: Value, - theirs: Value, + _ancestor: &Value, + ours: &Value, + theirs: &Value, ) -> FLResult> { let mut ots = TaskForSerde::default(); ots.set_from_yaml(ours)?; @@ -554,7 +654,22 @@ impl crate::item::ItemTypePolicy for TaskTypePolicy { let tt = Task::task_from(&mut tts, world)?; ot.name = merge_strings(&ot.name, &tt.name); ot.text = merge_strings(&ot.text, &tt.text); - // TODO: other fields + // ot.context = ""; + if ot.status == TaskStatus::Open { + if tt.status == TaskStatus::Closed { + ot.status = TaskStatus::Closed; + ot.when_closed = tt.when_closed; + } + } + ot.priority = std::cmp::min(ot.priority, tt.priority); + // ot.project = ""; + ot.deadline = std::cmp::min(ot.deadline, tt.deadline); + ot.show_after_date = std::cmp::min(ot.show_after_date, tt.show_after_date); + for t in tt.blockedby { + if !ot.blockedby.contains(&t) { + ot.blockedby.push(t); + } + } Ok(Box::new(ot)) } fn check_valid( @@ -588,6 +703,13 @@ impl crate::item::ItemTypePolicy for TaskTypePolicy { ); ar } + /** get item data from serde value */ + fn from_yaml(&self, values: &Value, world: &mut World) -> FLResult> { + let mut ts = TaskForSerde::default(); + ts.set_from_yaml(values)?; + let t = Task::task_from(&mut ts, world)?; + Ok(Box::new(t)) + } } /** convenience function for debug traces */ fn trace(m: &str) { diff --git a/fanling-engine/src/test/mod.rs b/fanling-engine/src/test/mod.rs new file mode 100644 index 0000000..68a4b02 --- /dev/null +++ b/fanling-engine/src/test/mod.rs @@ -0,0 +1,302 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +/*! tests for engine */ +use super::*; +use crate::fanling_interface::Engine; +use std::fs; +use std::path::PathBuf; +#[cfg(test)] +mod utils; +#[test] +fn simple() -> crate::shared::NullResult { + trace("simple test: start"); + + const TEST_DIR1: &str = "testfiles1"; + let (test_dir, database_path) = utils::init_files(TEST_DIR1, "test-no-name"); + let mut options = utils::simple_options(&test_dir, &database_path); + { + trace("simple test: first engine - creating"); + let mut engine = super::FanlingEngine::new(&options)?; + trace("simple test: first engine - executing create item"); + let _response = engine.execute(&utils::create_simple_action("aaa"))?; + let _response = engine.handle_event(&fanling_interface::CycleEvent::StopPC)?; + } + { + trace("simple test: second engine"); + options.uniq_pfx = "b".to_string(); + let mut engine = super::FanlingEngine::new(&options)?; + let _response = engine.execute(&utils::update_simple_action("aaa-a2", "bbb", "bbbb"))?; + let _response = engine.handle_event(&fanling_interface::CycleEvent::StopPC)?; + } + { + trace("simple test: third engine"); + options.uniq_pfx = "c".to_string(); + let mut engine = super::FanlingEngine::new(&options)?; + let response = engine.execute(r#"{"t":"","i":"aaa-a2","a":"Show"}"#)?; + // trace(&format!("response is {:?}", response)); + assert_eq!(response.num_tags(), 2); + assert_eq!(response.get_tag(0).0, "content"); + assert_eq!(response.get_tag(1).0, "always"); + assert!(!response.is_shutdown_required()); + let response = engine.execute(r#"{"a":"Shutdown","i":"","t":""}"#)?; + assert!(response.is_shutdown_required()); + } + // FUTURE more tests + trace("simple test: done."); + Ok(()) +} +#[test] +fn local() -> crate::shared::NullResult { + let local_test_cases = vec![ + LocalTestCase { + narr: "simple".to_string(), + create_action: utils::create_simple_action("aaa"), + expected_text_after_create: "aaaa".to_string(), + update_action2: utils::update_simple_action("aaa-o2", "bbb", "bbbb"), + expected_text_after_update: "bbbb".to_string(), + update_action3: utils::update_simple_action("aaa-o2", "bbb", "cccc"), + expected_text_at_end: "ccccbbbb".to_string(), + field_key: "text".to_string(), + test_ident: "aaa-o2".to_string(), + }, + LocalTestCase { + narr: "task".to_string(), + create_action: utils::create_task_action("aaaa", "aaaaa"), + expected_text_after_create: "aaaaa".to_string(), + update_action2: utils::update_task_action("aaaa-o2", "aaaa", "bbbbb"), + expected_text_after_update: "bbbbb".to_string(), + update_action3: utils::update_task_action("aaaa-o2", "aaaa", "ccccc"), + expected_text_at_end: "cccccbbbbb".to_string(), + field_key: "text".to_string(), + test_ident: "aaaa-o2".to_string(), + }, + ]; + for test_case in local_test_cases { + local_test(&test_case)? + } + Ok(()) +} +struct LocalTestCase { + narr: String, + create_action: String, + expected_text_after_create: String, + update_action2: String, + expected_text_after_update: String, + update_action3: String, + expected_text_at_end: String, + field_key: String, + test_ident: String, +} +fn local_test(ltc: &LocalTestCase) -> crate::shared::NullResult { + fanling_trace!(&format!("local test: {}", <c.narr)); + const TEST_DIR1: &str = "testfiles2"; + let (test_dir, database_path) = utils::init_files(TEST_DIR1, "test-local"); + let mut options = utils::simple_options(&test_dir, &database_path); + options.uniq_pfx = "o".to_string(); + { + trace("local test: create item"); + let mut engine = super::FanlingEngine::new(&options)?; + engine.execute(<c.create_action)?; + engine.handle_event(&fanling_interface::CycleEvent::StopPC)?; + dump_fanling_error!(utils::check_engine( + &mut engine, + <c.expected_text_after_create, + <c.field_key, + <c.test_ident + )); + } + + trace("local test: repo2 - update"); + let mut engine2 = utils::test_engine(TEST_DIR1, "test-local", "test-local2", "p")?; + engine2.execute(<c.update_action2)?; + trace("local test: repo3 - update"); + let mut engine3 = utils::test_engine(TEST_DIR1, "test-local", "test-local3", "q")?; + engine3.execute(<c.update_action3)?; + + trace("local test: repo2 - push"); + utils::pull_push_and_shutdown(&mut engine2)?; + dump_fanling_error!(utils::check_engine( + &mut engine2, + <c.expected_text_after_update, + <c.field_key, + <c.test_ident + )); + { + trace("local test: check push from repo2"); + let mut engine = super::FanlingEngine::new(&options)?; + dump_fanling_error!(utils::check_engine( + &mut engine, + <c.expected_text_after_update, + <c.field_key, + <c.test_ident + )); + } + + trace("local test: repo3 - push"); + utils::pull_push_and_shutdown(&mut engine3)?; + dump_fanling_error!(utils::check_engine( + &mut engine2, + <c.expected_text_after_update, + <c.field_key, + <c.test_ident + )); + { + trace("local test: check push from repo"); + let mut engine = super::FanlingEngine::new(&options)?; + dump_fanling_error!(utils::check_engine( + &mut engine, + <c.expected_text_at_end, + <c.field_key, + <c.test_ident + )); + } + trace("local test: done."); + Ok(()) +} + +struct CloneTestCase { + narr: String, + create_action: String, + clone_action: String, + expected_text: String, + field_key: String, + test_ident: String, +} +#[test] +fn clone() -> crate::shared::NullResult { + trace("clone test: start"); + let test_cases = vec![ + CloneTestCase { + narr: "simple".to_string(), + create_action: utils::create_simple_action("aaa"), + clone_action: r#"{"t":"Simple","i":"aaa-a2","a":"Clone"}"#.to_string(), + expected_text: "aaaa".to_string(), + field_key: "text".to_string(), + test_ident: "aaa-b3".to_string(), + }, + CloneTestCase { + narr: "task".to_string(), + create_action: utils::create_task_action("aaaa", "aaaaa"), + clone_action: r#"{"t":"Task","i":"aaaa-a2","a":"Clone"}"#.to_string(), + expected_text: "aaaaa".to_string(), + field_key: "text".to_string(), + test_ident: "aaaa-b3".to_string(), + }, + ]; + for tc in test_cases { + trace(&format!("clone test: start {}", &tc.narr)); + const TEST_DIR1: &str = "testfiles3"; + let (test_dir, database_path) = utils::init_files(TEST_DIR1, "test-clone"); + let mut options = utils::simple_options(&test_dir, &database_path); + { + trace("clone test: first engine - creating"); + let mut engine = super::FanlingEngine::new(&options)?; + trace("clone test: first engine - executing create item"); + let response = engine.execute(&tc.create_action)?; + trace(&format!("{}: response {:?}", &tc.test_ident, &response)); + } + { + trace("clone test: clone"); + options.uniq_pfx = "b".to_string(); + let mut engine = super::FanlingEngine::new(&options)?; + let response = engine.execute(&tc.clone_action)?; + assert_eq!( + tc.test_ident.clone(), + response.get_test_data("ident"), + "{}: ident missing or wrong, response is {:#?}", + &tc.narr, + &response + ); + dump_fanling_error!(utils::check_engine( + &mut engine, + &tc.expected_text, + &tc.field_key, + &tc.test_ident + )); + let check_data = r#"{"a":"CheckData","i":"","t":""}"#; + let _resp = engine.execute(&check_data)?; + let _response = engine.handle_event(&fanling_interface::CycleEvent::StopPC)?; + } + } + Ok(()) +} +#[test] +fn no_name() -> crate::shared::NullResult { + trace("no name test: start"); + let create_actions: Vec = vec![ + utils::create_simple_action(""), + utils::create_task_action("", ""), + ]; + let mut i = 0; + for ca in create_actions { + trace(&format!("no name case {}", i)); + const TEST_DIR1: &str = "testfiles4"; + let (test_dir, database_path) = utils::init_files(TEST_DIR1, "test-no-name"); + let options = utils::simple_options(&test_dir, &database_path); + let mut engine = super::FanlingEngine::new(&options)?; + let resp = engine.execute(&ca)?; + // trace(&format!("result of execution is {:#?}", resp)); + assert!( + resp.num_tags() > 0, + "execution response should have tags {:#?}", + resp + ); + i += 1; + } + Ok(()) +} +#[test] +/// tests for task ready including blocking +fn ready_task() -> crate::shared::NullResult { + trace("ready task test: start"); + const TEST_DIR1: &str = "testfiles5"; + let (test_dir, database_path) = utils::init_files(TEST_DIR1, "test-ready"); + let options = utils::simple_options(&test_dir, &database_path); + let mut engine = super::FanlingEngine::new(&options)?; + let create1 = utils::create_task_action("t1", "task 1"); + let resp = engine.execute(&create1)?; + let ident1 = resp.get_test_data("ident"); + utils::check_test_data(&mut engine, &ident1, "ready", "true")?; + let create2 = utils::create_task_action("t2", "task 2"); + let resp = engine.execute(&create2)?; + let ident2 = resp.get_test_data("ident"); + utils::check_test_data(&mut engine, &ident2, "ready", "true")?; + let ready = r#"{"a":"ListReady","i":"","t":""}"#; + let resp = engine.execute(&ready)?; + assert_eq!("3", resp.get_test_data("count")); + let block = format!( + r#"{{"t":"Task","i":{:?},"a":{{"BlockBy":{:?}}}}}"#, + &ident1, &ident2 + ); + let unblock = format!( + r#"{{"t":"Task","i":{:?},"a":{{"UnblockBy":{:?}}}}}"#, + &ident1, &ident2 + ); + let _resp = engine.execute(&block)?; + utils::check_test_data(&mut engine, &ident1, "ready", "false")?; + let resp = engine.execute(&ready)?; + assert_eq!("2", resp.get_test_data("count")); + + trace("ready test: repo2 - update"); + let mut engine2 = utils::test_engine(TEST_DIR1, "test-ready", "test-ready2", "p")?; + utils::check_test_data(&mut engine2, &ident1, "ready", "false")?; + let _resp = engine.execute(&unblock)?; + utils::check_test_data(&mut engine, &ident1, "ready", "true")?; + let resp = engine.execute(&ready)?; + assert_eq!("3", resp.get_test_data("count")); + // TODO more testing: push to server and pull to second server + // TODO more testing: check in second repo for ready status + let _resp = engine.execute(&block)?; + let close_action = format!(r#"{{"t":"Task","i":"{}","a":"Close"}}"#, ident2); + let _response = engine.execute(&close_action)?; + utils::check_test_data(&mut engine, &ident1, "ready", "true")?; + let resp = engine.execute(&ready)?; + assert_eq!("2", resp.get_test_data("count")); + // TODO more testing: push to server and pull to second server + // TODO more testing: check in second repo for ready status + let check_data = r#"{"a":"CheckData","i":"","t":""}"#; + let _resp = engine.execute(&check_data)?; + Ok(()) +} diff --git a/fanling-engine/src/test/utils.rs b/fanling-engine/src/test/utils.rs new file mode 100644 index 0000000..6073bd1 --- /dev/null +++ b/fanling-engine/src/test/utils.rs @@ -0,0 +1,161 @@ +//* useful functions used in the tests +use super::*; +pub(crate) fn create_simple_action(name: &str) -> String { + format!( + r#"{{"t":"Simple","i":"","a":{{"Create":[{{"ident":"","type":"Simple"}},{{"name":"{}","text":"aaaa"}}]}}}}"#, + name + ) +} +pub(crate) fn create_task_action(name: &str, text: &str) -> String { + format!( + r#"{{"t":"Task","i":"","a":{{"Create":[{{"ident":"","type":"Task"}},{{"name":"{}","text":"{}","priority":"10","context":"default_context","deadline":"1970-01-01 00:00:00","show_after_date":"1970-01-01 00:00:00"}}]}}}}"#, + &name, &text, + ) +} +pub(crate) fn update_simple_action(ident: &str, name: &str, text: &str) -> String { + format!( + r#"{{"t":"Simple","i":"{}","a":{{"Update":[{{"ident":"{}","type":"Simple"}},{{"name":"{}","text":"{}"}}]}}}}"#, + &ident, &ident, &name, &text, + ) +} +pub(crate) fn update_task_action(ident: &str, name: &str, text: &str) -> String { + format!( + r#"{{"t":"Task","i":"{}","a":{{"Update":[{{"ident":"{}","type":"Task"}},{{"name":"{}","text":"{}","priority":"10","context":"default_context","deadline":"2021-01-01 00:00:00","show_after_date":"2020-01-01 00:00:00"}}]}}}}"#, + &ident, &ident, &name, &text, + ) +} +pub(crate) fn pull_push_and_shutdown(engine: &mut FanlingEngine) -> crate::shared::NullResult { + engine.execute(r#"{"a":"Pull"}"#)?; + let check_data = r#"{"a":"CheckData","i":"","t":""}"#; + let _resp = engine.execute(&check_data)?; + engine.execute(r#"{"a":{"Push":{"force":false}}}"#)?; + engine.execute(r#"{"a":"Shutdown","i":"","t":""}"#)?; + engine.handle_event(&fanling_interface::CycleEvent::StopPC)?; + Ok(()) +} +pub(crate) fn check_test_data( + engine: &mut super::FanlingEngine, + ident: &str, + flag: &str, + expect: &str, +) -> NullResult { + let show1 = format!(r#"{{"t":"","i":"{}","a":"Show"}}"#, ident); + let resp = engine.execute(&show1)?; + let act = resp.get_test_data(flag); + assert_eq!( + expect, act, + "item {}: {} should be {}", + &ident, &flag, &expect, + ); + Ok(()) +} +pub(crate) fn simple_options(test_dir: &str, database_path: &str) -> super::EngineOptions { + super::EngineOptions { + repo_options: taipo_git_control::RepoOptions { + path: PathBuf::from(test_dir).into_boxed_path(), + name: "tester".to_string(), + email: "tester@example.com".to_string(), + write_to_server: true, + ..taipo_git_control::RepoOptions::default() + }, + interface_type: super::InterfaceType::PC, + search_options: crate::search::SearchOptions { + database_path: database_path.to_string(), + }, + uniq_pfx: "a".to_string(), + auto_link: false, + } +} +pub(crate) fn init_files(dir: &str, subdir: &str) -> (String, String) { + trace(&"initialising files...".to_string()); + // const TEST_DIR1: &str = "testfiles2"; + let test_dir = format!("{}/{}", dir, subdir); + let database_path = format!("{}.db", test_dir); + let _ = fs::remove_dir_all(&test_dir); + let _ = fs::remove_file(&database_path); + let _ = fs::remove_dir_all(dir); + let _ = fs::create_dir_all(dir); + (test_dir, database_path) +} +pub(crate) fn test_engine( + test_dir_base: &str, + old_name: &str, + name: &str, + uniq_pfx: &str, +) -> crate::shared::FLResult { + trace(&format!( + "local repo: base: {} old name: {} name: {} prefix: {}", + test_dir_base, old_name, name, uniq_pfx + )); + let url = format!("{}/{}", test_dir_base, old_name); + let test_dir2 = format!("{}/{}", test_dir_base, name); + let database2_path = format!("{}.db", test_dir2); + let _ = fs::remove_dir_all(&test_dir2); + let _ = fs::remove_file(&database2_path); + + let options = super::EngineOptions { + repo_options: taipo_git_control::RepoOptions { + url: Some(url), + path: PathBuf::from(test_dir2).into_boxed_path(), + name: "tester".to_string(), + email: "tester@example.com".to_string(), + write_to_server: true, + ..taipo_git_control::RepoOptions::default() + }, + interface_type: super::InterfaceType::PC, + search_options: crate::search::SearchOptions { + database_path: database2_path, + }, + uniq_pfx: uniq_pfx.to_string(), + auto_link: false, + }; + + let engine = super::FanlingEngine::new(&options)?; + Ok(engine) +} +pub(crate) fn check_engine( + engine: &mut crate::FanlingEngine, + expected_text: &str, + field_key: &str, + test_ident: &str, +) -> crate::shared::NullResult { + trace(&format!("testing engine ({})", engine.trace_descr())); + // let ident = "aaa-o2"; + let (item_base, item_values) = engine.world.get_item_parts(&test_ident.to_owned())?; + assert_eq!(test_ident, item_base.ident); + match item_values.get(field_key) { + None => { + return Err(fanling_error!(&format!( + "no '{}' value for item", + field_key + ))) + } + Some(t) => assert_eq!( + expected_text, t, + "wrong field '{}' value in test result (for '{}')", + &field_key, &test_ident + ), + } + Ok(()) +} + +/** convenience function for test traces */ +pub(crate) fn trace(txt: &str) { + println!( + "engine test {} {} {}", + ansi_term::Colour::Red + .bold() + .blink() + .on(ansi_term::Colour::Black) + .paint(">".repeat(16)), + ansi_term::Colour::Black + .bold() + .on(ansi_term::Colour::White) + .paint(txt), + ansi_term::Colour::Red + .bold() + .blink() + .on(ansi_term::Colour::Black) + .paint("<".repeat(16)), + ); +} diff --git a/fanling-engine/src/world.rs b/fanling-engine/src/world.rs index 83c5412..deaa019 100644 --- a/fanling-engine/src/world.rs +++ b/fanling-engine/src/world.rs @@ -13,6 +13,7 @@ use crate::search::Search; use crate::shared::{FLResult, FanlingError, NullResult, Tracer}; use crate::store::Store; use askama::Template; +use fanling_interface::error_response_result; use log::trace; use std::cell::RefCell; use std::collections::HashMap; @@ -84,7 +85,7 @@ impl<'a> World { for entry in self.store.list_all_items()? { // trace(&format!("should load {:?}", entry)); let (base, values) = split_data_parts(entry.blob.as_bytes())?; - let ident = self.make_known(values, base)?; + let ident = self.make_known(&values, base)?; let path_from_ident = self.store.path_from_ident(&ident); if path_from_ident != entry.path { return Err(fanling_error!(&format!( @@ -231,21 +232,30 @@ impl<'a> World { // let mut vals = HashMap::new(); // vals.insert("name".to_owned(), "Default context".to_owned()); // self.default_context = Some(self.make_item(&type_name, &base, &vals)?); - self.default_context = Some(self.ensure_item("default_context".to_owned())?); + self.default_context = + Some(self.ensure_item("default_context".to_owned(), "Simple".to_string())?); trace("created context."); Ok(()) } - /** create an item (simple only) */ - fn ensure_item(&mut self, ident: Ident) -> FLResult { - let type_name = "Simple".to_owned(); + /** create an item */ + fn ensure_item(&mut self, ident: Ident, type_name: Ident) -> FLResult { + // let type_name = "Simple".to_owned(); let base = ItemBaseForSerde { ident: ident.to_owned(), type_name: type_name.clone(), - can_be_context: true, + can_be_context: type_name == "Simple", + can_be_parent: true, ..ItemBaseForSerde::default() }; let mut vals = HashMap::new(); vals.insert("name".to_owned(), ident); + match type_name.as_str() { + "Simple" => {} + "Task" => { + vals.insert("context".to_string(), "default_context".to_string()); + } + _ => return Err(fanling_error!(&format!("invalid type '{}'", &type_name))), + } let res = self.make_item(&type_name, &base, &vals)?; Ok(res) } @@ -285,7 +295,7 @@ impl<'a> World { let mut item = item_type.make_raw(); // let mut item: Item = ItemType::from(*item_type_rcrc.borrow()).make_raw(); item.set_data(vals, self)?; - let descr = item.description(); + let descr = item.descr_for_ident(); if descr.is_empty() { return Err(fanling_error!("description must not be blank")); } @@ -298,7 +308,6 @@ impl<'a> World { trace(&format!("item as created {:#?}", item)); let item_rcrc = Rc::new(RefCell::new(item)); - // TODO set values (which values?) self.store.add_item(&item_rcrc)?; self.search.add_item(&item_rcrc)?; trace("made item."); @@ -338,7 +347,7 @@ impl<'a> World { _json_value: serde_json::value::Value, ) -> fanling_interface::ResponseResult { let mut res = match basic_request.action.kind() { - crate::ActionKind::Engine => panic!("should not come here"), + crate::ActionKind::Engine => error_response_result("should not come here"), crate::ActionKind::World => self.do_world_action(basic_request), crate::ActionKind::Item => { let ident: Ident = basic_request @@ -346,26 +355,34 @@ impl<'a> World { .as_ref() .ok_or_else(|| fanling_error!("need ident here"))? .to_string(); - let item_rf = self.get_item(ident)?; + let item_rf = self.get_item(ident, "Simple".to_owned())?; let item: &mut Item = &mut item_rf.deref().borrow_mut(); let res = item.do_action(basic_request.action.clone(), self)?; + trace("item action done"); Ok(res) } }?; self.add_always(&mut res)?; + trace("action done"); Ok(res) } /** get an [`Item`] by [`Ident`] */ - pub fn get_item(&mut self, ident: Ident) -> FLResult { + pub fn get_item(&mut self, ident: Ident, type_name: Ident) -> FLResult { trace(&format!("getting item '{}'", ident)); match self.store.get_item_if_known(&ident) { Some(i) => Ok(i.clone()), None => { - if self.auto_link && !(self.store.has_file(&ident).expect("bad???")) { - self.ensure_item(ident.clone())?; + // if let Some(type_name) = type_option { + if self.auto_link + && !(self + .store + .has_file(&ident) + .expect("error when checking if page in store")) + { + self.ensure_item(ident.clone(), type_name)?; } let (base, serde_value) = self.store.get_item_parts(&ident)?; - let item_ref = self.get_and_make_known(serde_value, &base)?; + let item_ref = self.get_and_make_known(&serde_value, &base)?; Ok(item_ref) } } @@ -373,7 +390,7 @@ impl<'a> World { /** take the raw data for an item and ensure that the item is ready to use */ fn get_and_make_known( &mut self, - serde_value: serde_yaml::Value, + serde_value: &serde_yaml::Value, base: &ItemBaseForSerde, ) -> FLResult { fanling_trace!("getting and making known"); @@ -390,7 +407,7 @@ impl<'a> World { &mut self, item_type: &ItemType, base: &ItemBaseForSerde, - serde_value: serde_yaml::Value, + serde_value: &serde_yaml::Value, ) -> FLResult { fanling_trace!(&format!("making and populating item {}", &base.ident)); let mut item = item_type.make_raw(); @@ -406,6 +423,7 @@ impl<'a> World { ); trace(&format!("setting ident ({})...", base.ident)); item.set_ident(base.ident.clone()); + item.fix_data(serde_value, self)?; trace("item made and populated"); Ok(Rc::new(RefCell::new(item))) } @@ -429,7 +447,11 @@ impl<'a> World { // _json_value: serde_json::value::Value, ) -> fanling_interface::ResponseResult { match &basic_request.action { - crate::Action::Start => Ok(fanling_interface::Response::new()), + crate::Action::Start | crate::Action::ListReady => { + let mut open = self.search.search_open_hier()?; + let mut ready = open.filter_on_item(|i, world| i.is_ready(world), self)?; + Self::show_list(&mut ready, "ready") + } crate::Action::Create(base, vals) => { let type_name = basic_request.ensure_type_name()?; let item_type_rf = self.get_item_type(type_name.clone())?; @@ -443,28 +465,29 @@ impl<'a> World { } let item_ref = self.make_item(&type_name, &base, &vals)?; let res = item_ref.deref().borrow_mut().for_edit(true, self); + fanling_trace!("action done"); res } crate::Action::Update(base, vals) => { let res = self.update_item_action(basic_request, &base, vals)?; + fanling_trace!("action done"); Ok(res) } crate::Action::Delete => self.delete_item_action(basic_request), crate::Action::GetAll => self.get_all(), crate::Action::CheckData => self.check_data(), - crate::Action::ListReady => { - let mut ready = self.search.search_ready_hier()?; - ready.set_level_changes(); - trace(&format!("ready {:?}", &ready)); - let lt = ListTemplate { items: ready }; - let mut resp = fanling_interface::Response::new(); - resp.add_tag("content", &(lt.render()?)); - // trace(&format!("ready list {:?}", &resp)); - Ok(resp) + crate::Action::ListOpen => { + let mut open = self.search.search_open_hier()?; + Self::show_list(&mut open, "open") + } + crate::Action::ListAll => { + let mut all = self.search.search_all_hier()?; + Self::show_list(&mut all, "all") } crate::Action::Pull => { trace("doing pull action"); self.pull()?; + fanling_trace!("action done"); Ok(fanling_interface::Response::new()) } crate::Action::Push { force } => { @@ -474,6 +497,7 @@ impl<'a> World { "after push, needs push: {:?}", self.store.get_needs_push() )); + fanling_trace!("action done"); Ok(fanling_interface::Response::new()) } crate::Action::New => { @@ -481,6 +505,7 @@ impl<'a> World { trace(&format!("new for item type {}", item_type_name)); let item_type = self.get_item_type(item_type_name)?; let mut item = item_type.deref().borrow().make_raw(); + fanling_trace!("action done"); item.for_edit(false, self) } crate::Action::NewChild(parent_ident) => { @@ -493,15 +518,39 @@ impl<'a> World { }; // self.set_parent_from_ident(&mut item, &base)?; item.set_from_serde(&base)?; + fanling_trace!("action done"); item.for_edit(false, self) } crate::Action::Clone => { let res = self.clone_item(basic_request); + fanling_trace!("action done"); res } - _ => panic!("invalid action {:?}", basic_request.action), + _ => error_response_result(&format!("invalid action {:?}", basic_request.action)), } } + /** show a list of items */ + fn show_list(list: &mut ItemListEntryList, narr: &str) -> fanling_interface::ResponseResult { + list.set_level_changes(); + trace(&format!( + "{}: {} entries {:?}", + narr, + list.entries.len(), + &list + )); + #[cfg(test)] + let entries_count = list.num_entries(); + let lt = ListTemplate { + items: list.clone(), + }; + let mut resp = fanling_interface::Response::new(); + resp.add_tag("content", &(lt.render()?)); + #[cfg(test)] + resp.set_test_data("count", &format!("{}", entries_count)); + // trace(&format!("list list {:?}", &resp)); + fanling_trace!("showing list"); + Ok(resp) + } /** update an item */ fn update_item_action( &mut self, @@ -515,7 +564,7 @@ impl<'a> World { let action_result = self.check_item_valid(item_type_rf, base, vals)?; trace(&format!("action result is {:#?}", action_result)); if action_result.ok() { - let item_rf = self.get_item(ident)?; + let item_rf = self.get_item(ident, "Simple".to_owned())?; let mut item = item_rf.deref().borrow_mut(); trace(&format!("values for base update: {:#?}", base)); item.set_from_serde(base)?; @@ -534,7 +583,7 @@ impl<'a> World { basic_request: &crate::BasicRequest, ) -> fanling_interface::ResponseResult { let existing_ident: Ident = basic_request.ensure_ident()?; - let existing_item_rf = self.get_item(existing_ident)?; + let existing_item_rf = self.get_item(existing_ident, "Simple".to_owned())?; let existing_item = existing_item_rf.deref().borrow_mut(); let item_type_rf = existing_item.item_type(); let item_type = item_type_rf.deref().borrow(); @@ -542,7 +591,7 @@ impl<'a> World { item.clone_from(&existing_item)?; item.set_ident( self.store - .make_identifier(&self.uniq_pfx, &item.description()), //?? + .make_identifier(&self.uniq_pfx, &item.descr_for_ident()), ); assert!(item.ident() != "", "ident is null"); self.search @@ -569,7 +618,7 @@ impl<'a> World { let type_name = basic_request.ensure_type_name()?; let _item_type_rf = self.get_item_type(type_name)?; // TODO check whether item can be deleted - let item_rf = self.get_item(ident)?; + let item_rf = self.get_item(ident, "Simple".to_owned())?; self.search.delete_item(item_rf.clone())?; self.store.mark_item_deleted(item_rf)?; Ok(fanling_interface::Response::new()) @@ -616,7 +665,7 @@ impl<'a> World { // let item_ref = self.get_and_make_known(serde_value, &base)?; // self.search.add_item(&item_ref)?; // assert_eq!(ident_from_path, item_ref.deref().borrow().ident()); - let ident = self.make_known(serde_value, base)?; + let ident = self.make_known(&serde_value, base)?; assert_eq!(ident_from_path, ident); } } @@ -629,7 +678,7 @@ impl<'a> World { /** add the item to the search engine */ fn make_known( &mut self, - serde_value: serde_yaml::Value, + serde_value: &serde_yaml::Value, base: ItemBaseForSerde, ) -> FLResult { let item_ref = self.get_and_make_known(serde_value, &base)?; @@ -649,9 +698,9 @@ impl<'a> World { pub fn search_contexts(&self) -> FLResult { Ok(self.search.search_special(SpecialKind::Context)?) } - /** search everything for ready with hierarchy */ - pub fn search_ready_hier(&self) -> FLResult { - self.search.search_ready_hier() + /** search everything for open with hierarchy */ + pub fn search_open_hier(&self) -> FLResult { + self.search.search_open_hier() } /** cross-check search and store */ fn check_data(&self) -> fanling_interface::ResponseResult { @@ -679,8 +728,13 @@ impl<'a> World { None => trace(&format!("bad path {}", path)), Some(ident) => { if !searched.contains_key(&ident) { - trace(&format!("{} in store repo but not search database", &ident)); + let msg = format!("{} in store repo but not search database", &ident); + trace(&msg); missing_from_search_count += 1; + // #[cfg(test)] + // { + // panic!(msg); + // } } searched.insert(ident.clone(), true); } @@ -690,11 +744,13 @@ impl<'a> World { let mut missing_from_store_count = 0; for (ident, found) in searched.iter() { if !found { - trace(&format!( - "{} not in store repo but in search database", - &ident, - )); + let msg = format!("{} not in store repo but in search database", &ident,); + trace(&msg); missing_from_store_count += 1; + // #[cfg(test)] + // { + // panic!(msg); + // } } } trace(&format!( @@ -719,9 +775,9 @@ impl<'a> World { pub fn push(&mut self, force: bool) -> NullResult { self.store.push(force) } - /** find all the children of this item that have ready status */ - pub fn search_ready_children(&self, ident: &str) -> FLResult { - self.search.search_ready_children(ident) + /** find all the children of this item that have open status */ + pub fn search_open_children(&self, ident: &str) -> FLResult { + self.search.search_open_children(ident) } /** get an item if it is already known. (This can be used to check whether an item is known). */ pub fn get_item_if_known(&self, ident: &Ident) -> Option<&ItemRef> { @@ -764,19 +820,26 @@ pub enum ActionResponse { specifics: Vec<(String, String)>, }, Success { + #[cfg(test)] /** identifier if created by the action*/ - ident: Option, + test_data: HashMap, }, } impl ActionResponse { /** create a new `ActionResponse` */ pub fn new() -> Self { - Self::Success { ident: None } + Self::Success { + #[cfg(test)] + test_data: HashMap::new(), + } } /** record that a user error has been found */ pub fn add_error(&mut self, area: &str, m: &str) { match self { - Self::Success { ident: _ } => { + Self::Success { + #[cfg(test)] + test_data: _, + } => { let mut ss = Vec::new(); ss.push((area.to_owned(), m.to_owned())); *self = Self::Failure { @@ -801,12 +864,19 @@ impl ActionResponse { } /** whether there has been no errors */ pub fn ok(&self) -> bool { - *self == Self::Success { ident: None } + *self + == Self::Success { + #[cfg(test)] + test_data: HashMap::new(), + } } /** user errors */ pub fn errors(&self) -> Vec<(String, String)> { match self { - Self::Success { ident: _ } => vec![], + Self::Success { + #[cfg(test)] + test_data: _, + } => vec![], Self::Failure { messages: _, specifics, @@ -816,7 +886,10 @@ impl ActionResponse { /** overall user error message */ pub fn overall_message(&self) -> String { match self { - Self::Success { ident: _ } => "".to_owned(), + Self::Success { + #[cfg(test)] + test_data: _, + } => "".to_owned(), Self::Failure { messages, specifics: _, @@ -829,16 +902,18 @@ impl ActionResponse { for (t, v) in self.errors() { response.add_tag(&t, &v); } - if let Some(id) = self.ident() { - response.set_ident(id); - } + // if let Some(td) = self. { + #[cfg(test)] + response.set_all_test_data(self.get_test_data()); + // } Ok(response) } - /** associate an [`Ident`] */ - pub fn set_ident(&mut self, new_ident: &Ident) { + #[cfg(test)] + /** associate test data */ + pub fn set_test_data(&mut self, test_data: HashMap) { match self { - Self::Success { ident: id } => { - *id = Some(new_ident.to_owned()); + Self::Success { test_data: td } => { + *td = test_data; } Self::Failure { messages: _, @@ -846,16 +921,28 @@ impl ActionResponse { } => {} } } + #[cfg(test)] /** retrieve the [`Ident`] */ - pub fn ident(&self) -> Option { + pub fn ident(&self) -> Option { match self { - Self::Success { ident } => ident.clone(), + Self::Success { test_data: td } => Some(td.get("ident").unwrap().clone()), Self::Failure { messages: _, specifics: _, } => None, } } + #[cfg(test)] + /** retrieve the test data */ + pub fn get_test_data(&self) -> HashMap { + match self { + Self::Success { test_data: td } => td.clone(), + Self::Failure { + messages: _, + specifics: _, + } => HashMap::new(), + } + } } /* template for initial HTML */ diff --git a/fanling-engine/templates/always.html b/fanling-engine/templates/always.html index c44657a..f4cc0c8 100644 --- a/fanling-engine/templates/always.html +++ b/fanling-engine/templates/always.html @@ -1,8 +1,20 @@ -{# needs to be pushed to id=always whenever contents should change #} -{% if needs_push -%} - - - +{# needs to be pushed to id=always whenever contents should change #} {% if +needs_push -%} + + + {% else -%} - + {% endif -%} diff --git a/fanling-engine/templates/code.js b/fanling-engine/templates/code.js index 89b37a2..323320c 100644 --- a/fanling-engine/templates/code.js +++ b/fanling-engine/templates/code.js @@ -14,6 +14,11 @@ var doAction = function(aVal, tVal, iVal) { }; invoke(c); }; +var doActionWithIdent = function(aVal, tVal, iVal, iVal2) { + var a = {}; + a[aVal] = iVal2; + doAction(a, tVal, iVal); +}; {% match interface_type %} {% when crate::InterfaceType::PC %} // code for PC platform @@ -26,7 +31,9 @@ var invoke = function(arg) { var invoke = function(arg) { console.log("\ninvoking from Android with " + JSON.stringify(arg)); taipo.execute(JSON.stringify(arg)); + console.log("execute done"); if (taipo.response_ok()) { + console.log("response ok from execute"); var nri = taipo.response_num_items(); for (i = 0 ; i < nri; i++) { let ri = taipo.response_item(i); @@ -36,6 +43,7 @@ var invoke = function(arg) { setTag(key, value); } } else { + console.log("system error found: " + taipo.response_error()); alert( taipo.response_error()); console.log("error: " + taipo.response_error()); } diff --git a/fanling-engine/templates/fanling.css b/fanling-engine/templates/fanling.css index fa52f9d..dc3807a 100644 --- a/fanling-engine/templates/fanling.css +++ b/fanling-engine/templates/fanling.css @@ -4,67 +4,77 @@ file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* colours from http://paletton.com/#uid=10b0u0kmRmRn24bn7d0lqvjkAR7 * */ -html, body { - background-color: #682E1D; - color: #ffffff; - font-family: sans-serif; - font-size: 1em; - min-height: 100vh; -} - h1, h2, h3, h4, h5 { - color: #ff805b; -} - a { - color: #ff805b; - text-decoration: underline; -} - a.big { - padding: 1em; - margin: 1em; -} - th { - text-align: left; -} - td, p, li { - color: #F97853; - font-size: 1em; -} - tr.alert { - color: #210f09; - background-color: #ff805b; - font-weight: bold; -} - tr.alert a { - color: #210f09; -} - textarea { - height: 100%; - width: 95%; - color: #210f09; - font-family: sans-serif; - font-size: 1em; -} - input, select { - color: #210f09; - font-family: sans-serif; - font-size: 1em; -} - li.block { - color: #ffffff; -} - div.error { - font-weight: bold; - font-size: 200%; -} - @media handheld { - html, body { - margin: 0.2em; - padding: 0.2em; - float: none; - } - } - /* Remove default bullets */ -ul, #myUL { +html, +body { + background-color: #682e1d; + color: #ffffff; + font-family: sans-serif; + font-size: 1em; + min-height: 100vh; +} +h1, +h2, +h3, +h4, +h5 { + color: #ff805b; +} +a { + color: #ff805b; + text-decoration: underline; +} +a.big { + padding: 1em; + margin: 1em; +} +th { + text-align: left; +} +td, +p, +li { + color: #f97853; + font-size: 1em; +} +tr.alert { + color: #210f09; + background-color: #ff805b; + font-weight: bold; +} +tr.alert a { + color: #210f09; +} +textarea { + height: 100%; + width: 95%; + color: #210f09; + font-family: sans-serif; + font-size: 1em; +} +input, +select { + color: #210f09; + font-family: sans-serif; + font-size: 1em; +} +li.block { + color: #ffffff; +} +div.error { + font-weight: bold; + font-size: 200%; +} +@media handheld { + html, + body { + margin: 0.2em; + padding: 0.2em; + float: none; + } +} +/* Remove default bullets */ +ul, +#myUL { list-style-type: none; } @@ -104,10 +114,10 @@ ul, #myUL { } span.itemlink { - text-decoration: underline; - color: #ff805b; + text-decoration: underline; + color: #ff805b; } span.itemlink:hover { - cursor: pointer; - color: #F97853; + cursor: pointer; + color: #f97853; } diff --git a/fanling-engine/templates/main.html b/fanling-engine/templates/main.html index 7f5e6ad..2373415 100644 --- a/fanling-engine/templates/main.html +++ b/fanling-engine/templates/main.html @@ -1,31 +1,54 @@ - - + + + - + - - + -

- - - -
Welcome to Fanling
-
-
+
+ + + + + +
Welcome to Fanling
+
+

Debugging

- - + + - diff --git a/fanling-engine/templates/show-simple.html b/fanling-engine/templates/show-simple.html index 729b7e6..f910f84 100644 --- a/fanling-engine/templates/show-simple.html +++ b/fanling-engine/templates/show-simple.html @@ -1,49 +1,82 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - {% if base.has_children %} - - {%- for child in base.children.entries %} - - - - {% endfor -%} - {% endif %} +
{{name|escape}}
Parent:{{- base.parent.descr -}}
Can be parent:{% if base.can_be_parent %} yes {% else %} no {% endif %}
Can be context: - {% if base.can_be_context %} yes {% else %} no {% endif %}
Sort:{{base.sort|escape}}
{{rendered_text|safe}}

Children

{{- child.descr -}}
+ + + + + + + + + + + + + + + + + + + + + + + + + + {% if base.has_children %} + + + + + {%- for child in base.children.entries %} + + + + {% endfor -%} {% endif %}
{{name|escape}}
Parent: + {{- base.parent.descr -}} +
Can be parent:{% if base.can_be_parent %} yes {% else %} no {% endif %}
Can be context: + {% if base.can_be_context %} yes {% else %} no {% endif %} +
Sort:{{base.sort|escape}}
{{rendered_text|safe}}

Children

+ {{- child.descr -}} +
- - + + - - + + diff --git a/fanling-engine/templates/show-task.html b/fanling-engine/templates/show-task.html index fb5b95a..954c7d1 100644 --- a/fanling-engine/templates/show-task.html +++ b/fanling-engine/templates/show-task.html @@ -1,96 +1,156 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {% if base.has_children %} - - {%- for child in base.children.entries %} - - - - {% endfor -%} - {% endif %} - {% if blockedby.has_entries() %} - - -
{{name|escape}}
Parent:{{- base.parent.descr -}}
Context:{{- context.descr -}}
Can be parent:{% if base.can_be_parent %} yes {% else %} no {% endif %}
Can be context:{% if can_be_context %} yes {% else %} no {% endif %}
Sort:{{base.sort|escape}}
Priority:{{priority|escape}}
Status:{{status|escape}} - {% match status %} - {% when TaskStatus::Open %} - {% when TaskStatus::Closed %} {{ when_closed }} - {% else %} (other: {{status}}) - {% endmatch %} -
Deadline:{{deadline}}
Show after:{{ show_after_date }}
{{rendered_text|safe}}

Children

{{- child.descr -}}
Blocked by:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% if base.has_children %} + + + + + {%- for child in base.children.entries %} + + + + {% endfor -%} {% endif %} {% if blockedby.has_entries() %} + + + - - {% endif %} - - + + {% endif %} + + - + + + +
    {{name|escape}}
    Parent: + {{- base.parent.descr -}} +
    Context: + {{- context.descr -}} +
    Can be parent:{% if base.can_be_parent %} yes {% else %} no {% endif %}
    Can be context:{% if can_be_context %} yes {% else %} no {% endif %}
    Sort:{{base.sort|escape}}
    Priority:{{priority|escape}}
    Status: + {{status|escape}} {% match status %} {% when TaskStatus::Open %} + + {% when TaskStatus::Closed %} {{ when_closed }} + + {% else %} (other: {{status}}) {% endmatch %} +
    Deadline:{{deadline}}
    Show after:{{ show_after_date }}
    {{rendered_text|safe}}

    Children

    + {{- child.descr -}} +
    Blocked by: +
      {% for t in blockedby.entries %} -
    • {{t.descr|escape}}
    • +
    • + {{t.descr|escape}} + +
    • {% endfor %} -
      +
    +
    + + {% endfor %} -
    - - + + - - + + diff --git a/fanling-interface/Cargo.toml b/fanling-interface/Cargo.toml index 691e8fe..0fb45e5 100644 --- a/fanling-interface/Cargo.toml +++ b/fanling-interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fanling-interface" -version = "0.1.1" +version = "0.1.2" authors = ["martin at starnova "] edition = "2018" # build = "build.rs" @@ -8,7 +8,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -libc = "0.2.67" +libc = "0.2.68" ansi_term = "0.12.1" # [build-dependencies] diff --git a/fanling-interface/src/lib.rs b/fanling-interface/src/lib.rs index 571b2f0..1954dc5 100644 --- a/fanling-interface/src/lib.rs +++ b/fanling-interface/src/lib.rs @@ -18,7 +18,7 @@ Currently, this interface is used by: * an Android implementation using the `fanling_c*interface` crate and the `Lowu` Android app. */ - +use std::collections::HashMap; use std::fmt; /** trait for an interface between a main program and an engine */ @@ -52,6 +52,11 @@ pub fn default_response_result() -> ResponseResult { trace("getting default response result"); Ok(Response::default()) } +/// +pub fn error_response_result(msg: &str) -> ResponseResult { + trace("getting error response result"); + Ok(Response::new_error_with_tags(&vec![("error", msg)])) +} /** the response from the Engine to the user interface resulting from a command or event */ #[derive(Default, Clone, Debug)] pub struct Response { @@ -63,8 +68,9 @@ pub struct Response { shutdown_required: bool, /** whether the response includes an error */ error: bool, - /** assocated ident if any */ - ident: Option, + /** assocated test data if any */ + // #[cfg(test)] + test_data: HashMap, } impl Response { /** create a response */ @@ -74,7 +80,8 @@ impl Response { // to_clear: vec![], shutdown_required: false, error: false, - ident: None, + // #[cfg(test)] + test_data: HashMap::new(), } } /** tell the user interface to clear any errors from the user's display*/ @@ -100,12 +107,19 @@ impl Response { self.add_tag(ss.0, ss.1) } } - /** create a response with several tag/value pairs */ + /** create a response with several tag/value pairs */ pub fn new_with_tags(tags: &[(&str, &str)]) -> Self { let mut resp = Self::new(); resp.add_tags(tags); resp } + /** create a response with several tag/value pairs and an error */ + pub fn new_error_with_tags(tags: &[(&str, &str)]) -> Self { + let mut resp = Self::new(); + resp.add_tags(tags); + resp.set_error(); + resp + } // /** get the tags to clear from the response */ // pub fn get_to_clear(&self) -> impl Iterator { // self.to_clear.iter() @@ -128,7 +142,7 @@ impl Response { } /** the user interface should shut down */ pub fn set_shutdown_required(&mut self) { - self.shutdown_required = true + self.shutdown_required = true; } /** whether the response includes an error */ pub fn is_error(&self) -> bool { @@ -136,15 +150,43 @@ impl Response { } /** set that the response includes an error */ pub fn set_error(&mut self) { - self.error = true - } - /** get associated ident if any */ - pub fn get_ident(&self) -> Option { - self.ident.clone() - } - /** set the ident */ - pub fn set_ident(&mut self, ident: String) { - self.ident = Some(ident.to_string()) + self.error = true; + } + /** get associated test data if any */ + pub fn get_test_data(&self, tag: &str) -> String { + // #[cfg(test)] + // { + self.test_data + .get(tag) + .expect(&format!("no tag: {}", tag)) + .to_string() + // } + // #[cfg(not(test))] + // { + // panic!("bad test data"); + // } + } + /** set all the test data */ + pub fn set_test_data(&mut self, key: &str, val: &str) { + // #[cfg(test)] + // { + self.test_data.insert(key.to_string(), val.to_string()); + // } + // #[cfg(not(test))] + // { + // panic!("bad test data"); + // } + } + /** set all the test data */ + pub fn set_all_test_data(&mut self, test_data: HashMap) { + // #[cfg(test)] + // { + self.test_data = test_data; + // } + // #[cfg(not(test))] + // { + // panic!("bad test data"); + // } } } #[derive(Debug)] diff --git a/fanling10/Cargo.toml b/fanling10/Cargo.toml index 8d22f04..04b93ea 100644 --- a/fanling10/Cargo.toml +++ b/fanling10/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fanling10" -version = "0.1.1" +version = "0.1.2" authors = ["martin "] edition = "2018" @@ -14,11 +14,11 @@ fanling-engine = { path = "../fanling-engine" } fanling-interface = { path = "../fanling-interface" } log = "0.4.8" quick-error = "1.2.3" -rust-embed = "5.5.0" -serde = "1.0.104" -serde_derive = "1.0.104" -serde_json = "1.0.48" -structopt = "0.3.11" +rust-embed = "5.5.1" +serde = "1.0.106" +serde_derive = "1.0.106" +serde_json = "1.0.50" +structopt = "0.3.12" taipo-git-control = { path = "../taipo-git-control" } [target.'cfg(not(target_os = "android"))'.dependencies] diff --git a/migrations/2019-10-08-070733_create_items/up.sql b/migrations/2019-10-08-070733_create_items/up.sql index 5a4a1b7..43d425c 100644 --- a/migrations/2019-10-08-070733_create_items/up.sql +++ b/migrations/2019-10-08-070733_create_items/up.sql @@ -3,7 +3,6 @@ CREATE TABLE item ( type_name VARCHAR NOT NULL, name VARCHAR NOT NULL, open BOOLEAN NOT NULL DEFAULT(1), - ready BOOLEAN NOT NULL DEFAULT(1), parent VARCHAR DEFAULT NULL, sort VARCHAR NOT NULL, classify VARCHAR NOT NULL, @@ -11,7 +10,6 @@ CREATE TABLE item ( targeted BOOLEAN NOT NULL DEFAULT(0) ); CREATE UNIQUE INDEX item_open ON item (open, sort, ident) WHERE open; -CREATE UNIQUE INDEX item_ready ON item (ready, special, sort, ident) WHERE ready; CREATE INDEX item_classify ON item (classify, open, name) WHERE classify != 'normal'; CREATE INDEX item_special ON item (special, open, name) WHERE special != 0; CREATE INDEX item_child ON item (parent, open); @@ -50,7 +48,6 @@ CREATE TABLE item_by_level_copy2( type_name VARCHAR NOT NULL, name VARCHAR NOT NULL, open BOOLEAN NOT NULL, - ready BOOLEAN NOT NULL, parent VARCHAR, sort VARCHAR NOT NULL, classify VARCHAR NOT NULL, diff --git a/scripts/start.sh b/scripts/start.sh index 387b3f2..0ed36e9 100755 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -37,18 +37,22 @@ else # cargo upgrade --aggressive fi echo "restoring old cargo" - git checkout $BASE/fanling-engine/Cargo.toml $BASE/taipo-git-control/Cargo.toml + git checkout $BASE/fanling-engine/Cargo.toml export PATH=$PATH:$BASE/scripts:$BASE/target/debug - $BASE/scripts/show-doco.sh & + diesel database reset + $BASE/scripts/db-rebuild.sh if [[ "$NETSPEED" != "fast" && $START_TYPE != "all" ]] ; then echo "pulling new cargo files..." cargo fetch fi - $BASE/scripts/db-rebuild.sh + # $BASE/scripts/db-rebuild.sh qgit& - scripts/edit.sh scripts/copy-ssh.sh& + cargo fix --allow-dirty cargo fmt + cargo +nightly fix -Z unstable-options --clippy --allow-dirty + scripts/edit.sh + $BASE/scripts/show-doco.sh & fi diff --git a/taipo-git-control/Cargo.toml b/taipo-git-control/Cargo.toml index d6163a6..d0e591c 100644 --- a/taipo-git-control/Cargo.toml +++ b/taipo-git-control/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "taipo-git-control" -version = "0.1.1" +version = "0.1.2" authors = ["martin "] edition = "2018" @@ -11,8 +11,8 @@ ansi_term = "0.12.1" chrono = "0.4.11" color-backtrace = "0.3.0" dirs = "2.0.2" -git2 = "=0.11.0" -git2_credentials = "0.5.0" +git2 = "0.13.1" +git2_credentials = "0.6.0" hex-slice = "0.1.4" openssl = { version = "0.10.28", features = ["vendored"] } quick-error = "1.2.3" diff --git a/taipo-git-control/src/error.rs b/taipo-git-control/src/error.rs index 5c6d62c..6be044c 100644 --- a/taipo-git-control/src/error.rs +++ b/taipo-git-control/src/error.rs @@ -64,12 +64,14 @@ macro_rules! dump_error { match $err { Ok(x) => x, Err(e) => { + // trace(&format!("error found at {} ({})", file!(), line!())); let re = RepoError::from(e); re.dump(file!(), line!(), column!()); if !cfg!(android) { panic!("git error"); } - return Err(repo_error!("should not come here")); + //return Err(repo_error!("should not come here")); + return Err(re); } } }; diff --git a/taipo-git-control/src/lib.rs b/taipo-git-control/src/lib.rs index af02975..b0fae58 100644 --- a/taipo-git-control/src/lib.rs +++ b/taipo-git-control/src/lib.rs @@ -16,7 +16,7 @@ can result in a merge conflict. It is the user's responsibility to resolve this before any further use of the system, by applying the necesary changes. -* TODO example tests; cover all main cases including merge conflicts +* FUTURE example tests; cover all main cases including merge conflicts Some code copied from [here](https://zsiciarz.github.io/24daysofrust/book/vol2/day16.html) diff --git a/taipo-git-control/src/repo.rs b/taipo-git-control/src/repo.rs index fbf8770..5881b4a 100644 --- a/taipo-git-control/src/repo.rs +++ b/taipo-git-control/src/repo.rs @@ -123,7 +123,7 @@ impl FanlingRepository { .clone() .ok_or_else(|| repo_error!("URL must be specified for clone"))?; trace(&format!( - "actually cloning ({:?}) to {:?}...", + "actually cloning (url {:?}) to {:?}...", url, &opts.path )); let r = dump_error!(builder.clone(&url, &opts.path.clone())); @@ -556,7 +556,7 @@ impl FanlingRepository { trace("commits are the same, not merging"); return Ok(MergeOutcome::AlreadyUpToDate); } - // TODO: maybe use merge analysis + // FUTURE: maybe use merge analysis trace("commits different, so merging and finding conflicts..."); let index = dump_error!(self.repo.merge_commits( &our_commit, @@ -627,7 +627,7 @@ impl FanlingRepository { "merge outcome had conflict, applying {} changes to index", changes.len() )); - self.repo.set_index(index); + self.repo.set_index(index)?; self.apply_changelist_to_index(changes, index) } else { Err(repo_error!("should not come here: should be conflict")) @@ -863,8 +863,8 @@ impl FanlingRepository { } /** Give a path, retrieve the blob at that location. */ pub fn blob_from_path(&self, path: &str) -> Result, RepoError> { - // TODO: cache the following and update each commit - trace(&format!("getting blob ({:?})...", path)); + // FUTURE: cache the following and update each commit + repo_trace!(&format!("getting blob (path {:?})...", path)); let commit = dump_error!(self.find_last_commit()).ok_or_else(|| (repo_error!("no commit")))?; let tree = dump_error!(commit.tree()); @@ -874,13 +874,15 @@ impl FanlingRepository { .try_get_subtree(tree)? .ok_or_else(|| repo_error!("no subtree"))?; Self::describe_tree(&subtree, "commit_merge:entries in subtree"); + trace("getting entry from repo..."); let entry = dump_error!(subtree.get_path(&Path::new(path))); let id = dump_error!(self.repo.find_blob(entry.id())); let content = id.content(); + let content_length = content.len(); let mut blob: Vec = vec![]; - blob.resize(content.len(), 0); + blob.resize(content_length, 0); blob.clone_from_slice(content); - trace("got blob."); + trace(&format!("got blob, length {}.", content_length)); Ok(blob) } /** do the changes */ @@ -1099,7 +1101,9 @@ impl Drop for FanlingRepository { if thread::panicking() { trace("already panicking, so no more checks") } else { - //TODO:replace assert!(!self.needs_push, "repo needs push but dropping"); + if self.needs_push { + trace("repo needs push but dropping|"); + } } } }