Перейти к содержанию
Fire Monkey от А до Я

Aptem

Пользователи
  • Постов

    55
  • Зарегистрирован

  • Посещение

  • Победитель дней

    1

Активность репутации

  1. Like
    Aptem получил реакцию от zairkz в [Делюсь опытом] Разработка собственного провайдера для БД   
    Здравствуйте, коллеги.
     
    В своих разработках мы придерживаемся принципа разделения приложения на слои:
    1. Слой доступа к данным - DAL
    2. Слой бизнес-логики - BL
    3. Слой представления - UI
     
    В связи с этим большую часть модулей приложений мы разрабатываем вручную, не прибегая к использованию визуальных компонентов. Все визуальные компоненты (кнопки, гриды, мемо и т. п.) только в слое UI. Также мы поступили и со слоем DAL, разработав собственный провайдер для доступа к базе данных Oracle. Активно используем его уже лет 5 и пока он покрывает все наши нужды.
     
    Представляю его вашему вниманию и прошу объективной критики по любому поводу.
     
    Интерфейс провайдера:
    unit OracleProviderInterface; interface uses Ora, DB, Variants, SysUtils, Classes, Contnrs, Spring.Collections ; /// <summary> /// Parameters data type enumeration /// </summary> type TParamDataType = ( pdtDefault = 1, pdtBlob = 2, pdtTable = 3, pdtClob = 4 ); /// <summary> /// Class for CDB database parameter /// </summary> type TCDBOraParam = Class ( TOraParam ) public {$REGION 'Fields'} /// <summary> /// Gets or sets parameter data type /// </summary> ParamDataType : TParamDataType; {$ENDREGION} {$REGION 'Constructors'} /// <summary> /// Creates TCDBOraParam instance /// </summary> constructor Create (); {$ENDREGION} end; /// <summary> /// Interface for database provider /// </summary> type IDatabaseProvider = interface ( IInterface ) ['{BF277F9C-D046-4015-8E17-6B4C67CC021B}'] {$REGION 'Methods'} /// <summary> /// Executes SQL query passed with parameters /// </summary> /// <param name="query">SQL query text</param> /// <param name="parameters">Binded query parameters</param> /// <returns>Query execution results</returns> function ExecuteQuery ( query : String; const parameters : IList<TCDBOraParam> = nil ) : TDataSet; /// <summary> /// Executes non-selective (insert, update, delete) SQL passed with parameters /// </summary> /// <param name="query">SQL query text</param> /// <param name="parameters">Binded query parameters</param> /// <returns>Number of rows affected</returns> function ExecuteNonQuery ( query : String; const parameters : IList<TCDBOraParam> = nil ) : Integer; /// <summary> /// Executes stored procedure with particular parameters /// </summary> /// <param name="procedureName">Stored procedure name</param> /// <param name="parameters">Binded query parameters</param> /// <returns>Execution results</returns> function ExecuteProcedure ( procedureName : String; const parameters : IList<TCDBOraParam> = nil ) : TOraParams; /// <summary> /// Begins session transaction /// </summary> procedure BeginTransaction (); /// <summary> /// Commits current session transaction /// </summary> procedure CommitTransaction (); /// <summary> /// Rollbacks current session transaction /// </summary> procedure RollbackTransaction (); /// <summary> /// Creates parameter /// </summary> /// <param name="name">Parameter name</param> /// <param name="value">Parameter value</param> /// <param name="paramType">Parameter type (e.g. input or output)</param> /// <param name="paramDataType">Parameter data type (e.g. default or BLOB)</param> /// <returns>Binded parameter</returns> function CreateParameter ( name : String; value : Variant; paramType : TParamType = ptInput; paramDataType : TParamDataType = pdtDefault ) : TCDBOraParam; overload; /// <summary> /// Connects to the server /// </summary> procedure OpenSession (); /// <summary> /// Disconnects from the server /// </summary> procedure CloseSession (); /// <summary> /// Gets session /// </summary> /// <returns>Connection session</returns> function GetSession () : TOraSession; {$ENDREGION} {$REGION 'Properties'} /// <summary> /// Connection session /// </summary> property Session : TOraSession read GetSession; {$ENDREGION} end; implementation { TCDBOraParam } /// <summary> /// Creates TCDBOraParam instance /// </summary> constructor TCDBOraParam.Create (); begin inherited Create ( nil ); ParamDataType := pdtDefault; end; end. Реализация:
    unit OracleProvider; interface uses Ora, DB, Variants, SysUtils, Classes, Contnrs, MemData, DBAccess, Spring.Container, Spring.Collections, //BL OracleProviderInterface ; /// <summary> /// Data provider to Oracle database /// </summary> type TOracleProvider = class ( TInterfacedObject, IDatabaseProvider ) private {$REGION 'Fields'} /// <summary> /// Database session /// </summary> _session : TOraSession; /// <summary> /// Query /// </summary> _query : TOraQuery; /// <summary> /// SQL-expression /// </summary> _sql : TOraSQL; /// <summary> /// Stored procedure /// </summary> _storedProcedure : TOraStoredProc; /// <summary> /// Flag to understand who owns session /// </summary> _isOwnSession : Boolean; /// <summary> /// Connection string /// </summary> _connectionString : String; {$ENDREGION} {$REGION 'Methods'} /// <summary> /// Gets parameter's field type by Variant value /// </summary> /// <param name="value">value</param> /// <returns>Field type for TOraParam</returns> function GetFieldType ( value : Variant ) : TFieldType; /// <summary> /// Initializes class inner objects /// </summary> /// <param name="isOwnSession">Flag to understand who owns session</param> procedure Initialize ( isOwnSession : Boolean ); /// <summary> /// Gets session /// </summary> /// <returns>Connection session</returns> function GetSession () : TOraSession; {$ENDREGION} public {$REGION 'Methods'} /// <summary> /// Executes SQL query passed with parameters /// </summary> /// <param name="query">SQL query text</param> /// <param name="parameters">Binded query parameters</param> /// <returns>Query execution results</returns> function ExecuteQuery ( query : String; const parameters : IList<TCDBOraParam> = nil ) : TDataSet; /// <summary> /// Executes non-selective (insert, update, delete) SQL passed with parameters /// </summary> /// <param name="query">SQL query text</param> /// <param name="parameters">Binded query parameters</param> /// <returns>Number of rows affected</returns> function ExecuteNonQuery ( query : String; const parameters : IList<TCDBOraParam> = nil ) : Integer; /// <summary> /// Executes stored procedure with particular parameters /// </summary> /// <param name="procedureName">Stored procedure name</param> /// <param name="parameters">Binded query parameters</param> /// <returns>Execution results</returns> function ExecuteProcedure ( procedureName : String; const parameters : IList<TCDBOraParam> = nil ) : TOraParams; /// <summary> /// Begins session transaction /// </summary> procedure BeginTransaction (); /// <summary> /// Commits current session transaction /// </summary> procedure CommitTransaction (); /// <summary> /// Rollbacks current session transaction /// </summary> procedure RollbackTransaction (); /// <summary> /// Creates parameter /// </summary> /// <param name="name">Parameter name</param> /// <param name="value">Parameter value</param> /// <param name="paramType">Parameter type (e.g. input or output)</param> /// <param name="paramDataType">Parameter data type (e.g. default or BLOB)</param> /// <returns>Binded parameter</returns> function CreateParameter ( name : String; value : Variant; paramType : TParamType = ptInput; paramDataType : TParamDataType = pdtDefault ) : TCDBOraParam; overload; /// <summary> /// Connects to the server /// </summary> procedure OpenSession (); /// <summary> /// Disconnects from the server /// </summary> procedure CloseSession (); {$ENDREGION} {$REGION 'Constructors'} /// <summary> /// Creates TOracleProvider instance with particular connection string /// </summary> /// <param name="connectionString">Connection string</param> constructor Create ( const connectionString : String ); overload; /// <summary> /// Creates TOracleProvider instance with particular session /// </summary> /// <param name="oracleSession">Connection session</param> constructor Create ( const oracleSession : TOraSession ); overload; {$ENDREGION} {$REGION 'Destructors'} /// <summary> /// Safely destroys TOracleProvider instance /// </summary> destructor Destroy (); override; {$ENDREGION} {$REGION 'Properties'} /// <summary> /// Connection session /// </summary> property Session : TOraSession read GetSession; {$ENDREGION} end; implementation {TOracleProvider} /// <summary> /// Creates TOracleProvider instance with particular connection string /// </summary> /// <param name="connectionString">Connection string</param> constructor TOracleProvider.Create ( const connectionString : String ); begin _connectionString := connectionString; Initialize ( true ); end; /// <summary> /// Creates TOracleProvider instance with particular session /// </summary> /// <param name="oracleSession">Connection session</param> constructor TOracleProvider.Create ( const oracleSession : TOraSession ); begin _session := oracleSession; Initialize ( false ); end; /// <summary> /// Initializes class inner objects /// </summary> /// <param name="isOwnSession">Flag to understand who owns session</param> procedure TOracleProvider.Initialize ( isOwnSession : Boolean ); begin if isOwnSession then begin _session := TOraSession.Create ( nil ); _session.ConnectString := _connectionString; _session.Connected := false; _session.LoginPrompt := false; _session.Options.Direct := true; _session.AutoCommit := false; _session.Options.NeverConnect := true; _session.Pooling := true; end; _query := TOraQuery.Create ( nil ); _query.Session := _session; _query.AutoCommit := false; _query.FetchAll := true; _sql := TOraSQL.Create ( nil ); _sql.Session := _session; _sql.AutoCommit := false; _storedProcedure := TOraStoredProc.Create ( nil ); _storedProcedure.Session := _session; _storedProcedure.AutoCommit := false; _storedProcedure.FetchAll := true; _isOwnSession := isOwnSession; end; /// <summary> /// Safely destroys TOracleProvider instance /// </summary> destructor TOracleProvider.Destroy (); begin if _query.Active then begin _query.Close (); end; _query.Free (); if _storedProcedure.Active then begin _storedProcedure.Close (); end; _storedProcedure.Free (); if _isOwnSession then begin if _session.Connected then begin _session.Close (); end; _session.Free (); end; inherited; end; /// <summary> /// Connects to the server /// </summary> procedure TOracleProvider.OpenSession (); begin if _session <> nil then begin try _session.Open (); except on e : Exception do begin raise Exception.Create ( e.Message ); end; end; end; end; /// <summary> /// Disconnects from the server /// </summary> procedure TOracleProvider.CloseSession (); begin if _isOwnSession then begin if ( _session <> nil ) AND ( _session.Connected ) then begin try _session.Close (); except on e : Exception do begin raise Exception.Create ( e.Message ); end; end; end; end; end; /// <summary> /// Begins session transaction /// </summary> procedure TOracleProvider.BeginTransaction (); begin try _session.StartTransaction (); except on e : Exception do begin raise Exception.Create ( e.Message ); end; end; end; /// <summary> /// Commits current session transaction /// </summary> procedure TOracleProvider.CommitTransaction (); begin if _session.InTransaction then begin try _session.Commit (); except on e : Exception do begin raise Exception.Create ( e.Message ); end; end; end; end; /// <summary> /// Rollbacks current session transaction /// </summary> procedure TOracleProvider.RollbackTransaction (); begin if _session.InTransaction then begin try _session.Rollback (); except on e : Exception do begin raise Exception.Create ( e.Message ); end; end; end; end; /// <summary> /// Executes SQL query passed with parameters /// </summary> /// <param name="query">SQL query text</param> /// <param name="parameters">Binded query parameters</param> /// <returns>Query execution results</returns> function TOracleProvider.ExecuteQuery ( query : String; const parameters : IList<TCDBOraParam> = nil ) : TDataSet; var i : Integer; param : TOraParam; dataSet : TDataSet; begin dataSet := TDataSet.Create ( nil ); _query.Close (); _query.SQL.Text := query; if Assigned ( parameters ) then begin _query.Params.Clear (); for i := 0 to parameters.Count - 1 do begin param := TOraParam.Create ( nil ); param.Name := TOraParam ( parameters[i] ).Name; param.Value := TOraParam ( parameters[i] ).Value; param.ParamType := TOraParam ( parameters[i] ).ParamType; _query.Params.AddParam ( param ); end; end; try if _session.Connected then begin _query.Execute (); dataSet := _query.Fields.DataSet; dataSet.Active := true; end; except on e : Exception do begin raise Exception.Create ( e.Message ); end; end; Result := dataSet; end; /// <summary> /// Executes stored procedure with particular parameters /// </summary> /// <param name="procedureName">Stored procedure name</param> /// <param name="parameters">Binded query parameters</param> /// <returns>Execution results</returns> function TOracleProvider.ExecuteProcedure ( procedureName : String; const parameters : IList<TCDBOraParam> = nil ) : TOraParams; var i : Integer; j : Integer; begin _storedProcedure.Close (); _storedProcedure.StoredProcName := procedureName; _storedProcedure.Prepare (); if Assigned ( parameters ) then begin for i := 0 to parameters.Count - 1 do begin for j := 0 to _storedProcedure.Params.Count - 1 do begin if _storedProcedure.Params[j].Name = TOraParam ( parameters[i] ).Name then begin _storedProcedure.Params[j] := TOraParam ( parameters[i] ); if TCDBOraParam ( parameters[i] ).ParamDataType = pdtBlob then begin if ( ( TOraParam ( parameters[i] ).Value <> Null ) AND ( TOraParam ( parameters[i] ).Value <> '') ) then begin _storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).DataType := ftOraBlob; _storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).ParamType := TOraParam ( parameters[i] ).ParamType; _storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).AsOraBlob.LoadFromFile ( TOraParam ( parameters[i] ).Value ); end else begin _storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).DataType := ftOraBlob; _storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).ParamType := TOraParam ( parameters[i] ).ParamType; _storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).AsOraBlob.FreeBlob (); end; end; if TCDBOraParam ( parameters[i] ).ParamDataType = pdtClob then begin if ( ( TOraParam ( parameters[i] ).Value <> Null ) AND ( TOraParam ( parameters[i] ).Value <> '') ) then begin _storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).DataType := ftOraClob; _storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).ParamType := TOraParam ( parameters[i] ).ParamType; _storedProcedure.ParamByName ( TOraParam ( parameters[i] ).Name ).Value := TOraParam ( parameters[i] ).Value; end; end; end; end; end; end; if _session.Connected then begin try _storedProcedure.Execute (); Result := _storedProcedure.Params; except on e : Exception do begin raise Exception.Create ( e.Message ); end; end; end else begin Result := TOraParams.Create ( nil ); end; end; /// <summary> /// Executes non-selective (insert, update, delete) SQL passed with parameters /// </summary> /// <param name="query">SQL query text</param> /// <param name="parameters">Binded query parameters</param> /// <returns>Number of rows affected</returns> function TOracleProvider.ExecuteNonQuery ( query : String; const parameters : IList<TCDBOraParam> = nil ) : Integer; var i : Integer; begin _query.Close (); _query.SQL.Clear (); _query.SQL.Text := query; if Assigned ( parameters ) then begin _query.Params.Clear (); for i := 0 to parameters.Count - 1 do begin _query.Params.AddParam ( TOraParam ( parameters[i] ) ); end; end; if _session.Connected then begin _query.ExecSQL (); Result := _query.RowsAffected; end else begin Result := 0; end; end; /// <summary> /// Creates parameter /// </summary> /// <param name="name">Parameter name</param> /// <param name="value">Parameter value</param> /// <param name="paramType">Parameter type (e.g. input or output)</param> /// <param name="paramDataType">Parameter data type (e.g. default or BLOB)</param> /// <returns>Binded parameter</returns> function TOracleProvider.CreateParameter ( name : String; value : Variant; paramType : TParamType = ptInput; paramDataType : TParamDataType = pdtDefault ) : TCDBOraParam; var parameter : TCDBOraParam; begin parameter := TCDBOraParam.Create (); parameter.Name := UpperCase ( name ); parameter.ParamType := paramType; parameter.DataType := GetFieldType ( value ); case paramDataType of pdtDefault: begin parameter.Value := value; end; pdtBlob: begin parameter.Value := value; end; pdtClob: begin parameter.Value := value; end; pdtTable: begin parameter.Table := true; parameter.DataType := ftFloat; parameter.Value := value; end; end; parameter.ParamDataType := paramDataType; Result := parameter; end; /// <summary> /// Gets parameter's field type by Variant value /// </summary> /// <param name="value">value</param> /// <returns>Field type for TOraParam</returns> function TOracleProvider.GetFieldType ( value : Variant ) : TFieldType; var fieldType : TFieldType; begin case VarType ( value ) AND VarTypeMask of varString : fieldType := TFieldType.ftWideString; varUString : fieldType := TFieldType.ftWideString; varInteger : fieldType := TFieldType.ftInteger; varByte : fieldType := TFieldType.ftInteger; varDate : fieldType := TFieldType.ftDate; else fieldType := TFieldType.ftUnknown; end; Result := fieldType; end; /// <summary> /// Gets session /// </summary> /// <returns>Connection session</returns> function TOracleProvider.GetSession () : TOraSession; begin Result := _session; end; initialization GlobalContainer.RegisterType<TOracleProvider>.Implements<IDatabaseProvider> ( 'Oracle' ); end. Использование:
     
      1. Объявляем:
    var _provider : IDatabaseProvider;   2. Инициализируем: 
    var _connectionString := login + '/' + password + '@' + HostName + ':' + Port + ':' + SID; _provider := GlobalContainer.Resolve<IDatabaseProvider> ( 'Oracle', [connectionString] ); _provider.OpenSession ();   3. Собственно использование:
        3.1 Получаем выборку (SELECT):
    function TVSPHelper.GetVSPListByWellId ( wellId : Integer ) : IList<IVSP>; var list : IList<IVSP>; item : IVSP; query : String; dataSet : TDataSet; i : Integer; j : Integer; parameters: IList<TCDBOraParam>; report : IReport; begin query := 'SELECT ' + 'id, ' + 'comments, ' + 'wellbore_id, ' + 'wellbore_name, ' + 'wellbore_comments, ' + 'wellbore_depth, ' + 'begin_date, ' + 'end_date, ' + 'expedition_id, ' + 'expedition_name, ' + 'organization_id, ' + 'organization_name, ' + 'subject_id, ' + 'subject_name, ' + 'well_id, ' + 'well_name, ' + 'well_comments, ' + 'well_x, ' + 'well_y, ' + 'well_altitude, ' + 'well_rotary_table_elevation ' + ' FROM ' + 'seis.v_vsp ' + ' WHERE ' + 'well_id = :ID '; parameters := TCollections.CreateList<TCDBOraParam> ( true ); parameters.Add ( _provider.CreateParameter ( 'ID', wellId ) ); dataSet := _provider.ExecuteQuery ( query, parameters ); list := TCollections.CreateList<IVSP>; dataSet.First (); for i := 0 to dataSet.RecordCount - 1 do begin item := GlobalContainer.Resolve<IVSP>; item.Id := dataSet.FieldByName ( 'id' ).AsInteger; item.Comments := dataSet.FieldByName ( 'comments' ).AsString; item.BeginDate := dataSet.FieldByName ( 'begin_date' ).AsDateTime; item.EndDate := dataSet.FieldByName ( 'end_date' ).AsDateTime; item.Wellbore.Id := dataSet.FieldByName ( 'wellbore_id' ).AsInteger; item.Wellbore.Name := dataSet.FieldByName ( 'wellbore_name' ).AsString; item.Wellbore.Comments := dataSet.FieldByName ( 'wellbore_comments' ).AsString; item.Wellbore.Depth := dataSet.FieldByName ( 'wellbore_depth' ).AsFloat; item.Expedition.Id := dataSet.FieldByName ( 'expedition_id' ).AsInteger; item.Expedition.Name := dataSet.FieldByName ( 'expedition_name' ).AsString; item.Organization.Id := dataSet.FieldByName ( 'organization_id' ).AsInteger; item.Organization.Name := dataSet.FieldByName ( 'organization_name' ).AsString; item.Wellbore.Well.Subject.Id := dataSet.FieldByName ( 'subject_id' ).AsInteger; item.Wellbore.Well.Subject.Name := dataSet.FieldByName ( 'subject_name' ).AsString; item.Wellbore.Well.Id := dataSet.FieldByName ( 'well_id' ).AsInteger; item.Wellbore.Well.Name := dataSet.FieldByName ( 'well_name' ).AsString; item.Wellbore.Well.Comments := dataSet.FieldByName ( 'well_comments' ).AsString; item.Wellbore.Well.X := dataSet.FieldByName ( 'well_x' ).AsFloat; item.Wellbore.Well.Y := dataSet.FieldByName ( 'well_y' ).AsFloat; item.Wellbore.Well.Altitude := dataSet.FieldByName ( 'well_altitude' ).AsFloat; item.Wellbore.Well.RotaryTableElevation := dataSet.FieldByName ( 'well_rotary_table_elevation' ).AsFloat; list.Add ( item ); dataSet.Next (); end; Result := list; end;     3.2 Вызов хранимой процедуры:
    function TVSPHelper.GetShotpointMap ( shotpoint : IVSPShotpoint ) : String; var procedureName : String; parameters: IList<TCDBOraParam>; returnParameters : TOraParams; imageURL : String; errorCode : Integer; errorMessage : String; begin procedureName := 'seis.geometric_functions.p_get_shotpoint_map'; parameters := TCollections.CreateList<TCDBOraParam> ( true ); parameters.Add ( _provider.CreateParameter ( 'operation', 0 ) ); parameters.Add ( _provider.CreateParameter ( 'shotpoint_id_in', shotpoint.Id ) ); parameters.Add ( _provider.CreateParameter ( 'image_url', '', ptOutput ) ); parameters.Add ( _provider.CreateParameter ( 'error_code', 0, ptOutput ) ); parameters.Add ( _provider.CreateParameter ( 'error_message', '', ptOutput ) ); try _provider.BeginTransaction (); returnParameters := _provider.ExecuteProcedure ( procedureName, parameters ); imageURL := returnParameters.ParamByName ( 'image_url' ).AsString; errorCode := returnParameters.ParamByName ( 'error_code' ).AsInteger; errorMessage := returnParameters.ParamByName ( 'error_message' ).AsString; if errorCode = 0 then begin _provider.CommitTransaction (); end else begin _provider.RollbackTransaction (); end; except _provider.RollbackTransaction (); raise; end; imageURL := AnsiReplaceStr ( imageURL, 'https', 'http' ); Result := imageURL; end;     3.3 Выполнение операций CRUD:
    procedure TAdministrationUserHelper.SetUserStatus ( userId : Integer; status : Integer ); var query : String; begin query := 'UPDATE seis.users SET status_id = ' + IntToStr ( status ); try Provider.BeginTransaction (); Provider.ExecuteNonQuery ( query ); Provider.CommitTransaction (); except Provider.RollbackTransaction (); raise; end; end; Стоит заметить, что данный провайдер прекрасно подходит как для десктоп-приложений, так и для мобильных приложений. Конечно с небольшими изменениями, но принцип и структура не меняются. Кому будет интересно, могу выложить код провайдера для работы с БД SQLite, который я использовал для приложений под iOS.
  2. Like
    Aptem получил реакцию от alexbirukov в [Поделись опытом] Релиз приложений. Автоматизация процесса.   
    Судя по тому, что ответов не было, опишу как это получилось у меня.
     
    Получилось настроить все как я и писал выше, но пока без UI-тестов.
     
    FinalBuilder оказался действительно мощным инструментов, который позволяет делать очень много и решать одни и те же задачи разными способами.
     
    Итак, приступим к описанию.
     
    У меня 17 модулей в группе проектов. Часть кода является общей для всех проектов и вынесена отдельно из проекта. Тесты созданы на каждый проект и на общий код. Пока не будут без ошибок скомпилированы и пройдены тесты, дальнейший процесс сборки пройти не может. Смысл в том, что если общий код с ошибками, то и код каждого из проектов может дать ошибки, поэтому продолжать сборку нет смысла.
     
    Алгоритм сборки:
    1. Компилирование тестов общей части кода.
    2. Если компилирование без ошибок, то запускаем тесты. Если с ошибками, то прерываем весь процесс и высылаем электронное письмо с логом компилятора.
    3. Если тесты пройдены без ошибок, то приступаем к компиляции проектов. Если с ошибками, то прерываем весь процесс и высылаем электронное письмо с логом тестов.
    4. Тоже самое с проектами. Сначала компилируем тесты и запускаем их. Если без ошибок, то компилируем сами проекты. Если с ошибками, то весь процесс уже не прерываем, а просто пропускаем проект с ошибками и также высылаем лог компилятора или тестов на почту.
    5. Сборка проходит каждую ночь в течении недели, а в воскресенье в конце сборки происходит дистрибуция бинарников на сервер обновлений. Во время дистрибуции в бинарники записывается информация о версии, последней компиляции и прочая информация.
     
    И все это происходит автоматически из одного окна FinalBuilder.
     
    Пока все нравится, полет две недели.
     
    А вот как это дерево, которое я описал, выглядит в FinalBuilder. Во вложении.
     
    Если кого-то интересуют подробности, то пишите, с удовольствием расскажу.

  3. Like
    Aptem получил реакцию от rareMax в Как при повторной компиляции получить список всех warnings   
    Решение нашлось более элегантное. Коллеги, со stackoverflow подсказали:
     
    Main menu -> Project -> Synatx Check
     
     
    Оригинал: http://stackoverflow.com/questions/32454899/delphi-how-to-get-warnings-list-after-second-compilation
  4. Like
    Aptem получил реакцию от rareMax в Атрибуты в Delphi   
    Мы используем атрибуты для проверки через RTTI заполнено ли поле значением или нет:
    unit TopologyPoint; interface uses SysUtils, Classes, Rtti, Spring.Container, //BL TopologyPointInterface, //Modules WhatElementAttribute ; /// <summary> /// Class for point primitive /// </summary> type TTopologyPoint = Class ( TInterfacedObject, ITopologyPoint ) private {$REGION 'Fields'} /// <summary> /// Gets or sets X coordinate /// </summary> [TWhatElementAttribute ( TWhatElement.PublicField )] _x : Variant; /// <summary> /// Gets or sets Y coordinate /// </summary> [TWhatElementAttribute ( TWhatElement.PublicField )] _y : Variant; /// <summary> /// Gets or sets Z coordinate /// </summary> _z : Variant; {$ENDREGION} {$REGION 'Methods'} /// <summary> /// Gets X /// </summary> /// <returns>X</returns> function GetX () : Variant; /// <summary> /// Sets X /// </summary> /// <param name="value">X</param> procedure SetX ( const value : Variant ); /// <summary> /// Gets Y /// </summary> /// <returns>Y</returns> function GetY () : Variant; /// <summary> /// Sets Y /// </summary> /// <param name="value">y</param> procedure SetY ( const value : Variant ); /// <summary> /// Gets Z /// </summary> /// <returns>Z</returns> function GetZ () : Variant; /// <summary> /// Sets Z /// </summary> /// <param name="value">Z</param> procedure SetZ ( const value : Variant ); /// <summary> /// Gets empty flag of coordinates /// </summary> /// <returns>True if all coordinates are 0, false otherwise</returns> function GetEmptyFlag () : Boolean; {$ENDREGION} public {$REGION 'Properties'} /// <summary> /// X coordinate /// </summary> property X : Variant read GetX write SetX; /// <summary> /// Y coordinate /// </summary> property Y : Variant read GetY write SetY; /// <summary> /// Z coordinate /// </summary> property Z : Variant read GetZ write SetZ; /// <summary> /// Gets empty flag of coordinates /// </summary> property IsEmpty : Boolean read GetEmptyFlag; {$ENDREGION} end; implementation {TTopologyPoint} /// <summary> /// Gets empty flag of coordinates /// </summary> /// <returns>True if all coordinates are 0, false otherwise</returns> function TTopologyPoint.GetEmptyFlag () : Boolean; var context : TRttiContext; objectType : TRttiType; field : TRttiField; attribute : TCustomAttribute; found : Boolean; begin context := TRTTIContext.Create (); objectType := context.GetType ( Self.ClassInfo ); found := false; for field in objectType.GetFields do begin for attribute in field.GetAttributes do begin if attribute is TWhatElementAttribute then begin if ( attribute as TWhatElementAttribute ).Element = TWhatElement.PublicField then begin if field.FieldType.Handle = TypeInfo ( String ) then begin if field.GetValue ( self ).ToString <> '' then begin found := true; break; end; end; if field.FieldType.Handle = TypeInfo ( Integer ) then begin if field.GetValue ( self ).ToString <> '0' then begin found := true; break; end; end; if field.FieldType.Handle = TypeInfo ( Extended ) then begin if field.GetValue ( self ).ToString <> '0' then begin found := true; break; end; end; end; end; end; end; Result := not found; end; /// <summary> /// Gets X /// </summary> /// <returns>X</returns> function TTopologyPoint.GetX () : Variant; begin Result := _x; end; /// <summary> /// Sets X /// </summary> /// <param name="value">X</param> procedure TTopologyPoint.SetX ( const value : Variant ); begin _x := value; end; /// <summary> /// Gets Y /// </summary> /// <returns>Y</returns> function TTopologyPoint.GetY () : Variant; begin Result := _y; end; /// <summary> /// Sets Y /// </summary> /// <param name="value">y</param> procedure TTopologyPoint.SetY ( const value : Variant ); begin _y := value; end; /// <summary> /// Gets Z /// </summary> /// <returns>Z</returns> function TTopologyPoint.GetZ () : Variant; begin Result := _z; end; /// <summary> /// Sets Z /// </summary> /// <param name="value">Z</param> procedure TTopologyPoint.SetZ ( const value : Variant ); begin _z := value; end; initialization GlobalContainer.RegisterType<TTopologyPoint>.Implements<ITopologyPoint>; end. А вот сам класс TWhatElement:
    unit WhatElementAttribute; interface uses SysUtils, Classes; /// <summary> /// Attribute type enumeration /// </summary> type TWhatElement = ( PublicField, PublicProperty, PublicMethod ); /// <summary> /// Class for attribute types /// </summary> type TWhatElementAttribute = class ( TCustomAttribute ) private {$REGION 'Private fields'} /// <summary> /// Element type instance /// </summary> _element : TWhatElement; {$ENDREGION} public {$REGION 'Properties'} /// <summary> /// Property for element type instance /// </summary> property Element : TWhatElement read _element write _element; {$ENDREGION} {$REGION 'Constructors'} /// <summary> /// Creates TWhatElementAttribute instance /// </summary> constructor Create ( const element : TWhatElement ); {$ENDREGION} end; implementation /// <summary> /// Creates TWhatElementAttribute instance /// </summary> constructor TWhatElementAttribute.Create ( const element : TWhatElement ); begin _element := element; end; end. Смысл в том, что когда полей очень много, то писать проверку всех полей очень затруднительно. В случае использования атрибутов, достаточно указать атрибут поля и он будет включен в проверку автоматически.
  5. Like
    Aptem отреагировална bigjorj в Ошибка OutOfMemoryException при компиляции сразу всех проектов в группе   
    Я так подозреваю - переходить на RAD 10 SEATLE!!!   
     
    https://www.embarcadero.com/ru/products/rad-studio#double
  6. Like
    Aptem получил реакцию от Martifan в Лямбда выражения   
    Лямбда-выражение реализуется в Delphi через анонимные методы. Думаю, что они присутствуют и в Builder.
     
    Попробуйте почитать вот здесь: http://docwiki.embarcadero.com/RADStudio/XE7/en/How_to_Handle_Delphi_Anonymous_Methods_in_C%2B%2B
     
    В Delphi анонимные методы реализуются так.
     
    Объявление:
    type TSearchFunction = reference to function ( criteria : Integer ) : IDictionary<String, Integer>; ... LeftFunction : TSearchFunction; Использование:
    fmSearch.LeftFunction := function ( criteria : Integer ) : IDictionary<String, Integer> begin Result := searchHelper.GetItems ( criteria ); end;
  7. Like
    Aptem отреагировална Brovin Yaroslav в [Spring4D] Освобождение памяти объектов интерфейсного типа   
    Ваша ошибка в том, что вы используете не тот список. Вы используете TList, который не удаляет свои элементы в момент разрушения списка. Поэтому ваша утечка - это не распущенные экземпляры TSurvey. Вместо этого нужно использовать TObjectList, который при установке ownsObjects = True, удаляем свои элементы.
     
    Итого, код создания списка должен быть таким:
    function GetList: IList<TSurvey>; var list : IList<TSurvey>; i : Integer; item : TSurvey; begin list := TCollections.CreateObjectList<TSurvey>(True); // <-- !!! for i := 0 to 10 do begin item := TSurvey.Create (); item.Id := Random(100); item.Name := I.ToString; list.Add ( item ); end; Result := list; end; Можно поиграться с проектом в Windows MemoryLeak.zip
  8. Like
    Aptem получил реакцию от ASRenuff в HTC Desire C. Делюсь опытом.   
    Коллеги, приветствую.
     
    Хочу поделиться успешным опытом подключения аппарата HTC Desire C к Delphi XE5. Никаких сложностей с подключением не возникло. Скачал с официального сайта HTC программу HTC Sync http://www.htc.com/ru/support/content.aspx?id=6301. В ее состав входят и драйверы. После установки саму программу HTC Sync снес, она мне как таковая не нужна. Смартфон автоматически появился в дереве target-устройств проекта.
     
     
    Выходные данные:
     
    Смартфон: HTC Desire C
    ОС смартфона: Android 4.0.3
    ОС среды Delphi: Windows XP
    Версия Delphi: XE5
     
    UPD. Небольшая поправка для пользователей Windows 7. Почему-то, по-умолчанию, драйверы на эту ОС ставятся некорректно и устройство помечается как "Драйверы не установлены". Достаточно запустить процедуру обновления драйверов и руками указать путь к ним. Драйверы лежат в папке "C:\Program Files\HTC\HTC Driver\Driver Files\Win7_x86\" после установки вышеописанной программы.

  9. Like
    Aptem получил реакцию от Brovin Yaroslav в Как определить локаль, установленную на устройстве iOS?   
    Большое спасибо, Ярослав! То, что нужно.
     
    Воспользовался кроссплатформенным сервисом FM, так как приложение тоже кроссплатформенное.
  10. Like
    Aptem отреагировална Brovin Yaroslav в Как определить локаль, установленную на устройстве iOS?   
    Добрый день,
     
    Например так, если использовать кроссплатформенный сервис FM:
    uses FMX.Platform; {$R *.fmx} procedure TForm2.Button1Click(Sender: TObject); var LocaleService: IFMXLocaleService; LangID: string; begin if TPlatformServices.Current.SupportsPlatformService(IFMXLocaleService, IInterface(LocaleService)) then LangID := LocaleService.GetCurrentLangID; end; Либо, как вы заметили, можно этого добиться используя на прямую iOS API:
    var CurrentLocale: NSLocale; LanguageISO: NSString; begin CurrentLocale := TNSLocale.Wrap(TNSLocale.OCClass.currentLocale); LanguageISO := TNSString.Wrap(CurrentLocale.objectForKey((NSLocaleLanguageCode as ILocalObject).GetObjectID)); // <-- Здесь берем то, что нужно от локали Result := UTF8ToString(LanguageISO.UTF8String); end;
  11. Like
    Aptem получил реакцию от Brovin Yaroslav в HTC Desire C. Делюсь опытом.   
    Коллеги, приветствую.
     
    Хочу поделиться успешным опытом подключения аппарата HTC Desire C к Delphi XE5. Никаких сложностей с подключением не возникло. Скачал с официального сайта HTC программу HTC Sync http://www.htc.com/ru/support/content.aspx?id=6301. В ее состав входят и драйверы. После установки саму программу HTC Sync снес, она мне как таковая не нужна. Смартфон автоматически появился в дереве target-устройств проекта.
     
     
    Выходные данные:
     
    Смартфон: HTC Desire C
    ОС смартфона: Android 4.0.3
    ОС среды Delphi: Windows XP
    Версия Delphi: XE5
     
    UPD. Небольшая поправка для пользователей Windows 7. Почему-то, по-умолчанию, драйверы на эту ОС ставятся некорректно и устройство помечается как "Драйверы не установлены". Достаточно запустить процедуру обновления драйверов и руками указать путь к ним. Драйверы лежат в папке "C:\Program Files\HTC\HTC Driver\Driver Files\Win7_x86\" после установки вышеописанной программы.

  12. Like
    Aptem получил реакцию от rareMax в Столицы мира   
    Автор: Артем Ходаев (Aptem)
    Ссылка на приложение: AppStore
     
    Выпустил свое первое приложения для iPhone - Страны мира.

    Смысл приложения не затейлив - справочник стран мира и их столиц. Уникальность его в том, что он содержит полный список всех признанных Россией стран мира. За основу взят Общероссийский классификатор стран мира. На текущий момент найти в AppStore подобное приложение (с полным списком) мне не удалось. Может плохо искал

    Также в приложении есть небольшой тест для проверки знаний столиц. Самый простой вариант - 10 вопросов по 5 вариантов ответа, в конце итог с правильными ответами.

    Планы на приложение огромные, от новых списков столиц уже не только мира, но и регионов отдельных стран. Например, столица Тосканы? Или столица штата Техас? И до интеллектуального алгоритма проверки знаний. Ну и версия под Android.
     

     
     
     

     
     
     

     
     
     

  13. Like
    Aptem получил реакцию от Brovin Yaroslav в Можно ли у TListViewItem при добавлении его в TListView установить свойство Accessory в None?   
    Сам же нашел ответ. В мануале меленько-меленько написано
    Вот здесь: http://docwiki.embarcadero.com/Libraries/XE5/en/Talk:FMX.ListView.TListViewItem.Accessory
     
     
  14. Like
    Aptem получил реакцию от Brovin Yaroslav в Столицы мира   
    Автор: Артем Ходаев (Aptem)
    Ссылка на приложение: AppStore
     
    Выпустил свое первое приложения для iPhone - Страны мира.

    Смысл приложения не затейлив - справочник стран мира и их столиц. Уникальность его в том, что он содержит полный список всех признанных Россией стран мира. За основу взят Общероссийский классификатор стран мира. На текущий момент найти в AppStore подобное приложение (с полным списком) мне не удалось. Может плохо искал

    Также в приложении есть небольшой тест для проверки знаний столиц. Самый простой вариант - 10 вопросов по 5 вариантов ответа, в конце итог с правильными ответами.

    Планы на приложение огромные, от новых списков столиц уже не только мира, но и регионов отдельных стран. Например, столица Тосканы? Или столица штата Техас? И до интеллектуального алгоритма проверки знаний. Ну и версия под Android.
     

     
     
     

     
     
     

     
     
     

  15. Like
    Aptem получил реакцию от Nix0N в Столицы мира   
    Автор: Артем Ходаев (Aptem)
    Ссылка на приложение: AppStore
     
    Выпустил свое первое приложения для iPhone - Страны мира.

    Смысл приложения не затейлив - справочник стран мира и их столиц. Уникальность его в том, что он содержит полный список всех признанных Россией стран мира. За основу взят Общероссийский классификатор стран мира. На текущий момент найти в AppStore подобное приложение (с полным списком) мне не удалось. Может плохо искал

    Также в приложении есть небольшой тест для проверки знаний столиц. Самый простой вариант - 10 вопросов по 5 вариантов ответа, в конце итог с правильными ответами.

    Планы на приложение огромные, от новых списков столиц уже не только мира, но и регионов отдельных стран. Например, столица Тосканы? Или столица штата Техас? И до интеллектуального алгоритма проверки знаний. Ну и версия под Android.
     

     
     
     

     
     
     

     
     
     

×
×
  • Создать...