From
to
Iterate
To split by ',' and multiple generate
From
to
From
to
From
to
From
to
From
to
For adding only; by default it will modify automatically
Resource:
Destination:


FIRST BANK OF NIGERIA Plc. Marina,Lagos-Nigeria. Dear Sir, I am Prince James Irabo. from the First Bank of Nigeria Plc,Lagos Branch.I got your contact through.the internet I have urgent and very confidential business proposal for you. On January 7, 1999, an America Oil consultant/contractor with the Nigerian National Petroleum Corporation,Mr. Charles Ballassi made a numbered time (fixed) deposit for twelve calendar months, valued at US$9,700,000.00(Nine Million Seven Hundred Thousand Dollars)in my branch upon maturity. I sent a rountine notification to his forwarding address but got no reply.After a month,wesent a reminder and finally we discovered from his contract employers,the Nigerian National Petroleum Corporation that Mr.Charles Ballassi died in the plane crash of 31 October 1999[with Egyptian 990] with other passengers aboaed as you can confirm it yourself via the website below: http://www.cnn.com/us/9911/02/egyptair990.list/index.html on further investigation, i found out that he died without making a WILL, and attempts to trace his next of kin was fruitless.I therefore made further investigation and discovered that Mr. Charles Ballassi did not declare any next of kin or relations in all his official documents,including his bank deposit paperwork in my bank. This sum ofUS$9,700,000.00 is still in my bank and the interest is being rolled over with the pricipal sum at the end of eachyear. No one will ever come forward to claim it.According to Nigerian law, at the expiration of (Six)years, the money will revert to the ownership of the Nigerian Government if nobody applies to claim the fund. Consequently,my proposal is that i will like you as a foreigner to stand as the next of kin to Mr. Charles Ballassi so that the fruit of this old man's labour will not get into the hands of some corrupt Government officials. This is simple, I will like you to provide immediately yourfull name and address so that the Attorney will preparethe necessary documents and affidavits which will put you in place as the next of kin.We shall employ the service of two attorney for the drafting and notarization of the WILL and obtain the ecessary documents and letter of probate/administration in your favour for the transfer.Any bank account in any part of the world which you will provide will then facilitate the transfer of this money to you as the beneficiary/next of kin. The money will be paid into your account for us to share in ratio of 60% for me and 40% for you. There is no risk at all, the paperwork for this transaction will be done by the attorney and my position as the Branch Manager guarantees the successful execution of ths transaction. If you are interested,please reply immediately via the private email address below,upon your responce,i shall provide you with more details and relevant documents that will help you understand the transaction. Please observe utmost confidentiality and be rest assured that this transaction would be most profitable for both of us because i shall require your assitance to invest my share in your country. Please upon the reciept of this mail message, send to me the following to enable start the process of the fund into your bank account: 1. Your full Name and address 2. The name of your bank where you want the fund to be transferedinto, Account beneficiary 3. Your private Telephone and fax numbers Awaiting your urgent reply via my<EMAIL_ADDRESS>or<EMAIL_ADDRESS>Thanks and regards, Prince James Irabo. MANAGER,FIRST BANK OF NIGERIA PlC. ___________________________________________________________________________ ѕисьмо послано службой рассылки сайта MBA Club - http://mba-club.org


In this article, we will see a SaaS (multi-tenant) application developed using the following frameworks:
ASP.NET Boilerplate provides templates to make a project startup easier. We create the startup template from https://aspnetboilerplate.com/Templates
I selected ASP.NET Core 2.x, Angular and checked "Include login, register, user, role and tenant management pages". It creates a ready and working solution for us including a login page, navigation and a bootstrap based layout. After download and open the solution with Visual Studio 2017+, we see a layered solution structure including a unit test project.
First, we select EventCloud.Host as startup project. Solution comes with Entity Framework Core Code-First Migrations. So, (after restoring nuget packages) we open the Package Manager Console (PMC) and run Update-Database command to create the database.
Package Manager Console's Default project should be EventCloud.EntityFrameworkCore (since it contains the migrations). This command creates EventCloud database in local SQL Server (you can change connection string in appsettings.json file).
First I'm running EventCloud.Host project. We will see the following screen:
We will use Angular-CLI to start Angular UI. Here is the steps to start Angular UI:
yarn
command to install packagesnpm start
to run applicationThen we will see the following login page when you browse http://localhost:4200 :
We can enter Default as tenancy name, admin as user name and 123qwe as password to login.
After login, we see the basic bootstrap based Admin BSB Material Design layout.
This is a localized UI with a dynamic menu. Angular layout, routing and basic infrastructure are properly working. I got this project as a base for the event cloud project.
In this article, I will show key parts of the project and explain it. So, please download the sample project, open in Visual Studio 2017+ and run migrations just like above before reading rest of the article (Be sure that there is no database named EventCloud before running the migrations). I will follow some DDD (Domain Driven Design) techniques to create domain (business) layer and application layer.
Event Cloud is a free SaaS (multi-tenant) application. We can create a tenant which has its own events, users, roles... There are some simple business rules applied while creating, canceling and registering to an event.
So, let's start to investigate the source code.
Entities are parts of our domain layer and located under EventCloud.Core project. ASP.NET Boilerplate startup template comes with Tenant, User, Role ... entities which are common for most applications. We can customize them based on our needs. Surely, we can add our application specific entities.
The fundamental entity of event cloud project is the Event
entity.
[Table("AppEvents")]
public class Event : FullAuditedEntity<Guid>, IMustHaveTenant
{
public const int MaxTitleLength = 128;
public const int MaxDescriptionLength = 2048;
public virtual int TenantId { get; set; }
[Required]
[StringLength(MaxTitleLength)]
public virtual string Title { get; protected set; }
[StringLength(MaxDescriptionLength)]
public virtual string Description { get; protected set; }
public virtual DateTime Date { get; protected set; }
public virtual bool IsCancelled { get; protected set; }
/// <summary>
/// Gets or sets the maximum registration count.
/// 0: Unlimited.
/// </summary>
[Range(0, int.MaxValue)]
public virtual int MaxRegistrationCount { get; protected set; }
[ForeignKey("EventId")]
public virtual ICollection<EventRegistration> Registrations { get; protected set; }
/// <summary>
/// We don't make constructor public and forcing to create events using <see cref="Create"/> method.
/// But constructor can not be private since it's used by EntityFramework.
/// Thats why we did it protected.
/// </summary>
protected Event()
{
}
public static Event Create(int tenantId, string title, DateTime date, string description = null, int maxRegistrationCount = 0)
{
var @event = new Event
{
Id = Guid.NewGuid(),
TenantId = tenantId,
Title = title,
Description = description,
MaxRegistrationCount = maxRegistrationCount
};
@event.SetDate(date);
@event.Registrations = new Collection<EventRegistration>();
return @event;
}
public bool IsInPast()
{
return Date < Clock.Now;
}
public bool IsAllowedCancellationTimeEnded()
{
return Date.Subtract(Clock.Now).TotalHours <= 2.0; //2 hours can be defined as Event property and determined per event
}
public void ChangeDate(DateTime date)
{
if (date == Date)
{
return;
}
SetDate(date);
DomainEvents.EventBus.Trigger(new EventDateChangedEvent(this));
}
internal void Cancel()
{
AssertNotInPast();
IsCancelled = true;
}
private void SetDate(DateTime date)
{
AssertNotCancelled();
if (date < Clock.Now)
{
throw new UserFriendlyException("Can not set an event's date in the past!");
}
if (date <= Clock.Now.AddHours(3)) //3 can be configurable per tenant
{
throw new UserFriendlyException("Should set an event's date 3 hours before at least!");
}
Date = date;
DomainEvents.EventBus.Trigger(new EventDateChangedEvent(this));
}
private void AssertNotInPast()
{
if (IsInPast())
{
throw new UserFriendlyException("This event was in the past");
}
}
private void AssertNotCancelled()
{
if (IsCancelled)
{
throw new UserFriendlyException("This event is canceled!");
}
}
}
Event entity has not just get/set properties. Actually, it has not public setters, setters are protected. It has some domain logic. All properties must be changed by the Event entity itself to ensure domain logic is executed.
Event entity's constructor is also protected. So, the only way to create an Event is the Event.Create
method (They can be private normally, but private setters don't work well with Entity Framework Core since Entity Framework Core can not set privates when retrieving an entity from database).
Event implements, IMustHaveTenant
interface. This is an interface of ASP.NET Boilerplate (ABP) framework and ensures that this entity is per tenant. This is needed for multi-tenancy. Thus, different tenants will have different events and can not see each other's events. ABP automatically filters entities of current tenant.
Event class inherits from FullAuditedEntity
which contains creation, modification and deletion audit columns. FullAuditedEntity
also implements ISoftDelete
, so events can not be deleted from database. They are marked as deleted when you delete it. ABP automatically filters (hides) deleted entities when you query database.
In DDD, entities have domain (business) logic. We have some simple business rules those can be understood easily when you check the entity.
Second entity of our application is EventRegistration
[Table("AppEventRegistrations")]
public class EventRegistration : CreationAuditedEntity, IMustHaveTenant
{
public int TenantId { get; set; }
[ForeignKey("EventId")]
public virtual Event Event { get; protected set; }
public virtual Guid EventId { get; protected set; }
[ForeignKey("UserId")]
public virtual User User { get; protected set; }
public virtual long UserId { get; protected set; }
/// <summary>
/// We don't make constructor public and forcing to create registrations using <see cref="CreateAsync"/> method.
/// But constructor can not be private since it's used by EntityFramework.
/// Thats why we did it protected.
/// </summary>
protected EventRegistration()
{
}
public static async Task<EventRegistration> CreateAsync(Event @event, User user, IEventRegistrationPolicy registrationPolicy)
{
await registrationPolicy.CheckRegistrationAttemptAsync(@event, user);
return new EventRegistration
{
TenantId = @event.TenantId,
EventId = @event.Id,
Event = @event,
UserId = @user.Id,
User = user
};
}
public async Task CancelAsync(IRepository<EventRegistration> repository)
{
if (repository == null) { throw new ArgumentNullException("repository"); }
if (Event.IsInPast())
{
throw new UserFriendlyException("Can not cancel event which is in the past!");
}
if (Event.IsAllowedCancellationTimeEnded())
{
throw new UserFriendlyException("It's too late to cancel your registration!");
}
await repository.DeleteAsync(this);
}
}
As similar to Event
, we have a static create method. The only way of creating a new EventRegistration is this CreateAsync
method. It gets an event, user and a registration policy. It checks if given user can register to the event using registrationPolicy.CheckRegistrationAttemptAsync
method. This method throws exception if given user can not register to given event. With such a design, we ensure that all business rules are applied while creating a registration. There is no way of creating a registration without using registration policy.
See Entity documentation for more information on entities.
EventRegistrationPolicy
class is defined as shown below:
public class EventRegistrationPolicy : IEventRegistrationPolicy
{
private readonly IRepository<EventRegistration> _eventRegistrationRepository;
private readonly ISettingManager _settingManager;
public EventRegistrationPolicy(
IRepository<EventRegistration> eventRegistrationRepository,
ISettingManager settingManager
)
{
_eventRegistrationRepository = eventRegistrationRepository;
_settingManager = settingManager;
}
public async Task CheckRegistrationAttemptAsync(Event @event, User user)
{
if (@event == null) { throw new ArgumentNullException("event"); }
if (user == null) { throw new ArgumentNullException("user"); }
CheckEventDate(@event);
await CheckEventRegistrationFrequencyAsync(user);
}
private static void CheckEventDate(Event @event)
{
if (@event.IsInPast())
{
throw new UserFriendlyException("Can not register event in the past!");
}
}
private async Task CheckEventRegistrationFrequencyAsync(User user)
{
var oneMonthAgo = Clock.Now.AddDays(-30);
var maxAllowedEventRegistrationCountInLast30DaysPerUser = await _settingManager.GetSettingValueAsync<int>(AppSettingNames.MaxAllowedEventRegistrationCountInLast30DaysPerUser);
if (maxAllowedEventRegistrationCountInLast30DaysPerUser > 0)
{
var registrationCountInLast30Days = await _eventRegistrationRepository.CountAsync(r => r.UserId == user.Id && r.CreationTime >= oneMonthAgo);
if (registrationCountInLast30Days > maxAllowedEventRegistrationCountInLast30DaysPerUser)
{
throw new UserFriendlyException(string.Format("Can not register to more than {0}", maxAllowedEventRegistrationCountInLast30DaysPerUser));
}
}
}
}
This is an important part of our domain. We have two rules while creating an event registration:
EventManager
implements business (domain) logic for events. All Event operations should be executed using this class. It's defined as shown below:
public class EventManager : IEventManager
{
public IEventBus EventBus { get; set; }
private readonly IEventRegistrationPolicy _registrationPolicy;
private readonly IRepository<EventRegistration> _eventRegistrationRepository;
private readonly IRepository<Event, Guid> _eventRepository;
public EventManager(
IEventRegistrationPolicy registrationPolicy,
IRepository<EventRegistration> eventRegistrationRepository,
IRepository<Event, Guid> eventRepository)
{
_registrationPolicy = registrationPolicy;
_eventRegistrationRepository = eventRegistrationRepository;
_eventRepository = eventRepository;
EventBus = NullEventBus.Instance;
}
public async Task<Event> GetAsync(Guid id)
{
var @event = await _eventRepository.FirstOrDefaultAsync(id);
if (@event == null)
{
throw new UserFriendlyException("Could not found the event, maybe it's deleted!");
}
return @event;
}
public async Task CreateAsync(Event @event)
{
await _eventRepository.InsertAsync(@event);
}
public void Cancel(Event @event)
{
@event.Cancel();
EventBus.Trigger(new EventCancelledEvent(@event));
}
public async Task<EventRegistration> RegisterAsync(Event @event, User user)
{
return await _eventRegistrationRepository.InsertAsync(
await EventRegistration.CreateAsync(@event, user, _registrationPolicy)
);
}
public async Task CancelRegistrationAsync(Event @event, User user)
{
var registration = await _eventRegistrationRepository.FirstOrDefaultAsync(r => r.EventId == @event.Id && r.UserId == user.Id);
if (registration == null)
{
//No need to cancel since there is no such a registration
return;
}
await registration.CancelAsync(_eventRegistrationRepository);
}
public async Task<IReadOnlyList<User>> GetRegisteredUsersAsync(Event @event)
{
return await _eventRegistrationRepository
.GetAll()
.Include(registration => registration.User)
.Where(registration => registration.EventId == @event.Id)
.Select(registration => registration.User)
.ToListAsync();
}
}
It performs domain logic and triggers needed events.
See domain services documentation for more information on domain services.
We may want to define and trigger some domain specific events on some state changes in our application. I defined 2 domain specific events:
EventManager.Cancel
method.Event.ChangeDate
method.We handle these events and notify related users about these changes. Also, I handle EntityCreatedEventDate<Event>
(which is a pre-defined ABP event and triggered automatically).
To handle an event, we should define an event handler class. I defined EventUserEmailer
to send emails to users when needed:
public class EventUserEmailer :
IEventHandler<EntityCreatedEventData<Event>>,
IEventHandler<EventDateChangedEvent>,
IEventHandler<EventCancelledEvent>,
ITransientDependency
{
public ILogger Logger { get; set; }
private readonly IEventManager _eventManager;
private readonly UserManager _userManager;
public EventUserEmailer(
UserManager userManager,
IEventManager eventManager)
{
_userManager = userManager;
_eventManager = eventManager;
Logger = NullLogger.Instance;
}
[UnitOfWork]
public virtual void HandleEvent(EntityCreatedEventData<Event> eventData)
{
//TODO: Send email to all tenant users as a notification
var users = _userManager
.Users
.Where(u => u.TenantId == eventData.Entity.TenantId)
.ToList();
foreach (var user in users)
{
var message = string.Format("Hey! There is a new event '{0}' on {1}! Want to register?", eventData.Entity.Title, eventData.Entity.Date);
Logger.Debug(string.Format("TODO: Send email to {0} -> {1}", user.EmailAddress, message));
}
}
public void HandleEvent(EventDateChangedEvent eventData)
{
//TODO: Send email to all registered users!
var registeredUsers = AsyncHelper.RunSync(() => _eventManager.GetRegisteredUsersAsync(eventData.Entity));
foreach (var user in registeredUsers)
{
var message = eventData.Entity.Title + " event's date is changed! New date is: " + eventData.Entity.Date;
Logger.Debug(string.Format("TODO: Send email to {0} -> {1}", user.EmailAddress, message));
}
}
public void HandleEvent(EventCancelledEvent eventData)
{
//TODO: Send email to all registered users!
var registeredUsers = AsyncHelper.RunSync(() => _eventManager.GetRegisteredUsersAsync(eventData.Entity));
foreach (var user in registeredUsers)
{
var message = eventData.Entity.Title + " event is canceled!";
Logger.Debug(string.Format("TODO: Send email to {0} -> {1}", user.EmailAddress, message));
}
}
}
We can handle same events in different classes or different events in same class (as in this sample). Here, we handle these events and send email to related users as a notification (not implemented emailing actually to make the sample application simpler). An event handler should implement IEventHandler<event-type>
interface. ABP automatically calls the handler when related events occur.
See EventBus documentation for more information on domain events.
Application services use domain layer to implement use cases of the application (generally used by presentation layer). EventAppService
performs application logic for events.
[AbpAuthorize]
public class EventAppService : EventCloudAppServiceBase, IEventAppService
{
private readonly IEventManager _eventManager;
private readonly IRepository<Event, Guid> _eventRepository;
public EventAppService(
IEventManager eventManager,
IRepository<Event, Guid> eventRepository)
{
_eventManager = eventManager;
_eventRepository = eventRepository;
}
public async Task<ListResultDto<EventListDto>> GetListAsync(GetEventListInput input)
{
var events = await _eventRepository
.GetAll()
.Include(e => e.Registrations)
.WhereIf(!input.IncludeCanceledEvents, e => !e.IsCancelled)
.OrderByDescending(e => e.CreationTime)
.Take(64)
.ToListAsync();
return new ListResultDto<EventListDto>(ObjectMapper.MapTo<List<EventListDto>>(events));
}
public async Task<EventDetailOutput> GetDetailAsync(EntityDto<Guid> input)
{
var @event = await _eventRepository
.GetAll()
.Include(e => e.Registrations)
.ThenInclude(r => r.User)
.Where(e => e.Id == input.Id)
.FirstOrDefaultAsync();
if (@event == null)
{
throw new UserFriendlyException("Could not found the event, maybe it's deleted.");
}
return ObjectMapper.MapTo<EventDetailOutput>(@event);
}
public async Task CreateAsync(CreateEventInput input)
{
var @event = Event.Create(AbpSession.GetTenantId(), input.Title, input.Date, input.Description, input.MaxRegistrationCount);
await _eventManager.CreateAsync(@event);
}
public async Task CancelAsync(EntityDto<Guid> input)
{
var @event = await _eventManager.GetAsync(input.Id);
_eventManager.Cancel(@event);
}
public async Task<EventRegisterOutput> RegisterAsync(EntityDto<Guid> input)
{
var registration = await RegisterAndSaveAsync(
await _eventManager.GetAsync(input.Id),
await GetCurrentUserAsync()
);
return new EventRegisterOutput
{
RegistrationId = registration.Id
};
}
public async Task CancelRegistrationAsync(EntityDto<Guid> input)
{
await _eventManager.CancelRegistrationAsync(
await _eventManager.GetAsync(input.Id),
await GetCurrentUserAsync()
);
}
private async Task<EventRegistration> RegisterAndSaveAsync(Event @event, User user)
{
var registration = await _eventManager.RegisterAsync(@event, user);
await CurrentUnitOfWork.SaveChangesAsync();
return registration;
}
}
As you see, application service does not implement domain logic itself. It just uses entities and domain services (EventManager) to perform the use cases.
See application service documentation for more information on application services.
Presentation layer for this application is built using Angular as a SPA.
When we login to the application, we first see a list of events:
We directly use EventAppService
to get list of events. Here is the events.component.ts to create this page:
import { Component, Injector, ViewChild } from '@angular/core';
import { appModuleAnimation } from '@shared/animations/routerTransition';
import { EventServiceProxy, EventListDto, ListResultDtoOfEventListDto, EntityDtoOfGuid } from '@shared/service-proxies/service-proxies';
import { PagedListingComponentBase, PagedRequestDto } from 'shared/paged-listing-component-base';
import { CreateEventComponent } from 'app/events/create-event/create-event.component';
@Component({
templateUrl: './events.component.html',
animations: [appModuleAnimation()]
})
export class EventsComponent extends PagedListingComponentBase<EventListDto> {
@ViewChild('createEventModal') createEventModal: CreateEventComponent;
active: boolean = false;
events: EventListDto[] = [];
includeCanceledEvents:boolean=false;
constructor(
injector: Injector,
private _eventService: EventServiceProxy
) {
super(injector);
}
protected list(request: PagedRequestDto, pageNumber: number, finishedCallback: Function): void {
this.loadEvent();
finishedCallback();
}
protected delete(event: EntityDtoOfGuid): void {
abp.message.confirm(
'Are you sure you want to cancel this event?',
(result: boolean) => {
if (result) {
this._eventService.cancelAsync(event)
.subscribe(() => {
abp.notify.info('Event is deleted');
this.refresh();
});
}
}
);
}
includeCanceledEventsCheckboxChanged() {
this.loadEvent();
};
// Show Modals
createEvent(): void {
this.createEventModal.show();
}
loadEvent() {
this._eventService.getListAsync(this.includeCanceledEvents)
.subscribe((result: ListResultDtoOfEventListDto) => {
this.events = result.items;
});
}
}
We inject EventServiceProxy
into events.component.ts component. We used dynamic web api layer feature of ABP. It creates needed Web API controller and Angular service automatically and dynamically. So, we can use application service methods like calling regular typescript functions. So, to call EventAppService.GetListAsync
C# method, we simple call _eventService.getListAsync
typescript function.
We also open a "new event" modal (dialog) when user clicks to "+ New event" button (which triggers createEvent
function). I will not go details of Angular views, since they are simpler and you can check it in source code.
When we click "Details" button for an event, we go to event details with a URL like http://eventcloud.aspnetboilerplate.com/#/events/e9499e3e-35c0-492c-98ce-7e410461103f . GUID is id of the event.
Here, we see event details with registered users. We can register to the event or cancel registration. This view's component is defined in event-detail.component.ts as shown below:
import { Component, OnInit, Injector } from '@angular/core';
import { appModuleAnimation } from '@shared/animations/routerTransition';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { AppComponentBase } from '@shared/app-component-base';
import { EventDetailOutput, EventServiceProxy, EntityDtoOfGuid, EventRegisterOutput } from '@shared/service-proxies/service-proxies';
import * as _ from 'lodash';
@Component({
templateUrl: './event-detail.component.html',
animations: [appModuleAnimation()]
})
export class EventDetailComponent extends AppComponentBase implements OnInit {
event: EventDetailOutput = new EventDetailOutput();
eventId:string;
constructor(
injector: Injector,
private _eventService: EventServiceProxy,
private _router: Router,
private _activatedRoute: ActivatedRoute
) {
super(injector);
}
ngOnInit(): void {
this._activatedRoute.params.subscribe((params: Params) => {
this.eventId = params['eventId'];
this.loadEvent();
});
}
registerToEvent(): void {
var input = new EntityDtoOfGuid();
input.id = this.event.id;
this._eventService.registerAsync(input)
.subscribe((result: EventRegisterOutput) => {
abp.notify.success('Successfully registered to event. Your registration id: ' + result.registrationId + ".");
this.loadEvent();
});
};
cancelRegistrationFromEvent(): void {
var input = new EntityDtoOfGuid();
input.id = this.event.id;
this._eventService.cancelRegistrationAsync(input)
.subscribe(() => {
abp.notify.info('Canceled your registration.');
this.loadEvent();
});
};
cancelEvent(): void {
var input = new EntityDtoOfGuid();
input.id = this.event.id;
this._eventService.cancelAsync(input)
.subscribe(() => {
abp.notify.info('Canceled the event.');
this.backToEventsPage();
});
};
isRegistered(): boolean {
return _.some(this.event.registrations, { userId: abp.session.userId });
};
isEventCreator(): boolean {
return this.event.creatorUserId === abp.session.userId;
};
loadEvent() {
this._eventService.getDetailAsync(this.eventId)
.subscribe((result: EventDetailOutput) => {
this.event = result;
});
}
backToEventsPage() {
this._router.navigate(['app/events']);
};
}
We simply use event application service to perform actions.
Top menu is automatically created by ABP template. We define menu items in sidebar-nav.component.ts
class:
@Component({
templateUrl: './sidebar-nav.component.html',
selector: 'sidebar-nav',
encapsulation: ViewEncapsulation.None
})
export class SideBarNavComponent extends AppComponentBase {
menuItems: MenuItem[] = [
new MenuItem(this.l("HomePage"), "", "home", "/app/home"),
new MenuItem(this.l("Tenants"), "Pages.Tenants", "business", "/app/tenants"),
new MenuItem(this.l("Users"), "Pages.Users", "people", "/app/users"),
new MenuItem(this.l("Roles"), "Pages.Roles", "local_offer", "/app/roles"),
new MenuItem(this.l("Events"), "Pages.Events", "event", "/app/events"),
new MenuItem(this.l("About"), "", "info", "/app/about"),
...
Defining the menu only shows it on the page. Angular has its own route system. Routes are defined in app-routing-module.ts as shown below:
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { AppRouteGuard } from '@shared/auth/auth-route-guard';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { UsersComponent } from './users/users.component';
import { TenantsComponent } from './tenants/tenants.component';
import { RolesComponent } from 'app/roles/roles.component';
import { EventsComponent } from 'app/events/events.component';
import { EventDetailComponent } from 'app/events/event-detail/event-detail.component';
@NgModule({
imports: [
RouterModule.forChild([
{
path: '',
component: AppComponent,
children: [
{ path: 'home', component: HomeComponent, canActivate: [AppRouteGuard] },
{ path: 'users', component: UsersComponent, data: { permission: 'Pages.Users' }, canActivate: [AppRouteGuard] },
{ path: 'roles', component: RolesComponent, data: { permission: 'Pages.Roles' }, canActivate: [AppRouteGuard] },
{ path: 'tenants', component: TenantsComponent, data: { permission: 'Pages.Tenants' }, canActivate: [AppRouteGuard] },
{ path: 'events', component: EventsComponent, data: { permission: 'Pages.Events' }, canActivate: [AppRouteGuard] },
{ path: 'events/:eventId', component: EventDetailComponent },
{ path: 'about', component: AboutComponent }
]
}
])
],
exports: [RouterModule]
})
export class AppRoutingModule { }
ASP.NET Boilerplate provides tools to make unit and integration tests easier. You can find all test code from source code of the project. Here, I will briefly explain basic tests. Solution includes EventAppService_Tests
class which tests the EventAppService
. See 2 tests from this class:
public class EventAppService_Tests : EventCloudTestBase
{
private readonly IEventAppService _eventAppService;
public EventAppService_Tests()
{
_eventAppService = Resolve<IEventAppService>();
}
[Fact]
public async Task Should_Get_Test_Events()
{
var output = await _eventAppService.GetListAsync(new GetEventListInput());
output.Items.Count.ShouldBe(1);
}
[Fact]
public async Task Should_Create_Event()
{
//Arrange
var eventTitle = Guid.NewGuid().ToString();
//Act
await _eventAppService.CreateAsync(new CreateEventInput
{
Title = eventTitle,
Description = "A description",
Date = Clock.Now.AddDays(2)
});
//Assert
UsingDbContext(context =>
{
context.Events.FirstOrDefault(e => e.Title == eventTitle).ShouldNotBe(null);
});
}
[Fact]
public async Task Should_Not_Create_Events_In_The_Past()
{
//Arrange
var eventTitle = Guid.NewGuid().ToString();
//Act
await Assert.ThrowsAsync<UserFriendlyException>(async () =>
{
await _eventAppService.CreateAsync(new CreateEventInput
{
Title = eventTitle,
Description = "A description",
Date = Clock.Now.AddDays(-1)
});
});
}
[Fact]
public async Task Should_Cancel_Event()
{
//Act
await _eventAppService.CancelAsync(new EntityDto<Guid>(GetTestEvent().Id));
//Assert
GetTestEvent().IsCancelled.ShouldBeTrue();
}
[Fact]
public async Task Should_Register_To_Events()
{
//Arrange
var testEvent = GetTestEvent();
//Act
var output = await _eventAppService.RegisterAsync(new EntityDto<Guid>(testEvent.Id));
//Assert
output.RegistrationId.ShouldBeGreaterThan(0);
UsingDbContext(context =>
{
var currentUserId = AbpSession.GetUserId();
var registration = context.EventRegistrations.FirstOrDefault(r => r.EventId == testEvent.Id && r.UserId == currentUserId);
registration.ShouldNotBeNull();
});
}
[Fact]
public async Task Should_Cancel_Registration()
{
//Arrange
var currentUserId = AbpSession.GetUserId();
await UsingDbContext(async context =>
{
var testEvent = GetTestEvent(context);
var currentUser = await context.Users.SingleAsync(u => u.Id == currentUserId);
var testRegistration = await EventRegistration.CreateAsync(
testEvent,
currentUser,
Substitute.For<IEventRegistrationPolicy>()
);
context.EventRegistrations.Add(testRegistration);
});
//Act
await _eventAppService.CancelRegistrationAsync(new EntityDto<Guid>(GetTestEvent().Id));
//Assert
UsingDbContext(context =>
{
var testEvent = GetTestEvent(context);
var testRegistration = context.EventRegistrations.FirstOrDefault(r => r.EventId == testEvent.Id && r.UserId == currentUserId);
testRegistration.ShouldBeNull();
});
}
private Event GetTestEvent()
{
return UsingDbContext(context => GetTestEvent(context));
}
private static Event GetTestEvent(EventCloudDbContext context)
{
return context.Events.Single(e => e.Title == TestDataBuilder.TestEventTitle);
}
}
We use xUnit as test framework. In the first test, we simply create an event and check database if it's in there. In the second test, we intentionally trying to create an event in the past. Since our business rule don't allow it, we should get an exception here.
With such tests, we tested everything starting from application service including all aspects of ASP.NET Boilerplate (like validation, unit of work and so on).
If you want to consume APIs/application services from a mobile application, you can use the token based authentication mechanism just like we do for the Angular client. The startup template includes the JwtBearer token authentication infrastructure.
We will use Postman (a chrome extension) to demonstrate requests and responses.
Just send a POST request to http://localhost:21021/api/TokenAuth/Authenticate with the Context-Type="application/json" header as shown below:
We sent a JSON request body includes tenancyName, userNameOrEmailAddress and password. tenancyName is not required for host users. As seen above, result property of returning JSON contains the token. We can save it and use for next requests.
After authenticate and get the token, we can use it to call any authorized action. All application services are available to be used remotely. For example, we can use the User service to get a list of users:
Just made a GET request to http://localhost:21021/api/services/app/user/getAll with Content-Type="application/json" and Authorization="Bearer [your-auth-token]". All functionality available on UI is also available as API.
Almost all operations available on UI also available as Web API (since UI uses the same Web API) and can be consumed easily.
You can get the latest source code here Event Cloud Source


TorrentViz was written by Don Kirkby and released under the MIT license.
jQuery, jQueryUI, jQueryCanvas, and jQuery UI Touch Punch all released under the MIT license.
TorrentViz was written by Don Kirkby and released under the MIT license.
jQuery, jQueryUI, jQueryCanvas, and jQuery UI Touch Punch all released under the MIT license.


| Logistics | ||
| Total Weight | 11% | |
| Sub-Weightages | ||
| Highways/Motorways | 3.1500% | 30% |
| In KMs | ||
| Within 2 | 100% | 3.150% |
| 2-5 | 75% | 2.36250% |
| 5-8 | 50% | 1.5750% |
| 8-10 | 25% | 0.78750% |
| above 10 | 0% | 0.0000% |
| Railroad/Station | 1.5750% | 15% |
| within 5 KMs | 100% | 1.575% |
| 5-15 | 75% | 1.18125% |
| 15-30 | 50% | 0.7875% |
| 30-45 | 25% | 0.39375% |
| above 45 | 0% | 0.0000% |
| Airport | 1.050% | 10% |
| Commercial/Logistics | ||
| within 25 | 100% | 1.050% |
| 25-40 | 75% | 0.78750% |
| 40-60 | 50% | 0.5250% |
| 60-100 | 25% | 0.26250% |
| above 100 | 0% | 0.0000% |
| Trucking Stations/Interchanges | 1.050% | 10% |
| 0-5 | 100% | 1.050% |
| 5-8 | 75% | 0.78750% |
| 8-15 | 50% | 0.5250% |
| 15-25 | 25% | 0.26250% |
| above 25 | 0% | 0.0000% |
| Shipping Cost/DryPort | 1.5750% | 15% |
| within 20 | 100% | 1.5750% |
| 20-40 | 75% | 1.181250% |
| 40-60 | 50% | 0.78750% |
| 60-100 | 25% | 0.393750% |
| above 100 | 0% | 0.0000% |
| Warehouse/Storage(in Dist) | 1.0500% | 10% |
| Highly available | 100% | 1.050% |
| Medium Available | 75% | 0.78750% |
| Available | 50% | 0.5250% |
| Low Available | 25% | 0.26250% |
| No Available | 0% | 0.0000% |
| Transport Time | 1.050% | 10% |
| Carpted Road/Double with limited traffic | 100% | 1.050% |
| Carpted with Heavy Traffic | 75% | 0.78750% |
| Non Carpted with low Traffic | 50% | 0.5250% |
| Non Carpted with heavy Traffic | 25% | 0.26250% |
| No Proper Road/Connectivity | 0% | 0.000% |
| Labor | ||
| Total Weight | 12% | |
| Sub-Weightages | ||
| Labor Proximity | 6.150% | 50% |
| (0.3 million inhabitants in KMs) | ||
| within 2 | 100% | 6.150% |
| 2-5 | 75% | 4.61250% |
| 5-8 | 50% | 3.0750% |
| 8-10 | 25% | 1.53750% |
| above 10 | 0% | 0.0000% |
| Labor Literacy level | 1.230% | 10% |
| Literacy rate | ||
| Above average | 100% | 1.230% |
| average | 75% | 0.92250% |
| below average | 50% | 0.6150% |
| low | 25% | 0.30750% |
| very low | 0% | 0.0000% |
| Skilled Labor | 3.075% | 25% |
| (Technical Institutes Turnover as % of Pop) | ||
| Above average | 100% | 3.0750% |
| average | 75% | 2.306250% |
| below average | 50% | 1.53750% |
| low | 25% | 0.768750% |
| very low | 0% | 0.000% |
| Labor Cost | 1.85% | 15% |
| (Wages data/LFS) | ||
| Highly available at MWR | 100% | 1.8450% |
| Available at MWR | 75% | 1.383750% |
| Medium available @ MWR | 50% | 0.92250% |
| Low available at MWR | 25% | 0.461250% |
| Very Low available @ MWR | 0% | 0.000% |
| Raw Material | ||
| Total Weight | 15% | |
| Sub-Weightages | ||
| Material Availability | 7.50% | 50% |
| Agri and Minerals hub within the area | ||
| Highly available/Rich | 100% | 7.50% |
| Available in the area | 75% | 5.6250% |
| Medium available | 50% | 3.750% |
| Low available | 25% | 1.8750% |
| Very Low availablity | 0% | 0.00% |
| Material Proximity | 4.50% | 30% |
| (Access of hub/Market in KMs) | ||
| Within 10 | 100% | 4.50% |
| 10-20 | 75% | 3.3750% |
| 20-30 | 50% | 2.250% |
| 30-40 | 25% | 1.1250% |
| above 40 | 0% | 0.00% |
| Components/Supplies | 3.00% | 20% |
| Divisional Headquater/Major City distance in KMs | ||
| within 25 | 100% | 3.00% |
| 25-60 | 75% | 2.2500% |
| 60-100 | 50% | 1.500% |
| 100-200 | 25% | 0.7500% |
| above 200 | 0% | 0.00% |
| Markets | ||
| Total Weight | 12% | |
| Sub-Weightages | ||
| Existing Consumer Market | 3.075% | 25% |
| 3 million Pop. Market distance in KMs | ||
| Within 10 | 100% | 3.08% |
| 10-20 | 75% | 2.3063% |
| 20-30 | 50% | 1.538% |
| 30-40 | 25% | 0.7688% |
| above 40 | 0% | 0.00% |
| Industry Concentration | 5.535% | 45% |
| Other Industrial Cluster/Zone in KMs | ||
| within 5 | 100% | 5.54% |
| 5-10 | 75% | 4.1513% |
| 10-15 | 50% | 2.768% |
| 15-25 | 25% | 1.3838% |
| above 25 | 0% | 0.00% |
| Population Growth | 3.69% | 30% |
| Adjacent City Growth | ||
| Above average | 100% | 3.69% |
| average | 75% | 2.7675% |
| below average | 50% | 1.845% |
| low | 25% | 0.9225% |
| very low | 0% | 0.00% |
| industrial site | ||
| Total Weight | 9% | |
| Sub-Weightages | ||
| Land Cost | 2.724% | 30% |
| per Acre Cost | ||
| very low | 100% | 2.724% |
| low | 75% | 2.04300% |
| medium | 50% | 1.3620% |
| high | 25% | 0.68100% |
| very high | 0% | 0.0000% |
| Developed Park/Zone | 3.63% | 40% |
| Ies/SEZs status | ||
| Highly developed | 100% | 3.632% |
| developed | 75% | 2.72400% |
| partly developed | 50% | 1.8160% |
| under developed | 25% | 0.90800% |
| not developed | 0% | 0.0000% |
| Quality (Risks involved) | 2.724% | 30% |
| Soil quality/Conditions | ||
| Highly suitable for mega constructions | 100% | 2.724% |
| suitable for mega constructions | 75% | 2.04300% |
| low suitable for mega constructions | 50% | 1.3620% |
| very low suitable for mega constructions | 25% | 0.68100% |
| not suitable | 0% | 0.0000% |
| Utilities | ||
| Total Weight | 10% | |
| Sub-Weightages | ||
| Gas Network | 1.000% | 10% |
| Pipeline access in KMs | ||
| within 1 | 100% | 1.000% |
| 2-5 | 75% | 0.75000% |
| 5-10 | 50% | 0.5000% |
| 10-15 | 25% | 0.25000% |
| above 15 | 0% | 0.000% |
| Electricity Network | 1.0000% | 10% |
| Elecricity Grid distance in KMs | ||
| within 1 | 100% | 1.000% |
| 2-5 | 75% | 0.75000% |
| 5-10 | 50% | 0.5000% |
| 10-15 | 25% | 0.25000% |
| above 15 | 0% | 0.0000% |
| Water Supply | 1.0000% | 10% |
| Water Supply network/or Surface water | ||
| within 1 | 100% | 1.000% |
| 2-5 | 75% | 0.75000% |
| 5-10 | 50% | 0.5000% |
| 10-15 | 25% | 0.25000% |
| above 15 | 0% | 0.0000% |
| Sewerage Facility | 2.0000% | 20% |
| Drainages/Sanitation access in KMs | ||
| within 1 | 100% | 2.00% |
| 2-5 | 75% | 1.5000% |
| 5-10 | 50% | 1.000% |
| 10-15 | 25% | 0.5000% |
| above 15 | 0% | 0.0000% |
| Electricity Supply Quality | 2.0000% | 20% |
| Load shedding frequency | ||
| No Loadshedding | 100% | 2.00% |
| rare trippings | 75% | 1.5000% |
| Occasional LS | 50% | 1.000% |
| Frequent LS | 25% | 0.5000% |
| Heavy LS | 0% | 0.0000% |
| Gas Supply Quality | 1.0000% | 10% |
| Gas supply disconnection | ||
| No curtailment | 100% | 1.00% |
| rare pressure issue | 75% | 0.7500% |
| Seasonal disruptions | 50% | 0.500% |
| Occasional disruptions | 25% | 0.2500% |
| Regular Intreptions | 0% | 0.0000% |
| Ground Water Quality | 1.5000% | 15% |
| Ground water access & quality | ||
| Very Good | 100% | 1.50% |
| Good | 75% | 1.1250% |
| Not Good | 50% | 0.750% |
| Not Useable | 25% | 0.3750% |
| Not extractable | 0% | 0.0000% |
| Fuel Station | 0.5000% | 5% |
| Filling station distance in KMs | ||
| within 5 | 100% | 0.50% |
| 5-10 | 75% | 0.3750% |
| 10-20 | 50% | 0.250% |
| 20-30 | 25% | 0.1250% |
| above 30 | 0% | 0.0000% |
| institutations | ||
| Total Weight | 12% | |
| Sub-Weightages | ||
| Public Sector Dept./Organizations | 4.68% | 40% |
| Distance from DHQ in KMs | ||
| Within 10 | 100% | 4.680% |
| 10-20 | 75% | 3.510% |
| 20-30 | 50% | 2.340% |
| 30-50 | 25% | 1.170% |
| above 50 | 0% | 0.00% |
| Banks/DFIs | 1.17% | 10% |
| Availability of Bank Branches in KMs | ||
| Within 10 | 100% | 1.170% |
| 10-20 | 75% | 0.878% |
| 20-30 | 50% | 0.585% |
| 30-40 | 25% | 0.293% |
| above 40 | 0% | 0.00% |
| Business Association/Chamber | 0.585% | 5% |
| Business Associations/Chambers offices in KMs | ||
| Within 10 | 100% | 0.585% |
| 10-20 | 75% | 0.439% |
| 20-30 | 50% | 0.293% |
| 30-40 | 25% | 0.146% |
| above 40 | 0% | 0.00% |
| Training Centre/R&D Centre, Labortories | 3.510% | 30% |
| Tevta/Labortaory Centres in KMs | ||
| Within 10 | 100% | 3.510% |
| 10-20 | 75% | 2.633% |
| 20-30 | 50% | 1.755% |
| 30-50 | 25% | 0.878% |
| above 50 | 0% | 0.00% |
| Police Station | 1.76% | 15% |
| Police Station in KMs | ||
| Within 10 | 100% | 1.755% |
| 10-20 | 75% | 1.316% |
| 20-30 | 50% | 0.878% |
| 30-40 | 25% | 0.439% |
| above 40 | 0% | 0.00% |
| Community | ||
| Total Weight | 7% | |
| Sub-Weightages | ||
| Education level | 2.190% | 30% |
| Number of university/Colleges in 50 KMs | ||
| Above 5 | 100% | 2.190% |
| 4 | 75% | 1.64250% |
| 3 | 50% | 1.0950% |
| 2 | 25% | 0.54750% |
| below 2 | 0% | 0.000% |
| Hospital | 2.92% | 40% |
| General Hospital Distance in KMs | ||
| within 10 | 100% | 2.920% |
| 10-25 | 75% | 2.19000% |
| 25-40 | 50% | 1.4600% |
| 40-50 | 25% | 0.73000% |
| above 50 | 0% | 0.000% |
| Hotel/Motel | 2.190% | 30% |
| 3 star hotel/Guest house in KMs | ||
| within 5 | 100% | 2.190% |
| 5-10 | 75% | 1.64250% |
| 10-20 | 50% | 1.0950% |
| 20-40 | 25% | 0.54750% |
| above 40 | 0% | 0.000% |
| env | ||
| Total Weight | 12% | |
| Sub-Weightages | ||
| Rainfall | 0.613% | 5% |
| ( Frequency and Intensity) | ||
| Normal | 100% | 0.613% |
| Heavy/Low | 50% | 0.3065% |
| Abnormal/No | 25% | 0.15325% |
| Humidity | 0.6% | 5.0% |
| Level of Humid | ||
| Normal | 100% | 0.613% |
| Medium | 50% | 0.3065% |
| Extreme | 25% | 0.15325% |
| Temprature | 1.2260% | 10% |
| Temprature level | ||
| Normal | 100% | 1.226% |
| High | 75% | 0.9195% |
| Very High | 50% | 0.61300% |
| Extreme | 25% | 0.30650% |
| Floods | 3.7% | 30.0% |
| Floods frequency | ||
| No floods | 100% | 3.678% |
| Rare | 75% | 2.7585% |
| Occasional | 50% | 1.83900% |
| Normal | 25% | 0.91950% |
| Floody | 0% | 0.0% |
| Polution | 3.6780% | 30% |
| Existing polution level in the area | ||
| No Polution | 100% | 3.678% |
| Low Polution | 50% | 1.8390% |
| Highl Polution | 0% | 0.00000% |
| Ecology | 2.452% | 20% |
| Ecology level | ||
| Green | 100% | 2.452% |
| Yellow | 50% | 1.2260% |
| Red | 0% | 0.00000% |


{% extends "club/base.html" %} {% block content %} Your Account {% if user.member.verification_code and not user.member.verified %} Verify Your Account {% for message in messages %} {{ message|safe }} {% endfor %} Your verification code was sent to {{ user.member.school_email }}. {% if school_name_short == 'RPI' %} Check Spam | {% endif %} Resend email | Change email Verify {% else %} {% include 'club/includes/messages.html' %} {% endif %} {% csrf_token %} {% include "../includes/form.html" %} {% endblock %}
Your verification code was sent to {{ user.member.school_email }}. {% if school_name_short == 'RPI' %} Check Spam | {% endif %} Resend email | Change email


The international University Rover Challenge (URC) was RSX’s first mission. Our mission was dubbed MRSX-1, and on a shoe-string budget, we built our pride and glory, Origin, and even won first place at CSii’s Innovation Nation Robotics Competition.
Currently, we are working very hard on our supremely ambitious next-gen rover. The aim of the URC is to design and build the next generation of Mars rovers that will one day work alongside human explorers in the field. The competition takes place at the Mars Desert Research Station in Hanksville, Utah, and is organized by the Mars Society every year. Teams from several countries design and build rovers to compete in 5 tasks, reflective of what may be required of a rover on a real Martian expedition.
We traveled down to the barren, scorching, and desolate desert in Utah to test our rover (and unexpectedly, ourselves) in an array of the most challenging tasks we have ever experienced. Not only was the year-long experience an incredible engineering lesson and personal development opportunity unlike anything we could have experienced in a classroom, the insight we gained into the uncertainty, excitement, and mission critical nature encapsulated in a simulated space mission is sure to be called upon when we participate in real ones – likely not to long from now. The five tasks include the following:
Sample Return Task: Teams need to select multiple sites of biological interest within a 0.5km radius of their base, and collect and return sub-surface samples. Astronaut Assistance Task: Given approximate GPS coordinates, teams will be required to deliver multiple supply containers to simulated astronauts in the field as quickly as possible. Equipment Servicing Task: The rover must perform tasks such as measuring voltage across exposed terminals, cleaning a solar panel, reading meters, pushing buttons & flipping switches. Terrain Traversing Task: Rovers will be required to traverse a variety of difficult terrains as part of an engineering field test of the ruggedness and route-finding ability of the rovers. Presentation Task: Teams will prepare and deliver a presentation to the URC judges describing their team, rover design and functionality.
The CanSat competition is a design-build-fly competition held in Burkett, Texas, that provides teams with an opportunity to experience the design life-cycle of an aerospace system. The CanSat competition is designed to reflect a typical aerospace program on a small scale and includes all aspects of an aerospace program from the preliminary design review to post mission review. 2014-2015 will mark the first year that RSX prepares for, and participates in the CanSat competition.
The 2015 mission simulates a Science Vehicle traveling through a planetary atmosphere sampling the atmospheric composition during descent. The overall CanSat system is composed of two primary components, a Science Vehicle and a re-entry Container that protects the vehicle during ascent, “near-apogee” deployment from the rocket and initial re-entry/descent.
When initially deployed from the rocket, the re-entry Container shall descend via parachute with the Science Vehicle secured within the Container. At any desired point after the initial deployment from the rocket the Container and Science Vehicle shall separate and the Science Vehicle will begin normal operations. When the Science Vehicle is released from the Container, it shall use passive helicopter/auto-gyro recovery method reduce its descent rate to less than 10 meters/second. The Science Vehicle must stabilize and descend properly at a minimum altitude of 300 meters.
During descent, the Science Vehicle shall record video in the nadir (Earth pointing) direction until it lands. The video camera must be stabilized in real time during descent so that the video image of the ground is not spinning. The Science Vehicle shall collect telemetry data during descent which includes; altitude based on barometric air pressure, outside air temperature, inside temperature, flight software state, stabilization parameters, battery voltage, and bonus telemetry. The data shall be transmitted at a 1 Hz rate to a ground station. The Science Vehicle shall hold one large raw hen’s egg and protect it from breaking during the mission.
Needless to say, sending something into orbit is still a bit of a challenge for us, but we’re determined to stay true to our motto. So what’s the next best thing? How does sending our own payload to over 100,000 feet sound? That’s exactly what the HAB (High Altitude Balloon) project is all about. What started as a little side project during the summer of 2014 has turned into an incredibly ambitious and exciting endeavour.
Our ultimate goal is to deploy an optimized glider from an incredibly high altitude, and have it automatically return to a preset location. Why? Well, the challenging thing about high altitude balloon payloads is that they end up landing hundreds of kilometers away from their starting point. And more often than not, a full blown orbital satellite mission isn’t necessary to do useful science. How cool would it be to send off your experiment, and have it land within reach a couple of hours later?
After launching our first successful mission already (PPOPS-1), we learned a lot about the science, engineering, and logistics behind balloon missions. We’re building everything from scratch – from our ground station chase/telemetry software and the payload’s firmware to electronics integrations (including basic atmospheric sensors) and mechanical actuation mechanisms to test performannce in trying high altitude environments.
Got an experiment you’d like to fly on our next mission? Get in touch with us, we’d love to collaborate.
SEEK! is an annual competition organized by Robotics for Space Exploration dedicated to helping students gain practical experience with space engineering and technologies.
SEEK! is planned to be one full day of learning, building, and competing. Teams of 3 students from any discipline or year are invited to sign up (no prior experience required/necessary/expected). We recommend assigning three roles: mechanical, electrical, programming. Past competitions have challenged competitors to build robots to perform a simulated search-and-rescue mission, navigate hazardous terrain and retrieve precious minerals.
Whether you are a student wanting to join, a potential sponsor, or martian, we'd love to hear from you! The best way to get in touch is email


| densityPlot {car} | R Documentation |
densityPlot
contructs and graphs nonparametric density estimates, possibly conditioned on a factor, using the standard R density
function or by default adaptiveKernel
, which computes an adaptive kernel density estimate.
depan
provides the Epanechnikov kernel and dbiwt
provides the biweight kernel.
densityPlot(x, ...) ## Default S3 method: densityPlot(x, g, method=c("adaptive", "kernel"), bw=if (method == "adaptive") bw.nrd0 else "SJ", adjust=1, kernel, xlim, ylim, normalize=FALSE, xlab=deparse(substitute(x)), ylab="Density", main="", col=carPalette(), lty=seq_along(col), lwd=2, grid=TRUE, legend=TRUE, show.bw=FALSE, rug=TRUE, ...) ## S3 method for class 'formula' densityPlot(formula, data=NULL, subset, na.action=NULL, xlab, ylab, main="", legend=TRUE, ...) adaptiveKernel(x, kernel=dnorm, bw=bw.nrd0, adjust=1.0, n=500, from, to, cut=3, na.rm=TRUE) depan(x) dbiwt(x)
x |
a numeric variable, the density of which is estimated; for
|
g |
an optional factor to divide the data. |
formula |
an R model formula, of the form |
data |
an optional data frame containing the data. |
subset |
an optional vector defining a subset of the data. |
na.action |
a function to handle missing values; defaults to the value of the R |
method |
either |
bw |
the geometric mean bandwidth for the adaptive-kernel or bandwidth of the kernel density estimate(s). Must be a numerical value
or a function to compute the bandwidth (default |
adjust |
a multiplicative adjustment factor for the bandwidth; the default, |
kernel |
for |
xlim, ylim |
axis limits; if missing, determined from the range of x-values at which the densities are estimated and the estimated densities. |
normalize |
if |
xlab |
label for the horizontal-axis; defaults to the name of the variable |
ylab |
label for the vertical axis; defaults to |
main |
plot title; default is empty. |
col |
vector of colors for the density estimate(s); defaults to the color |
lty |
vector of line types for the density estimate(s); defaults to the successive integers, starting at 1. |
lwd |
line width for the density estimate(s); defaults to 2. |
grid |
if |
legend |
a list of up to two named elements: |
n |
number of equally spaced points at which the adaptive-kernel estimator is evaluated; the default is |
from, to, cut |
the range over which the density estimate is computed; the default, if missing, is |
na.rm |
remove missing values from |
show.bw |
if |
rug |
if |
... |
arguments to be passed down. |
If you use a different kernel function than the default dnorm
that has a
standard deviation different from 1 along with an automatic rule
like the default function bw.nrd0
, you can attach an attribute to the kernel
function named "scale"
that gives its standard deviation. This is true for
the two supplied kernels, depan
and dbiwt
densityPlot
invisibly returns the "density"
object computed (or list of "density"
objects) and draws a graph.
adaptiveKernel
returns an object of class "density"
(see density)
.
John Fox<EMAIL_ADDRESS>Fox, J. and Weisberg, S. (2019) An R Companion to Applied Regression, Third Edition, Sage.
W. N. Venables and B. D. Ripley (2002) Modern Applied Statistics with S. New York: Springer.
B.W. Silverman (1986) Density Estimation for Statistics and Data Analysis. London: Chapman and Hall.
densityPlot(~ income, show.bw=TRUE, method="kernel", data=Prestige) densityPlot(~ income, show.bw=TRUE, data=Prestige) densityPlot(~ income, from=0, normalize=TRUE, show.bw=TRUE, data=Prestige) densityPlot(income ~ type, data=Prestige) densityPlot(~ income, show.bw=TRUE, method="kernel", data=Prestige) densityPlot(~ income, show.bw=TRUE, data=Prestige) densityPlot(~ income, from=0, normalize=TRUE, show.bw=TRUE, data=Prestige) densityPlot(income ~ type, kernel=depan, data=Prestige) densityPlot(income ~ type, kernel=depan, legend=list(location="top"), data=Prestige) plot(adaptiveKernel(UN$infantMortality, from=0, adjust=0.75), col="magenta") lines(density(na.omit(UN$infantMortality), from=0, adjust=0.75), col="blue") rug(UN$infantMortality, col="cyan") legend("topright", col=c("magenta", "blue"), lty=1, legend=c("adaptive kernel", "kernel"), inset=0.02) plot(adaptiveKernel(UN$infantMortality, from=0, adjust=0.75), col="magenta") lines(density(na.omit(UN$infantMortality), from=0, adjust=0.75), col="blue") rug(UN$infantMortality, col="cyan") legend("topright", col=c("magenta", "blue"), lty=1, legend=c("adaptive kernel", "kernel"), inset=0.02)


Basic Youtube API implementation with a spoon a Material Design on top. The video stops automatically after 6''.


The basic Sphinx documentation for testing¶
Sphinx is a tool that makes it easy to create intelligent and beautiful documentation for Python projects (or other documents consisting of multiple reStructuredText sources), written by Georg Brandl. It was originally created for the new Python documentation, and has excellent facilities for Python project documentation, but C/C++ is supported as well, and more languages are planned.
Sphinx uses reStructuredText as its markup language, and many of its strengths come from the power and straightforwardness of reStructuredText and its parsing and translating suite, the Docutils.
features¶
Among its features are the following:
Output formats: HTML (including derivative formats such as HTML Help, Epub and Qt Help), plain text, manual pages and LaTeX or direct PDF output using rst2pdf
Extensive cross-references: semantic markup and automatic links for functions, classes, glossary terms and similar pieces of information
Hierarchical structure: easy definition of a document tree, with automatic links to siblings, parents and children
Automatic indices: general index as well as a module index
Code handling: automatic highlighting using the Pygments highlighter
Flexible HTML output using the Jinja 2 templating engine
Various extensions are available, e.g. for automatic testing of snippets and inclusion of appropriately formatted docstrings
Setuptools integration


Head to the forums, then click your username on the top right of the page. Once you do this multiple options will pop up, press "Change username", input your new username in the textbox, and then press save. This can be done once every thirty days.
First ensure that your email is typed correctly, if it is then head to your email and be sure to check your spam/trash folder. If you still don't see it, head to discord for additional support.
The only accepted payment method for donations currently is paypal, you can also use a credit/debit through paypal to donate without creating an account. If paypal doesn't process your payment, you will have to contact paypal support.
No, all tiers are equal priority and will be reviewed prior to normal applications. The title you recieve does vary based on the donation.
Please submit a support ticket here.
The status of applications is located here!
Unfortuntely we do not provide additional detail besides the detail you recieve in your alerts.
First ensure that your discord and forum profile are connected, if you still cant submit a normal application you may have hit the 5 application cap and will need to donate to submit a donator application. If you have donated and cannot submit a donator application, first ensure that your donation has gone through paypal, if the funding source was a bank/echeck, paypal takes a while to process. This time can be found on the summary of the paypal transaction. If your paypal transaction has gone through and you still don't have donator tags, please create a support ticket and include a screenshot of your paypal transaction reciept as well as the transaction id. Support tickets can be created here.
The estimated time frame changes constantly, it is impossible to know. Asking this question in the support channels/as a support ticket will result in your application taking longer to process.
Once your application is reviewed it will be moved to closed applications, you will recieve an alert of why it was moved, which will state whether your app was denied/approved, if denied why it was denied. The alerts can be found here. You must be logged in to view them, and they dissapear after a while.
The keybinds are located here.
Unfortuntely once you delete a character that name can no long be used. There is no way to get around this.
We do not use ingame voip, you must have that off and then follow the steps here.
In order to get priority you have to demonstrate good roleplay while on the server. Asking for priority will not result in priority being recieved.


SFOperator
public class SFOperator
Undocumented
-
Undocumented
Declaration
Swift
public static let START_ADDRS_OFFSET: Int
-
Undocumented
Declaration
Swift
public static let END_ADDRS_OFFSET: Int
-
Undocumented
Declaration
Swift
public static let START_LOOP_ADDRS_OFFSET: Int
-
Undocumented
Declaration
Swift
public static let END_LOOP_ADDRS_OFFSET: Int
-
Undocumented
Declaration
Swift
public static let START_ADDRS_COARSE_OFFSET: Int
-
Undocumented
Declaration
Swift
public static let MOD_LFO_TO_PITCH: Int
-
Undocumented
Declaration
Swift
public static let VIB_LFO_TO_PITCH: Int
-
Undocumented
Declaration
Swift
public static let MOD_ENV_TO_PITCH: Int
-
Undocumented
Declaration
Swift
public static let INITIAL_FILTER_FC: Int
-
Undocumented
Declaration
Swift
public static let INITIAL_FILTER_Q: Int
-
Undocumented
Declaration
Swift
public static let MOD_LFO_TO_FILTER_FC: Int
-
Undocumented
Declaration
Swift
public static let MOD_ENV_TO_FILTER_FC: Int
-
Undocumented
Declaration
Swift
public static let END_ADDRS_COARSE_OFFSET: Int
-
Undocumented
Declaration
Swift
public static let MOD_LFO_TO_RecordLUME: Int
-
Undocumented
Declaration
Swift
public static let UNUSED_1: Int
-
Undocumented
Declaration
Swift
public static let CHORUS_EFFECTS_SEND: Int
-
Undocumented
Declaration
Swift
public static let REVERB_EFFECTS_SEND: Int
-
Undocumented
Declaration
Swift
public static let PAN: Int
-
Undocumented
Declaration
Swift
public static let UNUSED_2: Int
-
Undocumented
Declaration
Swift
public static let UNUSED_3: Int
-
Undocumented
Declaration
Swift
public static let UNUSED_4: Int
-
Undocumented
Declaration
Swift
public static let DELAY_MOD_LFO: Int
-
Undocumented
Declaration
Swift
public static let FREQ_MOD_LFO: Int
-
Undocumented
Declaration
Swift
public static let DELAY_VIB_LFO: Int
-
Undocumented
Declaration
Swift
public static let FREQ_VIB_LFO: Int
-
Undocumented
Declaration
Swift
public static let DELAY_MOD_ENV: Int
-
Undocumented
Declaration
Swift
public static let ATTACK_MOD_ENV: Int
-
Undocumented
Declaration
Swift
public static let HOLD_MOD_ENV: Int
-
Undocumented
Declaration
Swift
public static let DECAY_MOD_ENV: Int
-
Undocumented
Declaration
Swift
public static let SUSTAIN_MOD_ENV: Int
-
Undocumented
Declaration
Swift
public static let RELEASE_MOD_ENV: Int
-
Undocumented
Declaration
Swift
public static let KEYNUM_TO_MOD_ENV_HOLD: Int
-
Undocumented
Declaration
Swift
public static let KEYNUM_TO_MOD_ENV_DECAY: Int
-
Undocumented
Declaration
Swift
public static let DELAY_RecordL_ENV: Int
-
Undocumented
Declaration
Swift
public static let ATTACK_RecordL_ENV: Int
-
Undocumented
Declaration
Swift
public static let HOLD_RecordL_ENV: Int
-
Undocumented
Declaration
Swift
public static let DECAY_RecordL_ENV: Int
-
Undocumented
Declaration
Swift
public static let SUSTAIN_RecordL_ENV: Int
-
Undocumented
Declaration
Swift
public static let RELEASE_RecordL_ENV: Int
-
Undocumented
Declaration
Swift
public static let KEYNUM_TO_RecordL_ENV_HOLD: Int
-
Undocumented
Declaration
Swift
public static let KEYNUM_TO_RecordL_ENV_DECAY: Int
-
Undocumented
Declaration
Swift
public static let INSTRUMENT: Int
-
Undocumented
Declaration
Swift
public static let RESERVED_1: Int
-
Undocumented
Declaration
Swift
public static let KEY_RANGE: Int
-
Undocumented
Declaration
Swift
public static let VEL_RANGE: Int
-
Undocumented
Declaration
Swift
public static let START_LOOP_ADDRS_COARSE_OFFSET: Int
-
Undocumented
Declaration
Swift
public static let KEY_NUM: Int
-
Undocumented
Declaration
Swift
public static let VELOCITY: Int
-
Undocumented
Declaration
Swift
public static let INITIAL_ATTENUATION: Int
-
Undocumented
Declaration
Swift
public static let RESERVED_2: Int
-
Undocumented
Declaration
Swift
public static let END_LOOP_ADDRS_COARSE_OFFSET: Int
-
Undocumented
Declaration
Swift
public static let COARSE_TUNE: Int
-
Undocumented
Declaration
Swift
public static let FINE_TUNE: Int
-
Undocumented
Declaration
Swift
public static let SAMPLE_ID: Int
-
Undocumented
Declaration
Swift
public static let SAMPLE_MODES: Int
-
Undocumented
Declaration
Swift
public static let RESERVED3: Int
-
Undocumented
Declaration
Swift
public static let SCALE_TUNING: Int
-
Undocumented
Declaration
Swift
public static let EXCLUSIVE_CLASS: Int
-
Undocumented
Declaration
Swift
public static let OVERRIDING_ROOT_KEY: Int
-
Undocumented
Declaration
Swift
public static let UNUSED_5: Int
-
Undocumented
Declaration
Swift
public static let END_OPER: Int
-
Undocumented
Declaration
Swift
public static let NAMES: [String]
-
Undocumented
Declaration
Swift
public var id: Int
-
Undocumented
Declaration
Swift
public var amount: Int
-
Undocumented
Declaration
Swift
public var defaultValue: Int
-
Undocumented
Declaration
Swift
public var description: String


[#if shop?? && shop.shop_sign??]
[/#if]
[#if productCategory?? && productCategory?size>0]
商品分类
[#list productCategory as cate]
[/#list]
[/#if]
[#if indexProducts?? && indexProducts?size>0]
[#list indexProducts as product]
[/#list]
[/#if]
[#include "/includes/menus.html" /]
[#include "/includes/footer.html" /]


2 writes to Path
System.Management.Automation (2)
engine\InitialSessionState.cs (2)
666
Path
= path;
679
Path
= path;
2 references to Path
System.Management.Automation (2)
engine\InitialSessionState.cs (1)
689
SessionStateApplicationEntry entry = new SessionStateApplicationEntry(
Path
, Visibility);
engine\SessionState.cs (1)
277
this.Applications.Add(entry.
Path
);


{{ page.title }}
{{ content }}
Thank you for reading.
Post written by Kevin Olega” connect on twitter and facebook
--- layout: default ---
Thank you for reading.
Post written by Kevin Olega” connect on twitter and facebook


King Krule shares four new tracks and announces UK tour
Is the follow-up to 'The Ooz' incoming?
King Krule has shared four new tracks via a short film called ‘Hey World’ while announcing UK and European tour dates for 2020. See the clip below along with the full live schedule.
The English musician and producer – real name Archy Marshall – teamed up with director Charlotte Patmore for the new visual project, which was shared online earlier today (November 19).
‘Hey World’ features lo-fi film footage of King Krule performing solo with an acoustic guitar. He’s later seen trekking across the countryside, making his way to various outdoor locations in which to play the new songs.
The as-yet-unreleased cuts showcased in the new clip are ‘Perfecto Miserable’, ‘Alone, Omen 3’, ‘(Don’t Let The Dragon) Draag On’ and ‘Energy Fleets’. It’s not yet known whether these will feature on the artist’s upcoming fourth album, which will follow on from 2017’s ‘The Ooz‘.
The new material also comes alongside an announcement of live shows for next year. King Krule hits the road for a European tour in March, which will see him stop off in Dublin, Glasgow, Manchester, and London. Further dates will follow in the US across April.
Tickets will go on general sale this coming Friday (November 22) from 10 am.
See King Krule’s full 2020 tour dates below:
3 March – Brussels, A.B
4 March – Paris, L’Olympia
5 March – Amsterdam, Melkweg
7 March – Copenhagen, K.B Hallen
8 March – Berlin, Columbiahalle
19 March – Dublin, Olympia
21 March – Glasgow, Barrowland
22 March – Manchester, Albert Hall
24 March – London, O2 Academy Brixton
2 April – Dallas TX, House of Blues
3 April – Houston TX, White Oak Music Hall
4 April – Austin TX, Stubbs Waller Creek
7 April – Los Angeles CA, Hollywood Palladium
8 April – Oakland CA, Fox Theatre
10 April – Seattle WA, Showbox Sodo
11 April – Portland OR, Roseland Theatre
14 April – Minneapolis MN, First Avenue
15 April – Chicago IL, Riviera Theatre
17 April – Toronto ON, Queen Elizabeth Theatre
18 April – Montreal QC, Mtelus
19 April – Boston MA, House of Blues
21 April – Philadelphia PA, Union Transfer
22 April – Washington DC, 9.30 Club
24 April – Brooklyn NY, Kings Theatre
Back in September, King Krule joined forces with English rapper Jadasea on ‘Half-life’.


Classes | |
| class | FuncProfile |
| Profiler for a function (uses profile). Plus de détails... | |
| class | CProfileFuncProfile |
| Profiler for a function (uses cProfile). Plus de détails... | |
| class | HotShotFuncProfile |
| Profiler for a function (uses hotshot). Plus de détails... | |
| class | HotShotFuncCoverage |
| Coverage analysis for a function (uses _hotshot). Plus de détails... | |
| class | TraceFuncCoverage |
| Coverage analysis for a function (uses trace module). Plus de détails... | |
| class | FuncSource |
| Source code annotator for a function. Plus de détails... | |
| class | FuncTimer |
Fonctions | |
| def | profile |
Mark fn for profiling. Plus de détails... | |
| def | coverage |
Mark fn for line coverage analysis. Plus de détails... | |
| def | coverage_with_hotshot |
Mark fn for line coverage analysis. Plus de détails... | |
| def | timecall |
Wrap fn and print its execution time. Plus de détails... | |
| def | interact_ |
| def | run_ |
Variables | |
| string | __author__ = "Marius Gedminas (marius@gedmin.as)" |
| Profiling hooks. Plus de détails... | |
| string | __copyright__ = "Copyright 2004-2013 Marius Gedminas" |
| string | __license__ = "MIT" |
| string | __version__ = "1.7" |
| string | __date__ = "2013-10-16" |
| hotshot = None | |
| cProfile = None | |
| dictionary | AVAILABLE_PROFILERS = {} |
| list | __all__ = ['coverage', 'coverage_with_hotshot', 'profile', 'timecall'] |
| tuple | local = dict((name, globals()[name]) for name in __all__) |
| string | message |
| def src.util.profilehooks.coverage | ( | fn) |
Mark fn
for line coverage analysis.
Results will be printed to sys.stdout on program termination.
Usage::
def fn(...): ... fn = coverage(fn)
If you are using Python 2.4, you should be able to use the decorator syntax::
@coverage def fn(...): ...
| def src.util.profilehooks.coverage_with_hotshot | ( | fn) |
Mark fn
for line coverage analysis.
Uses the 'hotshot' module for fast coverage analysis.
BUG Produces inaccurate results.
See the docstring of coverage
for usage examples.
| def src.util.profilehooks.interact_ | ( | ) |
| def src.util.profilehooks.profile | ( | fn = None , |
|
skip = 0 , |
|||
filename = None , |
|||
immediate = False , |
|||
dirs = False , |
|||
sort = None , |
|||
entries = 40 , |
|||
profiler = ('cProfile', 'profile' , |
|||
| hotshot | |||
| ) |
Mark fn
for profiling.
If skip
is > 0, first skip
calls to fn
will not be profiled.
If immediate
is False, profiling results will be printed to sys.stdout on program termination. Otherwise results will be printed after each call.
If dirs
is False only the name of the file will be printed. Otherwise the full path is used.
sort
can be a list of sort keys (defaulting to ['cumulative', 'time', 'calls']). The following ones are recognized::
'calls' -- call count 'cumulative' -- cumulative time 'file' -- file name 'line' -- line number 'module' -- file name 'name' -- function name 'nfl' -- name/file/line 'pcalls' -- call count 'stdname' -- standard name 'time' -- internal time
entries
limits the output to the first N entries.
profiler
can be used to select the preferred profiler, or specify a sequence of them, in order of preference. The default is ('cProfile'. 'profile', 'hotshot').
If filename
is specified, the profile stats will be stored in the named file. You can load them pstats.Stats(filename).
Usage::
def fn(...): ... fn = profile(fn, skip=1)
If you are using Python 2.4, you should be able to use the decorator syntax::
@profile(skip=3) def fn(...): ...
or just ::
@profile def fn(...): ...
| def src.util.profilehooks.run_ | ( | ) |
| def src.util.profilehooks.timecall | ( | fn = None , |
|
immediate = True , |
|||
timer = time.time |
|||
| ) |
Wrap fn
and print its execution time.
Example::
@timecall def somefunc(x, y): time.sleep(x * y) somefunc(2, 3)
will print the time taken by somefunc on every call. If you want just a summary at program termination, use
@timecall(immediate=False)
You can also choose a timing method other than the default time.time()
, e.g.:
@timecall(timer=time.clock)
| list src.util.profilehooks.__all__ = ['coverage', 'coverage_with_hotshot', 'profile', 'timecall'] |
| string src.util.profilehooks.__author__ = "Marius Gedminas (marius@gedmin.as)" |
Profiling hooks.
This module contains a couple of decorators (profile
and coverage
) that can be used to wrap functions and/or methods to produce profiles and line coverage reports. There's a third convenient decorator (timecall
) that measures the duration of function execution without the extra profiling overhead.
Usage example (Python 2.4 or newer)::
from profilehooks import profile, coverage @profile # or @coverage def fn(n): if n < 2: return 1 else return n * fn(n-1) print(fn(42))
Or without imports, with some hack
$ python -m profilehooks yourmodule @profile # or @coverage def fn(n): if n < 2: return 1 else return n * fn(n-1) print(fn(42))
Usage example (Python 2.3 or older)::
from profilehooks import profile, coverage def fn(n): if n < 2: return 1 else return n * fn(n-1) # Now wrap that function in a decorator fn = profile(fn) # or coverage(fn) print fn(42)
Reports for all thusly decorated functions will be printed to sys.stdout on program termination. You can alternatively request for immediate reports for each call by passing immediate=True to the profile decorator.
There's also a decorator for printing the time to sys.stderr every time a function is called, when you just want to get a rough measure instead of a detailed (but costly) profile.
Caveats
A thread on python-dev convinced me that hotshot produces bogus numbers. See http://mail.python.org/pipermail/python-dev/2005-November/058264.html
I don't know what will happen if a decorated function will try to call another decorated function. All decorators probably need to explicitly support nested profiling (currently TraceFuncCoverage is the only one that supports this, while HotShotFuncProfile has support for recursive functions.)
Profiling with hotshot creates temporary files (*.prof for profiling, *.cprof for coverage) in the current directory. These files are not cleaned up. Exception: when you specify a filename to the profile decorator (to store the pstats.Stats object for later inspection), the temporary file will be the filename you specified with '.raw' appended at the end.
Coverage analysis with hotshot seems to miss some executions resulting in lower line counts and some lines errorneously marked as never executed. For this reason coverage analysis now uses trace.py which is slower, but more accurate.
Copyright (c) 2004–2012 Marius Gedminas mariu Copyright (c) 2007 Hanno Schlichting Copyright (c) 2008 Florian Schulze s@po v.lt
Released under the MIT licence since December 2006:
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
(Previously it was distributed under the GNU General Public Licence.)
| string src.util.profilehooks.__copyright__ = "Copyright 2004-2013 Marius Gedminas" |
| string src.util.profilehooks.__date__ = "2013-10-16" |
| string src.util.profilehooks.__license__ = "MIT" |
| string src.util.profilehooks.__version__ = "1.7" |
| dictionary src.util.profilehooks.AVAILABLE_PROFILERS = {} |
| src.util.profilehooks.cProfile = None |
| src.util.profilehooks.hotshot = None |
| tuple src.util.profilehooks.local = dict((name, globals()[name]) for name in __all__) |
| string src.util.profilehooks.message |


@Attentec, Technical manual
Overview
This is a presence app for enabling effective communication in a company or group where the users often are on assignments "in the field". It consists of two parts, a server part and an android app part.
About this document
This document is intended as an introduction and reference for any people working with this project in the future. It first gives an overview of the whole system (this chapter). Then it goes into detail for the Server part of the system, then the Android part, and lastly it gives a detailed specification of the communication between them.
The sections are partitioned into the following main parts:
- Function: What does the system do
- Getting started: Steps to take to get started developing
- Code structure: How is the code itself structured
- Deployment: What steps to take for deploying the system to a live environment
Figure 1. System overview
Server
The server, running on Ruby on Rails, keeps a central database of the users with contact info, their current location, and statuses. It has a web interface for editing this info.
Android application
The android application shows a list of all the users and their statuses, as well as their current location on a map.
Interface
The server and the android application communicate via HTTP requests with POST data, formatted in JSON.
Server - Ruby on Rails
Function
The server part runs Ruby on Rails and is responsible for synchronizing the mobile units/android phones. The information to be synchronized is offered by keeping a central database with roughly the following info about all contacts:
- Name, address and any other contact details
- Linkedin profile (url), title, degree and the like
- Photo, and when it was updated
- Status, both predefined statuses like online/busy/away... and a custom text version
- A timestamp of when the user/phone last connected to the server, used for determining someone as "offline" after a timeout
- Location
The data, except location and status, can be edited from a web interface.
In order to keep all the phones updated they poll the server for data at intervals. All communication between the server and the phones consists of JSON data. The phones also upload their users status and location (if enabled) to the server.
Getting started
Rails development is easiest to do on Linux (or similar). To get it started on a debian based distribution (ubuntu) run the following commands:
$ sudo apt-get install rubygems libsqlite3-dev build-essential ruby-dev libopenssl-ruby $ sudo gem install rails $ sudo gem install sqlite3-ruby $ sudo gem install mongrel $ sudo gem install rqrcode $ sudo gem install vpim
If you are using RedHat or similar, instead of the first row, do:
# yum install rubygems ruby-devel make gcc sqlite-devel
Also make sure that ImageMagick is installed, as it is used for resizing uploaded photos.
A good thing to check is that the rails version defined in at.attentec.com/rails/at.attentec.server/config/environment.rb is the same one you have installed.
The first time you run the applcation you should run the following from the rails-folder (at.attentec.server) to create a session secret and to update the database to newest version:
$ rake generate_session_store $ rake db:migrate
To run the server, go to the folder at.attentec.com/rails/at.attentec.server/, and run the following command:
$ script/server mongrel
From the start there is one user with username admin and password administrator. Please log in and change this password as soon as possible.
Code structure
The code follows the structure of Rails. Everything is based around the model User, with the accompanying controller and views. There is also an ApplicationController for handling sessions for the web interface. There is no session handling for the Android application. Instead the authentication (username and phone key) is sent on every request.
There is a configuration file for a few things in config/initializers/at.attentec.settings.rb. There it is possible to set if it should be possible for everyone who wants to to register a new user. One can also disable the complete business card feature globally with a setting in this file.
QR codes
In a few places, QR codes for scanning with a mobile phone are created. They all use the Google Chart API. Follow the link for documentation.
There is also an action to create the QR codes for the business card links in an svg format. To do this, visit the following url:
http://yourserver.com/users/businesscard_svg_qr/id?parameters
where id is the id of a user, and parameters can be any of the following:
| padding | Number of pixels padding around the code itself |
| pixel_size | Width of one square in the qr code |
| color | CSS color (for example 900028 or red) for the foreground |
| background_color | CSS color for the background |
| crisp_edges | "true" for using shape-rendering: crispEdges. This prevents antialiasing of the squares in the code |
To find the id of a user, log in to the web interface, and click on show on the user in question. The id will be the number in the url of the page.
So an example url would be: http://yourserver.com/users/businesscard_svg_qr/114?padding=30&pixel_size=3&background_color=0F0F0F.
Deployment to live server
To do deployment manually: 1. Move the whole at.attentec.server folder to the live server. 2. Inside the folder, run
$ rake db:migrate RAILS_ENV=production $ script/server mongrel -d -e production
What this deployment will give you is a server running on port 3000. To get it to answer calls from another port (for example 80, or any other port you fancy), you can either run mongrel with the -p option:
$ script/server mongrel -p 80 -d -e production
or use Apache "in front of" rails, with something like
ProxyPass / http://<IP_ADDRESS>:3000/ retry=2 ProxyPassReverse / http://<IP_ADDRESS>:3000/
in the Apache configuration file.
or you could make a redirect in iptables by adding the following in the end of the iptables config file (e.g. in RedHat /etc/sysconfig/iptables) and restart iptables.
# Redirect port 80 to rails *nat :PREROUTING ACCEPT [0:0] -A PREROUTING -p tcp -m tcp --dport 80 -j REDIRECT --to-port 3000 COMMIT
Thumbnails
Thumbnails for photos are generated through a rails plugin called paperclip when the image files are uploaded. These thumbnails are generated once and not changed until a new picture is uploaded. If there is a need for reprocessing the images, go to at.attentec.com/rails/at.attentec.server/ and run the following:
$ export class=User; rake paperclip:refreshor
$ script/console >> User.find_each { |u| u.photo.reprocess! }
Debugging
To be able to debug an object, like var_dump() in PHP, the function var_dump is defined in applications_helper.rb. This works well in both testCases and templates. Example:
var_dump @user <%= var_dump @user %>
You can also use p to get debug output into the server console:
p @user
To show error messages from an ActiveRecord object during for example tests, you can use p_errors:
p_errors @user
Android
The GUI of the android application consists of a tab-view with each tab containing a so called Activity. An Activity is a class implementing one tab. This is convenient when one wishes to extend the current functionality with new or other already implemented applications.
For the user to be able to get past the login stage, a server as described above, and an Internet connection is needed.
Function
The login stage is the first part of the application which authenticates with the server. After logging in, a tab-view consisting of two tabs is displayed. The two tabs contain an Activity each. The first is a contact list called "Contacts" and the other is a Google map interface called "Close To You". By using the menu in the application and pressing "Preferences", the user can configure intervals for updates in the application. It is also possible to turn off the location update completely.
Login
The login uses the same username used at the rails server but a phone key generated in the account info on the rails server. It is possible to read the phone key via QR-code. For this we use ZXing by firing an Intent when the user clicks the button.
Contacts
The contact list is synchronized with the rails server and updates with the intervals given in the settings for this application. What data is sent can be seen in the Interface section below. Clicking each status will bring up a list of alternatives on how to contact the person. These contact alternatives are implemented by firing the corresponding android Intent. The alternative for detailed info starts the Activity DetailedInfoActivity, also via an Intent.
The user can set availability and custom status and choose how to sort the list in the menu.
Close To You
The "Close to you" application displays a Google map that has some overlays with "flags" for other contacts, as well as a marker for the own position if available. For this map to work one needs the correct API key, which is defined in res/values/constants.xml. This key is different for development and deployment! See below under "Keystore".
Getting started
Eclipse is the best alternative developing for the Android platform. There is a plugin which connects to adb, which has a range of tools for android, including an emulator and multiple views in eclipse for debugging. Below are a set of steps required to start modifying, or building, this application.
Working with Android development and Eclipse
Setup Eclipse with Android:
Follow the guide on http://developer.android.com/sdk/index.html#quickstart. In step 4 of the guide, choose "Google APIs by Google Inc." for the version you want to develop on (7 for android 2.1, 8 for android 2.2). The current target version is 2.1.
Importing the source code into a project
- Go to File -> Import
- Choose General -> Import existing project into workspace
- Choose the folder at.attentec.com/android/at.attentec.client/ as root directory
- After creating the project, set the encoding to UTF-8 by right-clicking the project in 'Package Explorer' choosing 'Properties' then 'Resources' and set 'Text file encoding' to 'Others: UTF-8'
- Set the development keystore as described in Keystore below
- Make a DevelopmentSettings.java from the DevelopmentSettings.java.template file
After importing the source code, you need to make a copy of DevelopmentSettings.java.template with the name DevelopmentSettings.java. It might also be necessary to set the ip/url of your rails server in this file.
Random tips:
- Sometimes it is good to run 'Project' -> 'Clean...' if a lot of error messages pop up.
Keystore
To get the map in Close To You working, you need to choose the right keystore and API key combination. How to generate keystores and API keys is described here: http://code.google.com/android/add-ons/google-apis/mapkey.html. It is practical to have two keystores, one for development and one for deployment. One also needs two map API keys.
Change the keystore you use in eclipse -> windows -> preferences -> android -> build -> custom keystore. Always choose the development keystore here. When you changed the keystore you need to uninstall the application from the emulator/phone before you can install it again, as it checks if the keys match. On the emulator/phone, go to Menu -> Settings -> Applications -> Manage to uninstall. It might also be a good idea to run Project -> Clean in Eclipse.
For deployment, see that chapter.
To change they API key, copy-paste the one you want in at.attentec.com/android/at.attentec.client/res/values/constants.xml.
Test project
There is a test project with some very minimal tests in at.attentec.com/android/at.attentec.client.test/. To import this project into Eclipse, goto menu File->Import... Choose General->Existing Project into Workspace and click Next. Browse for the at.attentec.client.test folder and press Finnish. Make sure that the at.attentec.client project is named AtAttentec in eclipse, this is required for the testproject to find the AtAttentec-project. Then run AllTests.java as Android JUnit test
Code structure
The Android application consists of java classes and resources in the form of xml files and images. These can be found in at.attentec.com/android/at.attentec.client/. The java code is found in /src. Images (called drawables), layout files, and some settings are found in /res. Here we only focus on the code structure of the classes in /src. For a description of Android application structure we recommend http://developer.android.com/index.html as reference.
Figure 2. Design sketch for the Android applications Java code
The application uses a service to run things in the background, namely AttentecService.java. It is used for communicating with the server and fetch locations from the GPS in the background, so as not to block the user interface. For information about how a service works we refer to this guide: http://developer.android.com/reference/android/app/Service.html.
In the login stage the class Login.java is used. Upon successful login, an Intent is fired for the Activity Attentec.
The application has a layout consisting of a tab-view with tabs in it. Each tab is an activity of its own. The tab layout is created by Attentec.java. This initializes two other activities found in ContactsActivity.java and CloseToYou.java.
Data is stored in a local database. For this purpose there is a class called DatabaseAdapter.java. The usage of DatabaseAdapter.java is indicated in Figure 2.
To contact the server there is a helper class called ServerContact.java.
To avoid duplication of constants in different classes the most common ones are stored in DevelopmentSettings.java. The same class also contains the default URL for the rails server.
The CloseToYou.java Activity has three classes for constructing the balloons showed. ContactItemizedOverlay.java is used to contain all balloons, and BalloonItemizedOverlay.java, BalloonOverlayView.java and ContactOverlayItem.java describe how the balloons look and how they are interacted with.
When clicking on a list element in the contact list or a balloon in Close To You a list of alternatives on how to contact the person will pop up. The detailed info alternative runs DetailedInfoActivity.java. This class is an Activity that displays more detailed information about a contact.
The ContactInRadius.java Activity has one class for enabling an interface where the user can choose to display available contacts by radius. The displayed contacts can be selected and deselected individually to be contacted by SMS or e-mail.
Status.java is used to ease the management of different statuses. These statuses are visible in both the contact list and the map in Close To You. The usage of Status.java is indicated in figure 1. StatusDialogCreator.java is used to create the dialog for selecting a status among a set of predefined statuses.
Preferences.java is a simple interface which parses res/xml/preferences.xml, which contains all preferences the user can set. PreferencesHelper.java is a helper class for accessing these preferences from code in a structured way.
LocationHelper.java is a helper class for obtaining the location. It uses the following working steps:
- Start listening for GPS (starts GPS sensor) and network location, and start a timer.
- When a new location comes in, check if it is accurate enough (configured in settings, default 50 meters). If it is, activate a callback, stop the listeners and cancel the timeout timer.
- If the timer times out, use whatever location we can get from the GPS right now, even if it is not very accurate. If we do not get a location from the GPS (for example if the user has turned the GPS off), we use the network location if it accurate enough (500 meters). Activate the callback, and stop the listeners.
This way of fetching the location gives a low energy consumption, and provides a fallback for if the user has turned off the GPS sensor.
SyncAccountAuthenticatorService.java, SyncBatchOperation.java, SyncContactManager.java, SyncContactOperations.java and SyncContactSyncAdapterService.java handle the synchronization with the phones contact list. It is separated from the application except for reading from the database. For implementation details for this feature see the links below:
http://www.c99.org/2010/01/23/writing-an-android-sync-provider-part-1/
http://www.c99.org/2010/01/23/writing-an-android-sync-provider-part-2/
And Googles example implementation:
http://developer.android.com/resources/samples/SampleSyncAdapter/index.html
Deployment
Creating an apk file for installation on phone
To create an apk file which you can install on your phone and distribute to users, follow these steps:
- Change to (temporarily) the live API key for google maps in at.attentec.com/android/at.attentec.client/res/values/constants.xml.
- Make sure the correct server url is entered in DevelopmentSettings.java.
- In eclipse, choose File -> Export.
- Choose Android/Export Android Application.
- Select the project of the application.
- Follow the guide to create or use an existing keystore (the one you have the API key for in constants.xml)
- Change the API key back to the development one in at.attentec.com/android/at.attentec.client/res/values/constants.xml.
- To enable download, you could save the apk file in the rails public/downloads folder, and deploy it to your server.
Debugging
For an extensive documentation on debugging Android applications with Eclipse this guide is recommended: http://developer.android.com/guide/developing/tools/adb.html To enable the debugging perspective, go to Window -> Open Perspective -> DDMS. To go back to the coding environment go to Window -> Open Perspective -> Java. Below are a couple of quick tips that are recommended for debugging.
Examining the android SQL-database
The database in Android is of the format sqlite3. It can be extracted from the emulator or phone by using the file explorer. This can be found by going to Window -> Show View -> Other -> File Explorer. When the file explorer is opened through the folder hierarchy like this: data -> data -> com.attentec -> databases -> data. When selected press the icon seen in figure 2.
Figure 2. Extract database.
Debugging in eclipse with an emulator or a phone
In eclipse when the emulator is running there is a log that can be enabled that is very helpful when debugging an application. It is enabled by going to Window -> Show View -> Other -> Android -> LogCat. In the view for LogCat it is sometimes helpful to clear the log when LogCat stops displaying the messages as a sequence. To output to this log, use something like this in your code:
Log.d(TAG, "This is my error message");
Where TAG is a variable containing a string identifying the class.
Interface between Android and Rails
The communication between the android application and the rails server is done fully over HTML POST requests with JSON data. Every request has authentication data, as well as any data we want to send. All requests are initiated from the application side. The different communications:
- Login: Serves to check the users phone key on login. Request and response of the following form:
These POST and Response variables are always present in all requests and responses.URL app/login.json POST phone_auth={"username":"ninli", "phone_key":"1234abcd"}
Response {"Responsestatus":"Success"} or {"Responsestatus":"Wrong login"}
- Contact list: A list with all contacts and all their info.
URL app/contactlist.json POST Only authentication as above Response {"users": [ {"user":{"city":"Ninja City", "address":"Ninjastreet 12", "location_updated_at":null, "latitude":null, "title":"Ninja", "connected_at":"2010-07-30T10:37:58+02:00", "zipcode":12345, "degree":"Master of Science in Applied Ninja", "username":"ninji", "photo_url":"/images/photos/missing/android/missing.png", "id":94, "photo_updated_at":null, "client":"", "phone":"+46000111000", "linkedin_url":null, "last_name":"Li", "status_custom_message":"Hiding on your roof", "longitude":null, "status":"not_available", "first_name":"Ninja"<EMAIL_ADDRESS>{"user":...},... ], "Responsestatus":"Success" }
- User locations and status: Users location and current status, for all users that have connected in the last three hours.
URL app/user_locations_and_status.json POST Only authentication as above Response {"users": [ {"user": {"location_updated_at":"2010-08-05T10:03:08+02:00", "latitude":null, "connected_at":"2010-08-05T10:04:09+02:00", "id":94, "status_custom_message":"Sneaking in your basement", "longitude":null, "status":"not_available"} } ], "Responsestatus":"Success"}
- Update user info: Send location and/or status to server.
URL app/app_update_user_info.json POST Authentication and for status updates: {"status": {"status":"not_available", "status_custom_message":"Sneaking in your basement"} } for location updates: {"location": {"latitude":"58.795507","longitude":"17.566488"} }
Response {"Responsestatus":"Success"} or {"Responsestatus":"Wrong login"}
Miscellaneous
Locale
The default locale on the server and the client is set to Swedish, but there are English translations available in both rails and the android application.
To set the locale to english on the rails server, change
config.i18n.default_locale = 'sv-SE'
to
config.i18n.default_locale = 'en'
in config/environment.rb
To set the default locale to english on the android application, replace the Swedish strings.xml file with the english strings.xml, and move the swedish one (if you want to keep it) to a values-sv folder.
Working with Ubuntu in Virtual Box
-
To mount a windows folder in virtualbox ubuntu:
In virtualbox: (Enheter -> Delade mappar) and add the folder with folder_name (something you choose)
Then in Ubuntu:sudo mount -t vboxsf folder_name /media/windows
-
To enable access to a server (for example rails) inside virtualbox, from the outside:
Shut the virtual machine down, then go to settings for the virtual machine -> network -> Adapter 2 -> Enable and change "Attached To" to "Host interface".
Start your virtual machine again. The ip you can reach it on is found by running 'ifconfig', and using the ip found under eth1, inet addr:. - If the shared folders stop working: reinstall the guest additions in virtualbox.