Main thread doesn't wait background thread to finish in Swift - multithreading

Here's my problem : I'm doing a background work, where I parse some JSON and write some Objects into my Realm, and in the main thread I try to update the UI (reloading the TableView, it's linked to an array of Object). But when I reload the UI, my tableView doesn't update, like my Realm wasn't updated. I have the reload my View to see the updates. Here's my code :
if (Realm().objects(Objects).filter("...").count > 0)
{
var results = Realm().objects(Objects) // I get the existing objects but it's empty
tableView.reloadData()
}
request(.GET, url).responseJSON() {
(request, response, data, error) in
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// Parsing my JSON
Realm().write {
Realm().add(object)
}
dispatch_sync(dispatch_get_main_queue()) {
// Updating the UI
if (Realm().objects(Objects).filter("...").count > 0)
{
results = Realm().objects(Objects) // I get the existing objects but it's empty
tableView.reloadData()
}
}
}
}
I have to do something bad with my threads, but I couldn't find what. Can someone know what's wrong?
Thank you for your answer!

such workflow makes more sense to me for your case:
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// Parsing my JSON
Realm().write {
Realm().add(object)
dispatch_sync(dispatch_get_main_queue()) {
// Updating the UI
if (Realm().objects(Objects).filter("...").count > 0)
{
results = Realm().objects(Objects) // I get the existing objects but it's empty
tableView.reloadData()
}
}
}
}
NOTE: you have a problem with timing in your original workflow: the UI might be updated before the write's block executed, that is why your UI looks abandoned; this idea above would be a more synchronised way between tasks, according their performance's schedule.

You are getting some new objects and storing them into "results".
How is tableView.reloadData () supposed to access that variable? You must change something that your tableView delegate will access.
PS. Every dispatch_sync () is a potential deadlock. You are using one that is absolutely pointless. Avoid dispatch_sync unless you have a very, very good reason to use it.

Related

How to prevent global event handlers from firing caused by an API call

I have a custom module that uses Kentico API (DocumentHelper) to update certain fields of my document and then publish but I do not want it to trigger the event handlers that are linked to my document page type. I tried adding comments to .Publish("admin_edit") hoping that I can catch it from the WorkflowEventargs parameter but the VersionComment property always return null. Is there a way to accomplish this in Kentico?
update field:
var document = DocumentHelper.GetDocument(documentID, tree);
var workflowManager = WorkflowManager.GetInstance(tree);
var workflow = workflowManager.GetNodeWorkflow(document);
if (workflow != null)
{
document.CheckOut();
document.SetValue("SomeFIeld", "some value");
document.Update(true);
document.CheckIn();
document.Publish("admin_edit");
}
event handler:
public override void Init()
{
WorkflowEvents.Publish.After += Publish_After;
}
private void Publish_After(object sender, WorkflowEventArgs e)
{
if (!string.IsNullOrEmpty(e.VersionComment) &&
e.VersionComment.Contains("admin_edit"))
return;
}
You always get null for Version information, because that is related to the 'Page versioning' events, specially for 'SaveVersion'. You can find more about that on this link. If you expand 'Properties' you will see which properties are populated for the specific event. In your case, you can try something like this, to add your message for last version and then check for that comment on 'Publish_After' event, see code bellow:
var document = DocumentHelper.GetDocument(documentID, tree);
var workflowManager = WorkflowManager.GetInstance(tree);
var workflow = workflowManager.GetNodeWorkflow(document);
if (workflow != null)
{
document.CheckOut();
document.SetValue("SomeFIeld", "some value");
document.Update(true);
document.CheckIn(versionComment: "admin_edit");
document.Publish();
}
and then, in event handler, take last version and check for comment like this:
if (e.PublishedDocument?.VersionHistory?.Count > 0)
{
var lastVersion = e.PublishedDocument.VersionHistory[0] as VersionHistoryInfo;
if (lastVersion.VersionComment.Equals("admin_edit"))
{
return;
}
}
NOTE: In case that you have a lot of concurrent content editors, there is a chance that your last version is not version from API (someone changed content and saved it right after your API call made change). There is a low chance for that, but still is possible. If this is something that you will use often, you must take it in consideration. This code is tested for Kentico 11.

Processing an emaillist async in MVC4

I'm trying to make my MVC4-website check to see if people should be alerted with an email because they haven't done something.
I'm having a hard time figuring out how to approach this. I checked if the shared hosting platform would allow me to activate some sort of cronjob, but this is not available.
So now my idea is to perform this check on each page-request, which already seems suboptimal (because of the overhead). But I thought that with using an async it would not be in the way of people just visiting the site.
I first tried to do this in the Application_BeginRequest method in Global.asax, but then it gets called multiple times per page-request, so that didn't work.
Next I found that I can make a Global Filter which executes on OnResultExecuted, which would seemed promising, but still it's no go.
The problem I get there is that I'm using MVCMailer to send the mails, and when I execute it I get the error: {"Value cannot be null.\r\nParameter name: httpContext"}
This probably means that mailer needs the context.
The code I now have in my global filter is the following:
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
HandleEmptyProfileAlerts();
}
private void HandleEmptyProfileAlerts()
{
new Thread(() =>
{
bool active = false;
new UserMailer().AlertFirst("bla#bla.com").Send();
DB db = new DB();
DateTime CutoffDate = DateTime.Now.AddDays(-5);
var ProfilesToAlert = db.UserProfiles.Where(x => x.CreatedOn < CutoffDate && !x.ProfileActive && x.AlertsSent.Where(y => y.AlertType == "First").Count() == 0).ToList();
foreach (UserProfile up in ProfilesToAlert)
{
if (active)
{
new UserMailer().AlertFirst(up.UserName).Send();
up.AlertsSent.Add(new UserAlert { AlertType = "First", DateSent = DateTime.Now, UserProfileID = up.UserId });
}
else
System.Diagnostics.Debug.WriteLine(up.UserName);
}
db.SaveChanges();
}).Start();
}
So my question is, am I going about this the right way, and if so, how can I make sure that MVCMailer gets the right context?
The usual way to do this kind of thing is to have a single background thread that periodically does the checks you're interested in.
You would start the thread from Application_Start(). It's common to use a database to queue and store work items, although it can also be done in memory if it's better for your app.

How do I track whether a context menu item is already created by my Chrome Extension?

There are only four methods for chrome.contextMenus:
create
update
remove
removeAll
I am wondering how do I check whether one menu is already created?
I tried this:
try {
chrome.contextMenus.update("byname", {});
} catch (e) {
// doesn't exist
}
But it seems the error cannot be caught (but shown in the console).
Thanks for any kind of tips!
Each chrome.contextMenus.create call returns an unique identifier. Store these identifiers in an array or hash to keep track of them.
This is a direct solution to anyone having the op's problem, based on the suggestion by Rob W. The idea is to maintain your own list of existing context menu id's.
By using these wrapper functions to maintain context menu entries, also the removal and updates are being kept track of (addressing Fuzzyma's comment).
Usage works like Chrome's own methods, eg. createContextMenu({id: "something"}, onclick). It works for me.
let contextMenus = {}
// method to create context menu and keep track of its existence
function createContextMenu() {
if (arguments[0] && arguments[0].id) {
// TODO: not sure if this will work properly, is creation synchronous or asynchrounous?
// take in to account calll back and the runtime error?
chrome.contextMenus[arguments[0].id] = chrome.contextMenus.create.apply(null, arguments);
}
}
function updateContextMenu() {
if (arguments[0] && contextMenus[arguments[0]]) {
chrome.contextMenus.update.apply(mull, arguments);
}
}
function removeContextMenu() {
if (arguments[0] && contextMenus[arguments[0]]) {
chrome.contextMenus.remove.apply(null, arguments);
contextMenus[arguments[0]] = undefined;
}
}
function contextMenuExists(id) {
return !!contextMenus[id];
}

Best way to deal with document locking in xPages?

What is the best way to deal with document locking in xPages? Currently we use the standard soft locking and it seems to work fairly well in the Notes client.
In xPages I considered using the "Allow Document Locking" feature but I am worried that people would close the browser without using a close or save button then the lock would never be cleared.
Is there a way to clear the locks when the user has closed his session? I am seeing no such event.
Or is there an easier way to have document locking?
I realize I can clear the locks using an agent but when to run it? I would think sometime a night then I am fairly certain the lock should no longer really be active.
Here is code I'm using:
/* DOCUMENT LOCKING */
/*
use the global object "documentLocking" with:
.lock(doc) -> locks a document
.unlock(doc) -> unlocks a document
.isLocked(doc) -> returns true/false
.lockedBy(doc) -> returns name of lock holder
.lockedDT(doc) -> returns datetime stamp of lock
*/
function ynDocumentLocking() {
/*
a lock is an entry in the application scope
with key = "$ynlock_"+UNID
containing an array with
(0) = username of lock holder
(1) = timestamp of lock
*/
var lockMaxAge = 60 * 120; // in seconds, default 120 min
this.getUNID = function(v) {
if (!v) return null;
if (typeof v == "NotesXspDocument") return v.getDocument().getUniversalID();
if (typeof v == "string") return v;
return v.getUniversalID();
}
/* puts a lock into application scope */
this.lock = function(doc:NotesDocument) {
var a = new Array(1);
a[0] = #UserName();
a[1] = #Now();
applicationScope.put("$ynlock_"+this.getUNID(doc), a);
// print("SET LOCK "+"$ynlock_"+doc.getUniversalID()+" / "+a[0]+" / "+a[1]);
}
/* removes a lock from the application scope */
this.unlock = function(doc:NotesDocument) {
applicationScope.put("$ynlock_"+this.getUNID(doc), null);
//print("REMOVED LOCK for "+"$ynlock_"+doc.getUniversalID());
}
this.isLocked = function(doc:NotesDocument) {
try {
//print("ISLOCKED for "+"$ynlock_"+doc.getUniversalID());
// check how old the lock is
var v = applicationScope.get("$ynlock_"+this.getUNID(doc));
if (!v) {
//print("no lock found -> return false");
return false;
}
// if lock holder is the current user, treat as not locked
if (v[0] == #UserName()) {
//print("lock holder = user -> not locked");
return false;
}
var dLock:NotesDateTime = session.createDateTime(v[1]);
var dNow:NotesDateTime = session.createDateTime(#Now());
// diff is in seconds
//print("time diff="+dNow.timeDifference(dLock)+" dLock="+v[1]+" now="+#Now());
// if diff > x seconds then remove lock, it not locked
if (dNow.timeDifference(dLock) > lockMaxAge) {
// print("LOCK is older than maxAge "+lockMaxAge+" -> returning false");
return false;
}
//print("return true");
return true;
// TODO: check how old the lock is
} catch (e) {
print("ynDocumentLocking.isLocked: "+e);
}
}
this.lockedBy = function(doc:NotesDocument) {
try {
var v = applicationScope.get("$ynlock_"+this.getUNID(doc));
if (!v) return "";
//print("ISLOCKEDBY "+"$ynlock_"+doc.getUniversalID()+" = "+v[0]);
return v[0];
} catch (e) {
print("ynDocumentLocking.isLockedBy: "+e);
}
}
this.lockedDT = function(doc:NotesDocument) {
try {
var v = applicationScope.get("$ynlock_"+this.getUNID(doc));
if (!v) return "";
return v[1];
} catch (e) {
print("ynDocumentLocking.isLockedBy: "+e);
}
}
}
var documentLocking = new ynDocumentLocking();
You could take a page from the way webDAV works. There a servlet manages a "lock-list" of locked documents. The locks automatically expire after 10 minutes. Locks can be renewed or terminated trough calls. So when you edit a document you would request a lock, then kick off a CSJS timer that calls the relocking function every 8 minutes (so you have some margin for error) and the postSave calls the unlock (unless you stay in edit mode).
If a user closes the browser after 10 minutes the document is automatically unlocked. Since you are free how to implement the locking function, you can capture user/location and use that information in the "lock failed" display (you event could push that further and let the original author know about it or do some "retry" option.
It isn't simple to implement, but once implemented simple to use
ApplicationScope may be a good place to capture "locked" documents. After all, for applicationScope to expire, all users' sessions have to have expired, so anyone with the page open will not be able to save anyway.
Maybe capture UNID, user and time when someone edits a doc. Clear the value when the document is saved. Bear in mind that the user might close the browser etc. I've been discussing this approach internally and if we end up building this I would look to add it to OpenNTF. But we're unlikely to get onto it within the next month.
I Prefer to use a solution similar to Mr. Withers' answer. The main issue is how to deal with the unwanted and dreaded back button. It is easy to lock a document when it is opened, but there are many ways to close the XPage, and the user is not limited to just the navigation you provide but also can, as he stated, close the browser completely, use the back button, etc. So, the best way that I can think of is to create a few java objects which we will use in the application and session scopes.
The first step is to create a "LockedDocument" class. As we know, the documents are not serializable and we do not want to save the document itself in this object, we want to save the UNID and the time it was saved. We want to do it this way so that we can manage to clear the object after a given time (like thirty minutes to an hour). This class should also implement the comparable interface in order to sort the collection by this time so that the oldest documents are first and the newest documents are last.
Next we create another class that holds a list or a map with these LockedDocuments. This class must also have a thread (implement Runnable) that will check all documents every five minutes or so, I did not test this yet, but it should work). Any document that was locked thirty to sixty minutes ago (predefined) will be unlocked (deleted from the list). It is important that the list be sorted as described above and that the loop is "broken" when a time less than the locktime is reached in order to prevent unwanted processing.
The next step would be to include the user specific list in the sessionScope. This list is the LockedDocuments that this current user has. It is set when the user changes the document's status to editable, and is checked before the document is set to editable to prevent one document from being opened in multiple tabs by the same user. The lock is once again checked onquerysave(). Once a main page is opened, the lock is automatically released. The onquerysave() must also check to make sure the documents UNID is in the sessionScope list, or if the document is new before allowing a save.
quick recap
Any UNID saved in the applicationScope LockedDocumentList would not be editable by anyone unless it exists in their own sessionScope list.
It is possible to warn a user that their lockedTime is approaching and reset the timer.
The class containing a list with the locked documents must be a singleton
There are probably ways to improve this answer, and I am sure I am missing something. It is just a thought.
There might be a better way to handle this, but it is the best I found.
You can remove the Domino lock in window.onunload event:
window.onunload = function(){
dojo.xhrGet(...
}
No need to reinvent the wheel.

Kohana 3: How can I pass full control to another action within my controller?

In my controller, I have a before() function that calls parent::before() and then does some additional processing once the parent returns. based on a specific condition, I want to "save" the original request and pass execution to a specific action. Here is my before() function.
public function before() {
parent::before();
$this->uri = Request::Instance()->uri;
$match = ORM::factory('survey_tester')
->where('eid','=',$this->template->user->samaccountname)
->find();
if (!$match->loaded()) {
self::action_tester("add",$this->template->user);
}
}
And the action that is being called..
public function action_tester($op=null,$user=null) {
$testers = ORM::factory('survey_tester')->find_all();
$tester = array();
$this->template->title = 'Some new title';
$this->template->styles = array('assets/css/survey/survey.css' => 'screen');
$this->template->scripts = array('assets/js/survey/tester.js');
$tester['title'] = $this->template->title;
$tester['user'] = $this->template->user;
switch ($op) {
case "add":
$tester = ORM::factory('survey_tester');
$tester->name = $user->displayname;
$tester->email = $user->mail;
$tester->division = $user->division;
$tester->eid = $user->samaccountname;
if ($tester->save()) {
$this->template->content = new View('pages/survey/tester_add', $admin);
} else {
$this->template->content = new View('pages/survey/tester_error', $admin);
}
break;
default:
break;
}
}
This all seems to work fine. This is designed to prompt the user for a specific piece of information that is not provided by $user (populated by LDAP) if this is the first time they are hitting the controller for any reason.
The problem is the views are not rendering. Instead control passes back to whatever action was originally requested. This controller is called survey. If i browse to http://my.site.com/survey and login with new user info, the record gets written and i get the action_index views instead of my action_tester views.
I cannot figure out what I am doing wrong here. Any ideas will be appreciated. Thank you.
EDIT: I managed to get this working (sort-of) by using $this->request->action = 'tester'; but I'm not sure how to add/set new params for the request yet.
The issue is that you are calling your method (action_tester), but then Kohana is still going to call the original action after the before method is called, which is going to change the response content overwriting the changed made in action_tester().
You can change the action being called (after before is called) inside your before() method:
$this->request->action('action_tester');
After the before method is called, it should then call the new Action (action_tester) rather than the old one, but then you need to do something about the way you are passing your parameters then.
Or you could just redirect the request upon some condition:
if($something) {
$this->request->redirect('controller/tester');
}
This doesn't seem like a nice way to do it anyway.

Resources