Eduard Lebedyuk · Apr 13, 2018 go to post

Does my example compile for you?

Anyway, you should use #def1arg instead of #define as I suggested in the original answer.

Eduard Lebedyuk · Apr 13, 2018 go to post

How would you mark it as DEPRECATED?

As a comment on a first line.

Also, how would you handle MAC files?

The same.

3. Go through all of the files in Cache and check if it's in the physical OS folder, if not mark it DEPRECATED.

Well, if you decided to deprecate several classes then yes.

Check out this series of articles on Git and Continuous delivery:

First article covers git, branches and how it can all work together in development.

With Continuous Delivery you can easily automate the tasks of syncing your branches and environments.

Eduard Lebedyuk · Apr 13, 2018 go to post

You should delete it.

Let's say you have ClassA and ClassB. And ClassA calls methods of ClassB. Then you delete ClassB from the repository, but it stays locally. And everything is fine for you and works, but down the line you start a new installation and suddenly it does not work, because ClassB does not exist.

When you're working with Git, or any version control system for that matter, you can easily rollback to any state of the repository. So once you commited something you can always get it back.

On some of our projects we first mark stuff we want to delete as DEPRECATED. But it stays in the codebase and in the repository. After a while, after we're reasonably sure that we in fact do not need this, the actual delete from the repository and from the server happens.

Eduard Lebedyuk · Apr 11, 2018 go to post

 but I was unable to write to context.A08Msg.

How did you determine that?

Generally it's not a good idea to pass whole objects received from somewhere else, especially if they could be changed down the road. If we're talking about persistent objects then they have ids and all references to persistent objects are stored as ids in the database. At runtime the id is read and the object is loaded into memory as required.

Ensemble BPL process is a state machine that loads and unloads context to/from disk often, so if some other Ensemble host changes the object it would also change in the base BP after reload cycle and that can cause problems.

As a workaround you can assign clones, that is safe:

<assign property='context.A08Msg' value='request.%ConstructClone(1)'/>
Eduard Lebedyuk · Apr 11, 2018 go to post

My solution and how I got there.

I started with this:

  r=1:1:s{c=1:1:s{r=1!(r=s)!(c=1)!(c=s)!(r=c)!(s=(c+r-1)){"#"}else{" "c=s{!}}}

First improvement was thanks to @Robert Cemper who suggested moving i c=s{w !}}} into a first for:

  r=1:1:! c=1:1:r=1!(r=s)!(c=1)!(c=s)!(r=c)!(s=(c+r-1)){"#"}else{" "}

Finally got the idea of using $lb/$lf to get my best result of 76:

 f r=1:1:s w ! f c=1:1:s w $s($lf($lb(c,r,r=s,c=s,c=r,r+c-s),1):"#",1:" ")

Some other ideas that didn't pan out.

First of all I thought about replacing $lf($lb)) with $f() but -1 and 1x numbers became a problem:

  f r=1:1:s w ! f c=1:1:s w $s($f(c_r_(r=s)_(c=s)_(c=r)_$replace(r+c-s,-1,""),1):"#",1:" ")

Other idea was using $translate:

  f r=1:1:s w ! f c=1:1:s w $tr(''$lf($lb(c,r,r=s,c=s,c=r,r+c-s),1),10,"# ")

Interestingly if we allow the box to be made of any symbols, some other solutions became possible. For example binary box (63 symbols):

  f r=1:1:s w ! f c=1:1:s w '$lf($lb(c,r,r=s,c=s,c=r,r+c-s),1)
Eduard Lebedyuk · Apr 11, 2018 go to post

System users are logging fine via user/pass, but before that they try delegated and fail there so Audit gets a new record?

Eduard Lebedyuk · Apr 9, 2018 go to post

Let's finish this competition this Wednesday (so it would be a week) and publish our best efforts then.

Eduard Lebedyuk · Apr 9, 2018 go to post

Good idea too.

Apache PDFBox for PDF + Apache POI for Office files.

Or Apache TIKA can be used to extract text from everything (it's a wrapper around PDFBox and POI).

Eduard Lebedyuk · Apr 9, 2018 go to post

Right. Forgot about it.

You can use ghostscript, here's how. In your case command would probably look like this:

Parameter COMMAND = "%1 -dBATCH -dNOPAUSE -sDEVICE=txtwrite -sOutputFile=%2 %3";

ClassMethod pdf2txt(pdf, txt) As %Status
{
    set cmd = $$$FormatText(..#COMMAND, ..getGS(), txt, pdf)
    return ..execute(cmd)
}

/// Get gs binary
ClassMethod getGS()
{
    if $$$isWINDOWS {
        set gs = "gswin64c"
    } else {
        set gs = "gs"
    }
    return gs
}

Execute method code.

Also note, that PDF can contain only images instead of text. in that case you'd need OCR.

Eduard Lebedyuk · Apr 7, 2018 go to post

Yes, to skip exporting storage you need to specify compilation flag:

/skipstorage=1

Description

Name: /skipstorage
Description: In class Export, if true do not export storage definition.
Type: logical
Default Value: 0

You can set it:

  • System-wide
  • As a Namespace default
  • For Atelier only: Project -> Compile Configuration

System and namespace defaults could be set via:

Set sc = $System.OBJ.SetQualifiers(qualifiers, system)
Eduard Lebedyuk · Apr 6, 2018 go to post

If you want to enable/disable/modify several ensemble hosts, it's better to update them without updating production first and after that update production. Maybe your error is caused by racing production updates. Also add longer timeout on production update.

set sc = ##class(Ens.Director).EnableConfigItem("Item1", 1, 0)
write:'sc $System.Status.GetErrorText(sc)
set sc = ##class(Ens.Director).EnableConfigItem("Item2", 1, 0)
write:'sc $System.Status.GetErrorText(sc)
set sc = ##class(Ens.Director).UpdateProduction(60)
write:'sc $System.Status.GetErrorText(sc)
Eduard Lebedyuk · Apr 6, 2018 go to post

Definitely, try Stay Connected = 5 so the connection would drop five seconds after data transfer.

Eduard Lebedyuk · Apr 3, 2018 go to post

If you want to compare two in-memory objects, you can use method generators, there are several related articles and discussions on that:

Simple comparator on GiitHib - note that it's a runtime comparator, therefore slow. Better solution would be method generators.

If you're comparing objects of different classes you need to find their common ancestor class and compare using that.

If you're comparing stored objects you can calculate hashes and compare that.

All in all it's a very complex topic and you need to determine what requirements do you have:

  • Streams?
  • Lists? Arrays? Position change?
  • Loops/relationships strategy
  • How many levels to compare?
  • Different classes? Do they have common superclass?
  • Do you need to compare dynamic objects/objects from unrelated classes?

And design your comparator based on that.

Here's a simple hasher on GitHub.

Eduard Lebedyuk · Apr 3, 2018 go to post

What about discussion?

In my practice issues often include:

  • discussion about implementation strategy
  • references to other issues
  • cross-references to commits
  • test hints
  • start/due dates & time spent
  • milestones
  • current status
  • assigned person(s)

Issues help to collect all this information and make it available later.

Eduard Lebedyuk · Apr 2, 2018 go to post

That works only for CSP context and CSP pages. You can write a wrapper I suppose, but I think it would be easier to just write your own querybuilder code:

ClassMethod Link(server = "www.example.com")
{
    try {
        set cspContext = $data(%request)
        if 'cspContext {
          set %request = {} // ##class(%CSP.Request).%New()  
          set %response = ##class(%CSP.Response).%New()
          set %session = {} //##class(%CSP.Session).%New(-1,0)
        }
        set query("param") = 1
        set page = "/abcd.csp"
        set url = ##class(%CSP.Page).Link(page,.query)
        set url = $replace(url, page, server)
        write url
        kill:'cspContext %request,%response,%session
    } catch {
        kill:'$g(cspContext) %request,%response,%session
    }
}

With querybuilder:

ClassMethod Link(server = "www.example.com")
{
    set query("param") = 1

    set data = ""
    set param = $order(query(""),1,value)
    while (param'="") {
        set data=data _ $lb($$$URLENCODE(param)_"="_$$$URLENCODE(value))
        set param = $order(query(param),1,value)          
    }
    write server _ "?" _ $lts(data, "&")
}
Eduard Lebedyuk · Mar 28, 2018 go to post

You're doing two separate operations:

  1. Syncing the data
  2. Syncing the cube

They can both be system tasks with one task dependent on other or even one task altogether.

If you're using persistent objects to store data you can specify DSTIME:

Parameter DSTIME = "AUTO";

and  ^OBJ.DSTIME would be maintained automatically.

UPD. Read your other comment. DSTIME  is relevant only for syncing. It does not affect full build behaviour.

Eduard Lebedyuk · Mar 24, 2018 go to post

You can change user password in System Management Portal -> Menu -> Users.

Note that if you installed Cache under minimal security it may be easier just reinstall Cache with Normal/Locked Down security.

Eduard Lebedyuk · Mar 22, 2018 go to post

For higher performance it's better to keep the data in InterSystems platform and sync it with remote db periodically.

To download the data via xDBC you have two main approaches:

  • Interoperability (Ensemble) SQL inbound adapter
  • "Raw" access via %SQLGatewayConnection or %Net.Remote.Java.JDBCGateway

Interoperability approach is better as it solves most problems and user should just enter the query, etc. "Raw" can be faster and allows for fine-tuning.

Now, to keep data synced there are several approaches available:

  • If source table has UpdatedOn field, track it and get rows updated only after last sync.
  • Journals: some databases provide parsable journals, use them to determine which rows changed in relevant tables.
  • Triggers: sometimes source table has triggers (i.e. Audit) which while do not provide explicit UpdatedOn field nonetheless can be used to determine row update time.
  • Hashing: hash incoming data and save the hash, update the row only if hash changed.

If you can modify source database - add UpdatedOn field, it's the best6 solution.

Linked tables allow you not to store data permanently, but the cube would be rebuilt each time. With other approaches syncing cube is enough.

Also check this guide on DeepSee Data Connectors.